From 347b0646d3eebcb4237a167dcd00a6f4e0b3a09d Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:28:30 +0800 Subject: [PATCH 001/189] Update argcheck.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/utils/argcheck.py | 254 ++++++++++++++++++++++++++++++--------- 1 file changed, 195 insertions(+), 59 deletions(-) diff --git a/deepmd/utils/argcheck.py b/deepmd/utils/argcheck.py index 8ee0a480a7..1a4603dcd2 100644 --- a/deepmd/utils/argcheck.py +++ b/deepmd/utils/argcheck.py @@ -6,7 +6,6 @@ Callable, List, Optional, - Union, ) from dargs import ( @@ -166,10 +165,7 @@ def __init__(self) -> None: def register( self, name: str, alias: Optional[List[str]] = None, doc: str = "" - ) -> Callable[ - [Union[Callable[[], Argument], Callable[[], List[Argument]]]], - Union[Callable[[], Argument], Callable[[], List[Argument]]], - ]: + ) -> Callable[[], List[Argument]]: """Register a descriptor argument plugin. Parameters @@ -181,8 +177,8 @@ def register( Returns ------- - Callable[[Union[Callable[[], Argument], Callable[[], List[Argument]]]], Union[Callable[[], Argument], Callable[[], List[Argument]]]] - decorator to return the registered descriptor argument method + Callable[[], List[Argument]] + the registered descriptor argument method Examples -------- @@ -213,17 +209,9 @@ def get_all_argument(self, exclude_hybrid: bool = False) -> List[Argument]: for (name, alias, doc), metd in self.__plugin.plugins.items(): if exclude_hybrid and name == "hybrid": continue - args = metd() - if isinstance(args, Argument): - arguments.append(args) - elif isinstance(args, list): - arguments.append( - Argument( - name=name, dtype=dict, sub_fields=metd(), alias=alias, doc=doc - ) - ) - else: - raise ValueError(f"Invalid return type {type(args)}") + arguments.append( + Argument(name=name, dtype=dict, sub_fields=metd(), alias=alias, doc=doc) + ) return arguments @@ -1529,11 +1517,6 @@ def model_compression_type_args(): ) -model_args_plugin = ArgsPlugin() -# for models that require another model as input -hybrid_model_args_plugin = ArgsPlugin() - - def model_args(exclude_hybrid=False): doc_type_map = "A list of strings. Give the name to each type of atoms. It is noted that the number of atom type of training system must be less than 128 in a GPU environment. If not given, type.raw in each system should use the same type indexes, and type_map.raw will take no effect." doc_data_stat_nbatch = "The model determines the normalization from the statistics of the data. This key specifies the number of `frames` in each `system` used for statistics." @@ -1557,7 +1540,12 @@ def model_args(exclude_hybrid=False): hybrid_models = [] if not exclude_hybrid: - hybrid_models.extend(hybrid_model_args_plugin.get_all_argument()) + hybrid_models.extend( + [ + pairwise_dprc(), + linear_ener_model_args(), + ] + ) return Argument( "model", dict, @@ -1656,7 +1644,9 @@ def model_args(exclude_hybrid=False): Variant( "type", [ - *model_args_plugin.get_all_argument(), + standard_model_args(), + frozen_model_args(), + pairtab_model_args(), *hybrid_models, ], optional=True, @@ -1666,7 +1656,6 @@ def model_args(exclude_hybrid=False): ) -@model_args_plugin.register("standard") def standard_model_args() -> Argument: doc_descrpt = "The descriptor of atomic environment." doc_fitting = "The fitting of physical properties." @@ -1691,7 +1680,6 @@ def standard_model_args() -> Argument: return ca -@hybrid_model_args_plugin.register("pairwise_dprc") def pairwise_dprc() -> Argument: qm_model_args = model_args(exclude_hybrid=True) qm_model_args.name = "qm_model" @@ -1711,7 +1699,6 @@ def pairwise_dprc() -> Argument: return ca -@model_args_plugin.register("frozen") def frozen_model_args() -> Argument: doc_model_file = "Path to the frozen model file." ca = Argument( @@ -1724,7 +1711,6 @@ def frozen_model_args() -> Argument: return ca -@model_args_plugin.register("pairtab") def pairtab_model_args() -> Argument: doc_tab_file = "Path to the tabulation file." doc_rcut = "The cut-off radius." @@ -1745,7 +1731,6 @@ def pairtab_model_args() -> Argument: return ca -@hybrid_model_args_plugin.register("linear_ener") def linear_ener_model_args() -> Argument: doc_weights = ( "If the type is list of float, a list of weights for each model. " @@ -1820,7 +1805,7 @@ def learning_rate_variant_type_args(): ) -def learning_rate_args(fold_subdoc: bool = False) -> Argument: +def learning_rate_args(): doc_scale_by_worker = "When parallel training or batch size scaled, how to alter learning rate. Valid values are `linear`(default), `sqrt` or `none`." doc_lr = "The definitio of learning rate" return Argument( @@ -1838,7 +1823,6 @@ def learning_rate_args(fold_subdoc: bool = False) -> Argument: [learning_rate_variant_type_args()], optional=True, doc=doc_lr, - fold_subdoc=fold_subdoc, ) @@ -1866,6 +1850,8 @@ def loss_ener(): doc_limit_pref_f = limit_pref("force") doc_start_pref_v = start_pref("virial", abbr="v") doc_limit_pref_v = limit_pref("virial") + doc_start_pref_h = start_pref("hessian", abbr="h") # anchor added + doc_limit_pref_h = limit_pref("hessian") # anchor added doc_start_pref_ae = start_pref("atomic energy", label="atom_ener", abbr="ae") doc_limit_pref_ae = limit_pref("atomic energy") doc_start_pref_pf = start_pref( @@ -1920,6 +1906,20 @@ def loss_ener(): default=0.00, doc=doc_limit_pref_v, ), + Argument( + "start_pref_h", + [float, int], + optional=True, + default=0.00, + doc=doc_start_pref_h, + ), # anchor added + Argument( + "limit_pref_h", + [float, int], + optional=True, + default=0.00, + doc=doc_limit_pref_h, + ), # anchor added Argument( "start_pref_ae", [float, int], @@ -1979,6 +1979,142 @@ def loss_ener(): ), ] +@loss_args_plugin.register("ener_hess") +def loss_ener_hess(): # anchor added + doc_start_pref_e = start_pref("energy", abbr="e") + doc_limit_pref_e = limit_pref("energy") + doc_start_pref_f = start_pref("force", abbr="f") + doc_limit_pref_f = limit_pref("force") + doc_start_pref_v = start_pref("virial", abbr="v") + doc_limit_pref_v = limit_pref("virial") + doc_start_pref_h = start_pref("hessian", abbr="h") # anchor added + doc_limit_pref_h = limit_pref("hessian") # anchor added + doc_start_pref_ae = start_pref("atomic energy", label="atom_ener", abbr="ae") + doc_limit_pref_ae = limit_pref("atomic energy") + doc_start_pref_pf = start_pref( + "atomic prefactor force", label="atom_pref", abbr="pf" + ) + doc_limit_pref_pf = limit_pref("atomic prefactor force") + doc_start_pref_gf = start_pref("generalized force", label="drdq", abbr="gf") + doc_limit_pref_gf = limit_pref("generalized force") + doc_numb_generalized_coord = "The dimension of generalized coordinates. Required when generalized force loss is used." + doc_relative_f = "If provided, relative force error will be used in the loss. The difference of force will be normalized by the magnitude of the force in the label with a shift given by `relative_f`, i.e. DF_i / ( || F || + relative_f ) with DF denoting the difference between prediction and label and || F || denoting the L2 norm of the label." + doc_enable_atom_ener_coeff = "If true, the energy will be computed as \\sum_i c_i E_i. c_i should be provided by file atom_ener_coeff.npy in each data system, otherwise it's 1." + return [ + Argument( + "start_pref_e", + [float, int], + optional=True, + default=0.02, + doc=doc_start_pref_e, + ), + Argument( + "limit_pref_e", + [float, int], + optional=True, + default=1.00, + doc=doc_limit_pref_e, + ), + Argument( + "start_pref_f", + [float, int], + optional=True, + default=1000, + doc=doc_start_pref_f, + ), + Argument( + "limit_pref_f", + [float, int], + optional=True, + default=1.00, + doc=doc_limit_pref_f, + ), + Argument( + "start_pref_v", + [float, int], + optional=True, + default=0.00, + doc=doc_start_pref_v, + ), + Argument( + "limit_pref_v", + [float, int], + optional=True, + default=0.00, + doc=doc_limit_pref_v, + ), + Argument( + "start_pref_h", + [float, int], + optional=True, + default=0.00, + doc=doc_start_pref_h, + ), # anchor added + Argument( + "limit_pref_h", + [float, int], + optional=True, + default=0.00, + doc=doc_limit_pref_h, + ), # anchor added + Argument( + "start_pref_ae", + [float, int], + optional=True, + default=0.00, + doc=doc_start_pref_ae, + ), + Argument( + "limit_pref_ae", + [float, int], + optional=True, + default=0.00, + doc=doc_limit_pref_ae, + ), + Argument( + "start_pref_pf", + [float, int], + optional=True, + default=0.00, + doc=doc_start_pref_pf, + ), + Argument( + "limit_pref_pf", + [float, int], + optional=True, + default=0.00, + doc=doc_limit_pref_pf, + ), + Argument("relative_f", [float, None], optional=True, doc=doc_relative_f), + Argument( + "enable_atom_ener_coeff", + [bool], + optional=True, + default=False, + doc=doc_enable_atom_ener_coeff, + ), + Argument( + "start_pref_gf", + float, + optional=True, + default=0.0, + doc=doc_start_pref_gf, + ), + Argument( + "limit_pref_gf", + float, + optional=True, + default=0.0, + doc=doc_limit_pref_gf, + ), + Argument( + "numb_generalized_coord", + int, + optional=True, + default=0, + doc=doc_numb_generalized_coord, + ), + ] @loss_args_plugin.register("ener_spin") def loss_ener_spin(): @@ -1990,6 +2126,8 @@ def loss_ener_spin(): doc_limit_pref_fm = limit_pref("force_magnetic") doc_start_pref_v = start_pref("virial") doc_limit_pref_v = limit_pref("virial") + doc_start_pref_h = start_pref("hessian") # anchor added + doc_limit_pref_h = limit_pref("hessian") # anchor added doc_start_pref_ae = start_pref("atom_ener") doc_limit_pref_ae = limit_pref("atom_ener") doc_start_pref_pf = start_pref("atom_pref") @@ -2053,6 +2191,20 @@ def loss_ener_spin(): default=0.00, doc=doc_limit_pref_v, ), + Argument( + "start_pref_h", + [float, int], + optional=True, + default=0.00, + doc=doc_start_pref_h, + ), # anchor added + Argument( + "limit_pref_h", + [float, int], + optional=True, + default=0.00, + doc=doc_limit_pref_h, + ), # anchor added Argument( "start_pref_ae", [float, int], @@ -2561,7 +2713,6 @@ def multi_model_args(): model_dict = model_args() model_dict.name = "model_dict" model_dict.repeat = True - model_dict.fold_subdoc = True model_dict.doc = ( "The multiple definition of the model, used in the multi-task mode." ) @@ -2582,7 +2733,6 @@ def multi_loss_args(): loss_dict = loss_args() loss_dict.name = "loss_dict" loss_dict.repeat = True - loss_dict.fold_subdoc = True loss_dict.doc = "The multiple definition of the loss, used in the multi-task mode." return loss_dict @@ -2594,11 +2744,11 @@ def make_index(keys): return ", ".join(ret) -def gen_doc(*, make_anchor=True, make_link=True, multi_task=False, **kwargs) -> str: +def gen_doc(*, make_anchor=True, make_link=True, **kwargs): if make_link: make_anchor = True ptr = [] - for ii in gen_args(multi_task=multi_task): + for ii in gen_args(): ptr.append(ii.gen_doc(make_anchor=make_anchor, make_link=make_link, **kwargs)) key_words = [] @@ -2610,14 +2760,14 @@ def gen_doc(*, make_anchor=True, make_link=True, multi_task=False, **kwargs) -> return "\n\n".join(ptr) -def gen_json(multi_task: bool = False, **kwargs) -> str: +def gen_json(**kwargs): return json.dumps( - tuple(gen_args(multi_task=multi_task)), + tuple(gen_args()), cls=ArgumentEncoder, ) -def gen_args(multi_task: bool = False) -> List[Argument]: +def gen_args(multi_task=False) -> List[Argument]: if not multi_task: return [ model_args(), @@ -2629,24 +2779,14 @@ def gen_args(multi_task: bool = False) -> List[Argument]: else: return [ multi_model_args(), - learning_rate_args(fold_subdoc=True), + learning_rate_args(), multi_loss_args(), training_args(multi_task=multi_task), - nvnmd_args(fold_subdoc=True), + nvnmd_args(), ] -def gen_args_multi_task() -> Argument: - """Generate multi-task arguments.""" - return Argument( - "multi-task", - dict, - sub_fields=gen_args(multi_task=True), - doc="Multi-task arguments.", - ) - - -def gen_json_schema(multi_task: bool = False) -> str: +def gen_json_schema() -> str: """Generate JSON schema. Returns @@ -2654,16 +2794,11 @@ def gen_json_schema(multi_task: bool = False) -> str: str JSON schema. """ - arg = Argument( - "DeePMD-kit", - dict, - gen_args(multi_task=multi_task), - doc=f"DeePMD-kit {__version__}", - ) + arg = Argument("DeePMD-kit", dict, gen_args(), doc=f"DeePMD-kit {__version__}") return json.dumps(generate_json_schema(arg)) -def normalize(data, multi_task: bool = False): +def normalize(data, multi_task=False): base = Argument("base", dict, gen_args(multi_task=multi_task)) data = base.normalize_value(data, trim_pattern="_*") base.check_value(data, strict=True) @@ -2673,3 +2808,4 @@ def normalize(data, multi_task: bool = False): if __name__ == "__main__": gen_doc() + From 9a6b8964e6230d71400df19a04b607cea5818164 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:29:10 +0800 Subject: [PATCH 002/189] Update data.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/utils/data.py | 52 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/deepmd/utils/data.py b/deepmd/utils/data.py index 5d324afb95..30408ba295 100644 --- a/deepmd/utils/data.py +++ b/deepmd/utils/data.py @@ -25,7 +25,7 @@ class DeepmdData: """Class for a data system. - It loads data from hard disk, and mantains the data as a `data_dict` + It loads data from hard disk, and maintains the data as a `data_dict` Parameters ---------- @@ -267,7 +267,7 @@ def get_batch(self, batch_size: int) -> dict: iterator_1 = self.iterator + batch_size if iterator_1 >= set_size: iterator_1 = set_size - idx = np.arange(self.iterator, iterator_1) # pylint: disable=no-explicit-dtype + idx = np.arange(self.iterator, iterator_1) self.iterator += batch_size ret = self._get_subdata(self.batch_set, idx) return ret @@ -291,7 +291,7 @@ def get_test(self, ntests: int = -1) -> dict: else self.test_set["type"].shape[0] ) # print('ntest', self.test_set['type'].shape[0], ntests, ntests_) - idx = np.arange(ntests_) # pylint: disable=no-explicit-dtype + idx = np.arange(ntests_) ret = self._get_subdata(self.test_set, idx=idx) if self.modifier is not None: self.modifier.modify_data(ret, self) @@ -379,14 +379,14 @@ def _idx_map_sel(self, atom_type, type_sel): new_types.append(ii) new_types = np.array(new_types, dtype=int) natoms = new_types.shape[0] - idx = np.arange(natoms) # pylint: disable=no-explicit-dtype + idx = np.arange(natoms) idx_map = np.lexsort((idx, new_types)) return idx_map def _get_natoms_2(self, ntypes): sample_type = self.atom_type natoms = len(sample_type) - natoms_vec = np.zeros(ntypes).astype(int) # pylint: disable=no-explicit-dtype + natoms_vec = np.zeros(ntypes).astype(int) for ii in range(ntypes): natoms_vec[ii] = np.count_nonzero(sample_type == ii) return natoms, natoms_vec @@ -436,7 +436,7 @@ def _load_test_set(self, shuffle_test: bool): def _shuffle_data(self, data): ret = {} nframes = data["coord"].shape[0] - idx = np.arange(nframes) # pylint: disable=no-explicit-dtype + idx = np.arange(nframes) # the training times of each frame idx = np.repeat(idx, np.reshape(data["numb_copy"], (nframes,))) dp_random.shuffle(idx) @@ -479,6 +479,12 @@ def reformat_data_torch(self, data): pass else: if kk in data and self.data_dict[kk]["atomic"]: + # if kk == "hessian": # anchor added + # print( + # f"{kk} original shape is {data[kk].shape}\n" + # f"{kk} is reformated to {data[kk].shape}\n" + # f"{data[kk][:5]}\n" + # ) data[kk] = data[kk].reshape(-1, self.data_dict[kk]["ndof"]) data["atype"] = data["type"] if not self.pbc: @@ -500,6 +506,9 @@ def _load_set(self, set_name: DPPath): assert coord.shape[1] == self.data_dict["coord"]["ndof"] * self.natoms # load keys data = {} + # print(f"data_dict is {self.data_dict.keys()}") # anchor added + # print(f"self.data_dict['hessian'] is {self.data_dict['hessian']}") # anchor added + # print(f"self.data_dict['force'] is {self.data_dict['force']}") # anchor added for kk in self.data_dict.keys(): if self.data_dict[kk]["reduce"] is None: data["find_" + kk], data[kk] = self._load_data( @@ -609,6 +618,7 @@ def _load_data( path = set_name / (key + ".npy") if path.is_file(): data = path.load_numpy().astype(dtype) + # print(f"{path} is loaded") # anchor added try: # YWolfeee: deal with data shape error if atomic: if type_sel is not None: @@ -644,10 +654,29 @@ def _load_data( f"({nframes}, {natoms_sel}, {ndof_}) or" f"({nframes}, {natoms}, {ndof_})" ) - data = data.reshape([nframes, natoms, -1]) - data = data[:, idx_map, :] - data = data.reshape([nframes, -1]) + if key == "hessian": # handle hessian data-I; anchor created + data = data.reshape(nframes, 3 * natoms, 3 * natoms) + # print(f"hessian reshape before idx_map: {data.shape}") + # get idx_map for hessian + num_chunks, chunk_size = len(idx_map), 3 + idx_map_hess = np.arange(num_chunks * chunk_size) + idx_map_hess = idx_map_hess.reshape(num_chunks, chunk_size) + idx_map_hess = idx_map_hess[idx_map] + idx_map_hess = idx_map_hess.flatten() + data = data[:, idx_map_hess, :] + data = data[:, :, idx_map_hess] + data = data.reshape([nframes, -1]) + # print(f"hessian reshape after idx_map: {data.shape}") + ndof = 3 * ndof * 3 * ndof # size of hessian is 3Natoms * 3Natoms + # print(f"n_dof of hessian is {ndof}") + else: + data = data.reshape([nframes, natoms, -1]) + data = data[:, idx_map, :] + data = data.reshape([nframes, -1]) data = np.reshape(data, [nframes, ndof]) + # data = np.reshape(data, [nframes, -1]) # anchor modified + # if key == "hessian": # anchor added + # print(f"hessian out if_atomic: {data.shape}") except ValueError as err_message: explanation = "This error may occur when your label mismatch it's name, i.e. you might store global tensor in `atomic_tensor.npy` or atomic tensor in `tensor.npy`." log.error(str(err_message)) @@ -655,6 +684,8 @@ def _load_data( raise ValueError(str(err_message) + ". " + explanation) from err_message if repeat != 1: data = np.repeat(data, repeat).reshape([nframes, -1]) + # if key == "hessian": # anchor added + # print(f"{key} in _load_data: {data.shape}\n") return np.float32(1.0), data elif must: raise RuntimeError(f"{path} not found!") @@ -677,7 +708,7 @@ def _load_type_mix(self, set_name: DPPath): def _make_idx_map(self, atom_type): natoms = atom_type.shape[0] - idx = np.arange(natoms) # pylint: disable=no-explicit-dtype + idx = np.arange(natoms) if self.sort_atoms: idx_map = np.lexsort((idx, atom_type)) else: @@ -782,3 +813,4 @@ def __eq__(self, __value: object) -> bool: def __repr__(self) -> str: return f"DataRequirementItem({self.dict})" + From 717e672ba568aacdd8960e3499b615919722076f Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:29:39 +0800 Subject: [PATCH 003/189] Update training.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/training.py | 65 +++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index a7a97b3f8d..d4ef3b424e 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -28,6 +28,7 @@ DOSLoss, EnergySpinLoss, EnergyStdLoss, + EnergyHessianStdLoss, # anchor added TensorLoss, ) from deepmd.pt.model.model import ( @@ -67,6 +68,9 @@ from deepmd.utils.data import ( DataRequirementItem, ) +from deepmd.pt.model.model import ( + make_hessian_model +) # anchor added if torch.__version__.startswith("2"): import torch._dynamo @@ -115,6 +119,7 @@ def __init__( resuming = resume_model is not None self.restart_training = restart_model is not None model_params = config["model"] + # print(f"keys of model_params: {model_params.keys()}") # anchor added training_params = config["training"] self.multi_task = "model_dict" in model_params self.finetune_links = finetune_links @@ -184,7 +189,6 @@ def get_dataloader_and_buffer(_data, _params): if dist.is_available() else 0, # setting to 0 diverges the behavior of its iterator; should be >=1 drop_last=False, - collate_fn=lambda batch: batch, # prevent extra conversion pin_memory=True, ) with torch.device("cpu"): @@ -273,8 +277,20 @@ def get_lr(lr_params): else: self.opt_type, self.opt_param = get_opt_param(training_params) + # anchor added: loss_param_tmp for Hessian activation + loss_param_tmp = None + if not self.multi_task: + loss_param_tmp = config["loss"] + else: + for model_key in self.model_keys: + loss_param_tmp = config["loss_dict"][model_key] + # Model - self.model = get_model_for_wrapper(model_params) + dp_random.seed(training_params["seed"]) + if training_params["seed"] is not None: + torch.manual_seed(training_params["seed"]) + + self.model = get_model_for_wrapper(model_params, loss_param_tmp) # Loss if not self.multi_task: @@ -298,6 +314,7 @@ def get_lr(lr_params): ) # Data + dp_random.seed(training_params["seed"]) if not self.multi_task: self.get_sample_func = single_model_stat( self.model, @@ -933,7 +950,7 @@ def log_loss_valid(_task_key="Default"): continue if self.multi_task: chosen_index_list = dp_random.choice( - np.arange(self.num_model), # pylint: disable=no-explicit-dtype + np.arange(self.num_model), p=np.array(self.model_prob), size=self.world_size, replace=True, @@ -1089,7 +1106,7 @@ def get_data(self, is_train=True, task_key="Default"): batch_data = next(iter(self.validation_data[task_key])) for key in batch_data.keys(): - if key == "sid" or key == "fid" or key == "box" or "find_" in key: + if key == "sid" or key == "fid" or key == "box": continue elif not isinstance(batch_data[key], list): if batch_data[key] is not None: @@ -1211,10 +1228,28 @@ def get_additional_data_requirement(_model): return additional_data_requirement -def get_loss(loss_params, start_lr, _ntypes, _model): +def whether_hessian(loss_params): # anchor created loss_type = loss_params.get("type", "ener") if loss_type == "ener": + if loss_params["start_pref_h"] > 0.0: + # print("hessian mode is detected") + return True + else: + return False + + +def get_loss(loss_params, start_lr, _ntypes, _model): + loss_type = loss_params.get("type", "ener") + # print(f"loss_type: {loss_type}") # anchor added + # print(f"loss_params: {loss_params}") # anchor added + # print(f"start_lr: {start_lr}") # anchor added + if whether_hessian(loss_params): # anchor added loss_params["starter_learning_rate"] = start_lr + # print(f"EnergyHessianStdLoss(**loss_params): {EnergyHessianStdLoss(**loss_params)}") + return EnergyHessianStdLoss(**loss_params) + elif loss_type == "ener": + loss_params["starter_learning_rate"] = start_lr + # print(f"EnergyStdLoss(**loss_params): {EnergyStdLoss(**loss_params)}") # anchor added return EnergyStdLoss(**loss_params) elif loss_type == "dos": loss_params["starter_learning_rate"] = start_lr @@ -1253,7 +1288,15 @@ def get_single_model( return model -def get_model_for_wrapper(_model_params): +def get_model_for_wrapper( + _model_params, + _loss_params=None, # anchor added +): + if _loss_params is not None: # anchor added + if whether_hessian(_loss_params): + # print("hessian model is utilized") + _model_params["hessian_mode"] = True + # print("hessian_mode(True) is added to model_params") if "model_dict" not in _model_params: _model = get_single_model( _model_params, @@ -1265,7 +1308,14 @@ def get_model_for_wrapper(_model_params): _model[_model_key] = get_single_model( _model_params["model_dict"][_model_key], ) - return _model + # if _loss_params is not None: # anchor added + # if whether_hessian(_loss_params): + # print("hessian model is utilized") + # # return make_hessian_model(_model) + # return _model + # else: + # return _model + return _model # anchor noted: original return _model only def model_change_out_bias( @@ -1287,3 +1337,4 @@ def model_change_out_bias( f"to {to_numpy_array(new_bias).reshape(-1)!s}." ) return _model + From 9ce1c5fffe3cd3c28b897b1aca1d5674a8f068e7 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:30:02 +0800 Subject: [PATCH 004/189] Update wrapper.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/wrapper.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deepmd/pt/train/wrapper.py b/deepmd/pt/train/wrapper.py index 6bc7cdc87a..501af4eb56 100644 --- a/deepmd/pt/train/wrapper.py +++ b/deepmd/pt/train/wrapper.py @@ -173,6 +173,8 @@ def forward( model_pred = self.model[task_key](**input_dict) return model_pred, None, None else: + # torch.save(self.model[task_key].state_dict(), 'b.pt') # anchor added + # self.model[task_key].load_state_dict(torch.load('./b.pt')) # anchor added natoms = atype.shape[-1] model_pred, loss, more_loss = self.loss[task_key]( input_dict, @@ -194,3 +196,4 @@ def get_extra_state(self) -> Dict: "train_infos": self.train_infos, } return state + From 2bedfc3fbdfa8a020f75618b73e8e3b89580d7ff Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:30:49 +0800 Subject: [PATCH 005/189] Create ener_hess.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/ener_hess.py | 476 ++++++++++++++++++++++++++++++++++++ 1 file changed, 476 insertions(+) create mode 100644 deepmd/pt/loss/ener_hess.py diff --git a/deepmd/pt/loss/ener_hess.py b/deepmd/pt/loss/ener_hess.py new file mode 100644 index 0000000000..c0c00f6638 --- /dev/null +++ b/deepmd/pt/loss/ener_hess.py @@ -0,0 +1,476 @@ +# PDX-License-Identifier: LGPL-3.0-or-later +# anchor created +from typing import ( + List, + Optional, +) + +import torch +import torch.nn.functional as F + +from deepmd.pt.loss.loss import ( + TaskLoss, +) +from deepmd.pt.utils import ( + env, +) +from deepmd.pt.utils.env import ( + GLOBAL_PT_FLOAT_PRECISION, +) +from deepmd.utils.data import ( + DataRequirementItem, +) +import numpy as np # anchor added + + +class EnergyHessianStdLoss(TaskLoss): + def __init__( + self, + starter_learning_rate=1.0, + start_pref_e=0.0, + limit_pref_e=0.0, + start_pref_f=0.0, + limit_pref_f=0.0, + start_pref_v=0.0, + limit_pref_v=0.0, + start_pref_h=0.0, + limit_pref_h=0.0, + start_pref_ae: float = 0.0, + limit_pref_ae: float = 0.0, + start_pref_pf: float = 0.0, + limit_pref_pf: float = 0.0, + relative_f: Optional[float] = None, + enable_atom_ener_coeff: bool = False, + start_pref_gf: float = 0.0, + limit_pref_gf: float = 0.0, + numb_generalized_coord: int = 0, + use_l1_all: bool = False, + inference=False, + **kwargs, + ): + r"""Construct a layer to compute loss on energy, force and virial. + + Parameters + ---------- + starter_learning_rate : float + The learning rate at the start of the training. + start_pref_e : float + The prefactor of energy loss at the start of the training. + limit_pref_e : float + The prefactor of energy loss at the end of the training. + start_pref_f : float + The prefactor of force loss at the start of the training. + limit_pref_f : float + The prefactor of force loss at the end of the training. + start_pref_v : float + The prefactor of virial loss at the start of the training. + limit_pref_v : float + The prefactor of virial loss at the end of the training. + start_pref_h : float + The prefactor of hessian loss at the start of the training. + limit_pref_h : float + The prefactor of hessian loss at the end of the training. + start_pref_ae : float + The prefactor of atomic energy loss at the start of the training. + limit_pref_ae : float + The prefactor of atomic energy loss at the end of the training. + start_pref_pf : float + The prefactor of atomic prefactor force loss at the start of the training. + limit_pref_pf : float + The prefactor of atomic prefactor force loss at the end of the training. + relative_f : float + If provided, relative force error will be used in the loss. The difference + of force will be normalized by the magnitude of the force in the label with + a shift given by relative_f + enable_atom_ener_coeff : bool + if true, the energy will be computed as \sum_i c_i E_i + start_pref_gf : float + The prefactor of generalized force loss at the start of the training. + limit_pref_gf : float + The prefactor of generalized force loss at the end of the training. + numb_generalized_coord : int + The dimension of generalized coordinates. + use_l1_all : bool + Whether to use L1 loss, if False (default), it will use L2 loss. + inference : bool + If true, it will output all losses found in output, ignoring the pre-factors. + **kwargs + Other keyword arguments. + """ + super().__init__() + self.starter_learning_rate = starter_learning_rate + self.has_e = (start_pref_e != 0.0 and limit_pref_e != 0.0) or inference + self.has_f = (start_pref_f != 0.0 and limit_pref_f != 0.0) or inference + self.has_v = (start_pref_v != 0.0 and limit_pref_v != 0.0) or inference + self.has_h = (start_pref_h != 0.0 and limit_pref_h != 0.0) or inference + self.has_ae = (start_pref_ae != 0.0 and limit_pref_ae != 0.0) or inference + self.has_pf = (start_pref_pf != 0.0 and limit_pref_pf != 0.0) or inference + self.has_gf = start_pref_gf != 0.0 and limit_pref_gf != 0.0 + + self.start_pref_e = start_pref_e + self.limit_pref_e = limit_pref_e + self.start_pref_f = start_pref_f + self.limit_pref_f = limit_pref_f + self.start_pref_v = start_pref_v + self.limit_pref_v = limit_pref_v + self.start_pref_h = start_pref_h + self.limit_pref_h = limit_pref_h + self.start_pref_ae = start_pref_ae + self.limit_pref_ae = limit_pref_ae + self.start_pref_pf = start_pref_pf + self.limit_pref_pf = limit_pref_pf + self.start_pref_gf = start_pref_gf + self.limit_pref_gf = limit_pref_gf + self.relative_f = relative_f + self.enable_atom_ener_coeff = enable_atom_ener_coeff + self.numb_generalized_coord = numb_generalized_coord + if self.has_gf and self.numb_generalized_coord < 1: + raise RuntimeError( + "When generalized force loss is used, the dimension of generalized coordinates should be larger than 0" + ) + self.use_l1_all = use_l1_all + self.inference = inference + + def forward(self, input_dict, model, label, natoms, learning_rate, mae=False): + """Return loss on energy and force. + + Parameters + ---------- + input_dict : dict[str, torch.Tensor] + Model inputs. + model : torch.nn.Module + Model to be used to output the predictions. + label : dict[str, torch.Tensor] + Labels. + natoms : int + The local atom number. + + Returns + ------- + model_pred: dict[str, torch.Tensor] + Model predictions. + loss: torch.Tensor + Loss for model to minimize. + more_loss: dict[str, torch.Tensor] + Other losses for display. + """ + model_pred = model(**input_dict) + # print(f"keys of input_dict: {input_dict.keys()}") # anchor added + # print(f"keys of model_pred: {model_pred.keys()}") # anchor added + # print(f"keys of label: {label.keys()}") # anchor added + # print(f"label['find_hessian']: {label['hessian'].size()}, {label['find_hessian']}") # anchor added + # na3 = int(label['hessian'].cpu().detach().numpy().shape[1] ** 0.5) # hess size is [1,na3*na3,1]; anchor added + # print(f"label['hessian']: {label['hessian'].cpu().detach().numpy().reshape(na3, -1)[:5,:5]}") # anchor added + # print(f"label['find_force']: {label['force'].size()}, {label['find_force']}") # anchor added + # print(f"label['find_energy']: {label['energy'].size()}, {label['find_energy']}") # anchor added + # print(f"model: {type(model)}") # anchor added + coef = learning_rate / self.starter_learning_rate + pref_e = self.limit_pref_e + (self.start_pref_e - self.limit_pref_e) * coef + pref_f = self.limit_pref_f + (self.start_pref_f - self.limit_pref_f) * coef + pref_v = self.limit_pref_v + (self.start_pref_v - self.limit_pref_v) * coef + pref_h = self.limit_pref_h + (self.start_pref_h - self.limit_pref_h) * coef + pref_ae = self.limit_pref_ae + (self.start_pref_ae - self.limit_pref_ae) * coef + pref_pf = self.limit_pref_pf + (self.start_pref_pf - self.limit_pref_pf) * coef + pref_gf = self.limit_pref_gf + (self.start_pref_gf - self.limit_pref_gf) * coef + + loss = torch.zeros(1, dtype=env.GLOBAL_PT_FLOAT_PRECISION, device=env.DEVICE)[0] + more_loss = {} + # more_loss['log_keys'] = [] # showed when validation on the fly + # more_loss['test_keys'] = [] # showed when doing dp test + atom_norm = 1.0 / natoms + if self.has_e and "energy" in model_pred and "energy" in label: + # print("{if self.has_e and 'energy' in model_pred and 'energy' in label} is valid") # anchor added + energy_pred = model_pred["energy"] + energy_label = label["energy"] + if self.enable_atom_ener_coeff and "atom_energy" in model_pred: + atom_ener_pred = model_pred["atom_energy"] + # when ener_coeff (\nu) is defined, the energy is defined as + # E = \sum_i \nu_i E_i + # instead of the sum of atomic energies. + # + # A case is that we want to train reaction energy + # A + B -> C + D + # E = - E(A) - E(B) + E(C) + E(D) + # A, B, C, D could be put far away from each other + atom_ener_coeff = label["atom_ener_coeff"] + atom_ener_coeff = atom_ener_coeff.reshape(atom_ener_pred.shape) + energy_pred = torch.sum(atom_ener_coeff * atom_ener_pred, dim=1) + find_energy = label.get("find_energy", 0.0) + pref_e = pref_e * find_energy + if not self.use_l1_all: + l2_ener_loss = torch.mean(torch.square(energy_pred - energy_label)) + if not self.inference: + more_loss["l2_ener_loss"] = self.display_if_exist( + l2_ener_loss.detach(), find_energy + ) + loss += atom_norm * (pref_e * l2_ener_loss) + rmse_e = l2_ener_loss.sqrt() * atom_norm + more_loss["rmse_e"] = self.display_if_exist( + rmse_e.detach(), find_energy + ) + # more_loss['log_keys'].append('rmse_e') + else: # use l1 and for all atoms + l1_ener_loss = F.l1_loss( + energy_pred.reshape(-1), + energy_label.reshape(-1), + reduction="sum", + ) + loss += pref_e * l1_ener_loss + more_loss["mae_e"] = self.display_if_exist( + F.l1_loss( + energy_pred.reshape(-1), + energy_label.reshape(-1), + reduction="mean", + ).detach(), + find_energy, + ) + # more_loss['log_keys'].append('rmse_e') + if mae: + mae_e = torch.mean(torch.abs(energy_pred - energy_label)) * atom_norm + more_loss["mae_e"] = self.display_if_exist(mae_e.detach(), find_energy) + mae_e_all = torch.mean(torch.abs(energy_pred - energy_label)) + more_loss["mae_e_all"] = self.display_if_exist( + mae_e_all.detach(), find_energy + ) + + if ( + (self.has_f or self.has_pf or self.relative_f or self.has_gf) + and "force" in model_pred + and "force" in label + ): + # print("{if self.has_f and 'force' in model_pred and 'force' in label} is valid") # anchor added + find_force = label.get("find_force", 0.0) + pref_f = pref_f * find_force + force_pred = model_pred["force"] + force_label = label["force"] + diff_f = (force_label - force_pred).reshape(-1) + + if self.relative_f is not None: + force_label_3 = force_label.reshape(-1, 3) + norm_f = force_label_3.norm(dim=1, keepdim=True) + self.relative_f + diff_f_3 = diff_f.reshape(-1, 3) + diff_f_3 = diff_f_3 / norm_f + diff_f = diff_f_3.reshape(-1) + + if self.has_f: + if not self.use_l1_all: + l2_force_loss = torch.mean(torch.square(diff_f)) + if not self.inference: + more_loss["l2_force_loss"] = self.display_if_exist( + l2_force_loss.detach(), find_force + ) + loss += (pref_f * l2_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) + rmse_f = l2_force_loss.sqrt() + more_loss["rmse_f"] = self.display_if_exist( + rmse_f.detach(), find_force + ) + else: + l1_force_loss = F.l1_loss(force_label, force_pred, reduction="none") + more_loss["mae_f"] = self.display_if_exist( + l1_force_loss.mean().detach(), find_force + ) + l1_force_loss = l1_force_loss.sum(-1).mean(-1).sum() + loss += (pref_f * l1_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) + if mae: + mae_f = torch.mean(torch.abs(diff_f)) + more_loss["mae_f"] = self.display_if_exist( + mae_f.detach(), find_force + ) + + if self.has_pf and "atom_pref" in label: + atom_pref = label["atom_pref"] + find_atom_pref = label.get("find_atom_pref", 0.0) + pref_pf = pref_pf * find_atom_pref + atom_pref_reshape = atom_pref.reshape(-1) + l2_pref_force_loss = (torch.square(diff_f) * atom_pref_reshape).mean() + if not self.inference: + more_loss["l2_pref_force_loss"] = self.display_if_exist( + l2_pref_force_loss.detach(), find_atom_pref + ) + loss += (pref_pf * l2_pref_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) + rmse_pf = l2_pref_force_loss.sqrt() + more_loss["rmse_pf"] = self.display_if_exist( + rmse_pf.detach(), find_atom_pref + ) + + if self.has_gf and "drdq" in label: + drdq = label["drdq"] + find_drdq = label.get("find_drdq", 0.0) + pref_gf = pref_gf * find_drdq + force_reshape_nframes = force_pred.reshape(-1, natoms * 3) + force_label_reshape_nframes = force_label.reshape(-1, natoms * 3) + drdq_reshape = drdq.reshape(-1, natoms * 3, self.numb_generalized_coord) + gen_force_label = torch.einsum( + "bij,bi->bj", drdq_reshape, force_label_reshape_nframes + ) + gen_force = torch.einsum( + "bij,bi->bj", drdq_reshape, force_reshape_nframes + ) + diff_gen_force = gen_force_label - gen_force + l2_gen_force_loss = torch.square(diff_gen_force).mean() + if not self.inference: + more_loss["l2_gen_force_loss"] = self.display_if_exist( + l2_gen_force_loss.detach(), find_drdq + ) + loss += (pref_gf * l2_gen_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) + rmse_gf = l2_gen_force_loss.sqrt() + more_loss["rmse_gf"] = self.display_if_exist( + rmse_gf.detach(), find_drdq + ) + + if self.has_v and "virial" in model_pred and "virial" in label: + # print("{if self.has_v and 'virial' in model_pred and 'virial' in label} is valid") # anchor added + find_virial = label.get("find_virial", 0.0) + pref_v = pref_v * find_virial + diff_v = label["virial"] - model_pred["virial"].reshape(-1, 9) + l2_virial_loss = torch.mean(torch.square(diff_v)) + if not self.inference: + more_loss["l2_virial_loss"] = self.display_if_exist( + l2_virial_loss.detach(), find_virial + ) + loss += atom_norm * (pref_v * l2_virial_loss) + rmse_v = l2_virial_loss.sqrt() * atom_norm + more_loss["rmse_v"] = self.display_if_exist(rmse_v.detach(), find_virial) + if mae: + mae_v = torch.mean(torch.abs(diff_v)) * atom_norm + more_loss["mae_v"] = self.display_if_exist(mae_v.detach(), find_virial) + + if self.has_h and "hessian" in model_pred and "hessian" in label: # anchor added; sth to be corrected + # print("{if self.has_h and 'hessian' in model_pred and 'hessian' in label} is valid") + find_hessian = label.get("find_hessian", 0.0) + pref_h = pref_h * find_hessian + # print(f"label['hessian']: {type(label['hessian'])}, size from {label['hessian'].size()} " + # f"to {label['hessian'].reshape(-1,).size()}") # anchor added + # print(f"model_pred['hessian']: {type(model_pred['hessian'])}, size from {model_pred['hessian'].size()}" + # f"to {model_pred['hessian'].reshape(-1,).size()}") # anchor added + # print(f"label_hess has nan of {np.sum(np.isnan(label['hessian'].reshape(-1,).cpu().detach().numpy()))}") + # print(f"model_pred_hess has nan of {np.sum(np.isnan(model_pred['hessian'].reshape(-1,).cpu().detach().numpy()))}") + diff_h = label["hessian"].reshape(-1,) - model_pred["hessian"].reshape(-1,) # tbd + # print(f"diff_h: {diff_h.size()}") + # print(f"diff_h has nan of {np.sum(np.isnan(diff_h.cpu().detach().numpy()))}") + l2_hessian_loss = torch.mean(torch.square(diff_h)) + # print(f"l2_hessian_loss: {l2_hessian_loss:.8f}") + if not self.inference: + more_loss["l2_hessian_loss"] = self.display_if_exist( + l2_hessian_loss.detach(), find_hessian + ) + loss += (pref_h * l2_hessian_loss) # tbd + rmse_h = l2_hessian_loss.sqrt() # tbd + # print(f"rmse_h: {rmse_h:.8f}") + more_loss["rmse_h"] = self.display_if_exist(rmse_h.detach(), find_hessian) + if mae: + mae_h = torch.mean(torch.abs(diff_h)) # tbd + more_loss["mae_h"] = self.display_if_exist(mae_h.detach(), find_hessian) + + if self.has_ae and "atom_energy" in model_pred and "atom_ener" in label: + atom_ener = model_pred["atom_energy"] + atom_ener_label = label["atom_ener"] + find_atom_ener = label.get("find_atom_ener", 0.0) + pref_ae = pref_ae * find_atom_ener + atom_ener_reshape = atom_ener.reshape(-1) + atom_ener_label_reshape = atom_ener_label.reshape(-1) + l2_atom_ener_loss = torch.square( + atom_ener_label_reshape - atom_ener_reshape + ).mean() + if not self.inference: + more_loss["l2_atom_ener_loss"] = self.display_if_exist( + l2_atom_ener_loss.detach(), find_atom_ener + ) + loss += (pref_ae * l2_atom_ener_loss).to(GLOBAL_PT_FLOAT_PRECISION) + rmse_ae = l2_atom_ener_loss.sqrt() + more_loss["rmse_ae"] = self.display_if_exist( + rmse_ae.detach(), find_atom_ener + ) + + if not self.inference: + more_loss["rmse"] = torch.sqrt(loss.detach()) + return model_pred, loss, more_loss + + @property + def label_requirement(self) -> List[DataRequirementItem]: + """Return data label requirements needed for this loss calculation.""" + label_requirement = [] + if self.has_e: + label_requirement.append( + DataRequirementItem( + "energy", + ndof=1, + atomic=False, + must=False, + high_prec=True, + ) + ) + if self.has_f: + label_requirement.append( + DataRequirementItem( + "force", + ndof=3, + atomic=True, + must=False, + high_prec=False, + ) + ) + if self.has_v: + label_requirement.append( + DataRequirementItem( + "virial", + ndof=9, + atomic=False, + must=False, + high_prec=False, + ) + ) + if self.has_h: # anchor created + label_requirement.append( + DataRequirementItem( + "hessian", + ndof=1, # 9=3*3 --> 3N*3N=ndof*natoms*natoms + atomic=True, + must=False, + high_prec=False, + ) + ) + if self.has_ae: + label_requirement.append( + DataRequirementItem( + "atom_ener", + ndof=1, + atomic=True, + must=False, + high_prec=False, + ) + ) + if self.has_pf: + label_requirement.append( + DataRequirementItem( + "atom_pref", + ndof=1, + atomic=True, + must=False, + high_prec=False, + repeat=3, + ) + ) + if self.has_gf > 0: + label_requirement.append( + DataRequirementItem( + "drdq", + ndof=self.numb_generalized_coord * 3, + atomic=True, + must=False, + high_prec=False, + ) + ) + if self.enable_atom_ener_coeff: + label_requirement.append( + DataRequirementItem( + "atom_ener_coeff", + ndof=1, + atomic=True, + must=False, + high_prec=False, + default=1.0, + ) + ) + return label_requirement + From afb0227121dedc45e0c40b6e128cddb9413bef79 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:31:02 +0800 Subject: [PATCH 006/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/deepmd/pt/loss/__init__.py b/deepmd/pt/loss/__init__.py index e64a129d51..dce279a33e 100644 --- a/deepmd/pt/loss/__init__.py +++ b/deepmd/pt/loss/__init__.py @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: LGPL-3.0-or-later +# PDX-License-Identifier: LGPL-3.0-or-later from .denoise import ( DenoiseLoss, ) @@ -8,6 +8,9 @@ from .ener import ( EnergyStdLoss, ) +from .ener_hess import( + EnergyHessianStdLoss, +) # anchor added from .ener_spin import ( EnergySpinLoss, ) @@ -21,8 +24,10 @@ __all__ = [ "DenoiseLoss", "EnergyStdLoss", + "EnergyHessianStdLoss", # anchor added "EnergySpinLoss", "TensorLoss", "TaskLoss", "DOSLoss", ] + From 57426748e9833f4640bde24e6814cbc1e3c06360 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:31:44 +0800 Subject: [PATCH 007/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/__init__.py | 42 +++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/deepmd/pt/model/model/__init__.py b/deepmd/pt/model/model/__init__.py index 0cf2e7c5f1..cb85d6f779 100644 --- a/deepmd/pt/model/model/__init__.py +++ b/deepmd/pt/model/model/__init__.py @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: LGPL-3.0-or-later +# PDX-License-Identifier: LGPL-3.0-or-later """The model that takes the coordinates, cell and atom types as input and predicts some property. The models are automatically generated from atomic models by the `deepmd.dpmodel.make_model` method. @@ -44,13 +44,17 @@ ) from .ener_model import ( EnergyModel, + # EnergyHessianModel, # anchor added +) +from .ener_hess_model import ( + EnergyHessianModel, # anchor added ) from .frozen import ( FrozenModel, ) from .make_hessian_model import ( make_hessian_model, -) +) # anchor added from .make_model import ( make_model, ) @@ -71,7 +75,7 @@ def get_spin_model(model_params): if not model_params["spin"]["use_spin"] or isinstance( model_params["spin"]["use_spin"][0], int ): - use_spin = np.full(len(model_params["type_map"]), False) # pylint: disable=no-explicit-dtype + use_spin = np.full(len(model_params["type_map"]), False) use_spin[model_params["spin"]["use_spin"]] = True model_params["spin"]["use_spin"] = use_spin.tolist() # include virtual spin and placeholder types @@ -156,6 +160,10 @@ def get_standard_model(model_params): model_params["descriptor"]["ntypes"] = ntypes model_params["descriptor"]["type_map"] = copy.deepcopy(model_params["type_map"]) descriptor = BaseDescriptor(**model_params["descriptor"]) + # print(f"m_p[ds] in get_standard_model: {model_params['descriptor']}") # anchor added + # print(f"len of ds.sl()[ebd][ntw] in get_standard_model: {len(descriptor.serialize()['embeddings']['networks'])}") # anchor added + # print(f"type of descriptor in get_standard_model: {type(descriptor)}") # anchor added + # print(f"descriptor in get_standard_model: {descriptor}") # anchor added # fitting fitting_net = model_params.get("fitting_net", {}) fitting_net["type"] = fitting_net.get("type", "ener") @@ -182,6 +190,13 @@ def get_standard_model(model_params): modelcls = DOSModel elif fitting_net["type"] in ["ener", "direct_force_ener"]: modelcls = EnergyModel + # print("model type is EnergyModel") # anchor added + if model_params["hessian_mode"]: # anchor added + modelcls = EnergyHessianModel + # print("model type is EnergyHessianModel in ener type") + # elif fitting_net["type"] == "ener_hess": # anchor created + # modelcls = EnergyHessianModel + # print("model type is EnergyHessianModel") else: raise RuntimeError(f"Unknown fitting type: {fitting_net['type']}") @@ -192,21 +207,20 @@ def get_standard_model(model_params): atom_exclude_types=atom_exclude_types, pair_exclude_types=pair_exclude_types, ) + # print(f"descriptor type in __init__: {type(descriptor)}") # anchor added + # print(f"fitting type in __init__: {type(fitting)}") # anchor added + # print(f"fitting content in __init__: {fitting}") # anchor added model.model_def_script = json.dumps(model_params_old) return model def get_model(model_params): - model_type = model_params.get("type", "standard") - if model_type == "standard": - if "spin" in model_params: - return get_spin_model(model_params) - elif "use_srtab" in model_params: - return get_zbl_model(model_params) - else: - return get_standard_model(model_params) + if "spin" in model_params: + return get_spin_model(model_params) + elif "use_srtab" in model_params: + return get_zbl_model(model_params) else: - return BaseModel.get_class_by_type(model_type).get_model(model_params) + return get_standard_model(model_params) __all__ = [ @@ -214,6 +228,7 @@ def get_model(model_params): "get_model", "DPModelCommon", "EnergyModel", + "EnergyHessianModel", # anchor added "DipoleModel", "PolarModel", "DOSModel", @@ -222,5 +237,6 @@ def get_model(model_params): "SpinEnergyModel", "DPZBLModel", "make_model", - "make_hessian_model", + "make_hessian_model", # anchor added ] + From 75f4dce2c57f31fc13fc6d1a5b4ec75ab3bc986f Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:32:05 +0800 Subject: [PATCH 008/189] Update make_hessian_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/make_hessian_model.py | 78 +++++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/deepmd/pt/model/model/make_hessian_model.py b/deepmd/pt/model/model/make_hessian_model.py index 9588348f53..163978757b 100644 --- a/deepmd/pt/model/model/make_hessian_model.py +++ b/deepmd/pt/model/model/make_hessian_model.py @@ -15,6 +15,29 @@ ) +def compute_hessian(func, inputs): # anchor created + # print(f"func in compute_hessian of {type(func)}: {func}") + # print(f"inputs in compute_hessian of {type(inputs)}: {inputs}") + device = torch.device('cuda:0') + inputs.to(device) + inputs = inputs.requires_grad_(True) + y = func(inputs) + grads = torch.autograd.grad(y, inputs, create_graph=True)[0] + # for j in range(len(inputs)): + # grad_j = torch.autograd.grad(y, inputs[j], retain_graph=True, allow_unused=True)[0] + # print(f"{j} grad: {grad_j}") + n = len(inputs) + hessian = torch.zeros(n, n, device=device) + for i in range(n): + grad2 = torch.autograd.grad(grads[i], inputs, retain_graph=True)[0] + # print(f"{i} grad2: {grad2}") + # for j in range(len(inputs)): + # grad_ij = torch.autograd.grad(grads[i], inputs[j], retain_graph=True, allow_unused=True)[0] + # print(f"{i}{j} grad2: {grad_ij}") + hessian[i] = grad2 + return hessian + + def make_hessian_model(T_Model): """Make a model that can compute Hessian. @@ -129,6 +152,8 @@ def _cal_hessian_all( 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.atomic_output_def() + # print(f"fdef in _cal_hessian_all: {fdef}") # anchor added + # print(f"keys of fdef in _cal_hessian_all: {fdef.keys()}") # anchor added ['energy', 'mask'] # keys of values that require hessian hess_keys: List[str] = [] for kk in fdef.keys(): @@ -141,6 +166,7 @@ def _cal_hessian_all( vdef = fdef[kk] vshape = vdef.shape vsize = math.prod(vdef.shape) + # print(f"vdef is {vdef}, vshape is {vshape}, vsize is {vsize}") # anchor added # loop over frames for ii in range(nf): icoord = coord[ii] @@ -173,16 +199,49 @@ def _cal_hessian_one_component( # box: Optional[torch.Tensor] = None, # 9 # fparam: Optional[torch.Tensor] = None, # nfp # aparam: Optional[torch.Tensor] = None, # (nloc x nap) + # print(f"coord in _cal_hessian_one_component: {coord}") # anchor added + # print(f"atype in _cal_hessian_one_component: {atype}") # anchor added + # print(f"box in _cal_hessian_one_component: {box}") # anchor added wc = wrapper_class_forward_energy(self, ci, atype, box, fparam, aparam) - hess = torch.autograd.functional.hessian( - wc, - coord, - create_graph=False, - ) + # def energy_func(x): # anchor trying: success + # return (x ** 2).sum() + # def energy_func(x): # anchor trying: success + # return (2*x.pow(2)+3*x.pow(2)+x.pow(3)).sum() + # def energy_func(x): # anchor trying: success + # return ((x[0].pow(3))+5*(x[1].pow(2))+2*(x[0].pow(2))*(x[2].pow(5))) + # wc = energy_func + # print(f"wrapper_class in _cal_hessian_one_component: {type(wc)}") # anchor added + # print(f"wc in _cal_hessian_one_component: {wc}") # anchor added + + # hess = torch.autograd.functional.hessian( + # wc, + # coord, + # create_graph=False, + # # create_graph=True, # anchor changed to: FloatingPointError: gradients are Nan/Inf + # ) + # jacobian = torch.autograd.functional.jacobian( # anchor trying + # wc, + # coord, + # create_graph=True, + # ) + # print(f"jacobian in _cal_hessian_one_component: {jacobian}") # anchor added + # jacobian.backward(torch.ones_like(coord)) + # tmp = torch.autograd.grad(jacobian[0], coord[0], create_graph=False, allow_unused=True) # anchor added + # er_from_wc = wc.__call__(coord) # anchor added + # print(f"er_from_wc in _cal_hessian_one_component: {er_from_wc}") # anchor added + # coord = torch.tensor(coord, requires_grad=True) # anchor trying + # tmp = torch.autograd.grad(er_from_wc, coord[3], create_graph=False, allow_unused=True) # anchor added + # print(f"tmp in _cal_hessian_one_component: {tmp}") # anchor added + # coord = torch.tensor(coord, requires_grad=True) # anchor trying + # print(f"len of coord is {len(coord)}") # anchor + hess = compute_hessian(wc, coord) # anchor trying: identical to t.ag.f.hessian + # hess = torch.func.hessian(wc)(coord) # anchor tried + # print(f"hessian in _cal_hessian_one_component: {hess}") # anchor added return hess class wrapper_class_forward_energy: + # torch.autograd.set_detect_anomaly(True) # anchor added def __init__( self, obj: CM, @@ -211,6 +270,15 @@ def __call__( do_atomic_virial=False, ) er = res["energy_redu"][0].view([-1])[ci] + # def energy_func(x): # anchor trying: success + # return (x ** 2).sum() + # res = energy_func(xx) # anchor added + # er = res # anchor added + # print(f"obj in wrapper_class_forward_energy: {self.obj}") # anchor added + # print(f"res in wrapper_class_forward_energy: {res}") # anchor added + # print(f"er in wrapper_class_forward_energy: {er}") # anchor added + # print(f"er grad_fn in wrapper_class_forward_energy: {er.grad_fn}") # anchor added return er return CM + From 0165eba1217007e1247a44c3c244e95c5fc4c56d Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:32:32 +0800 Subject: [PATCH 009/189] Create ener_hess_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/ener_hess_model.py | 182 +++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 deepmd/pt/model/model/ener_hess_model.py diff --git a/deepmd/pt/model/model/ener_hess_model.py b/deepmd/pt/model/model/ener_hess_model.py new file mode 100644 index 0000000000..210ac9b3f4 --- /dev/null +++ b/deepmd/pt/model/model/ener_hess_model.py @@ -0,0 +1,182 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later +from copy import ( + deepcopy, +) +from typing import ( + Dict, + Optional, +) + +import torch + +from deepmd.pt.model.atomic_model import ( + DPEnergyAtomicModel, +) +from deepmd.pt.model.model.model import ( + BaseModel, +) +from .dp_model import ( + DPModelCommon, +) +from .make_model import ( + make_model, +) +from .make_hessian_model import ( + make_hessian_model, +) # anchor added +import numpy as np # anchor added + + +DPEnergyModel_ = make_model(DPEnergyAtomicModel) +DPEnergyModel_ = make_hessian_model(DPEnergyModel_) # anchor added + + +@BaseModel.register("ener_hess") +class EnergyHessianModel(DPModelCommon, DPEnergyModel_): # anchor created + model_type = "ener_hess" + + def __init__( + self, + *args, + **kwargs, + ): + DPModelCommon.__init__(self) + DPEnergyModel_.__init__(self, *args, **kwargs) + + def translated_output_def(self): + out_def_data = self.model_output_def().get_data() + output_def = { + "atom_energy": deepcopy(out_def_data["energy"]), + "energy": deepcopy(out_def_data["energy_redu"]), + } + if self.do_grad_r("energy"): + output_def["force"] = deepcopy(out_def_data["energy_derv_r"]) + output_def["force"].squeeze(-2) + # print(f"output_def['force'] in translated_output_def: {output_def['force']}") # anchor added + if self.do_grad_c("energy"): + output_def["virial"] = deepcopy(out_def_data["energy_derv_c_redu"]) + output_def["virial"].squeeze(-2) + output_def["atom_virial"] = deepcopy(out_def_data["energy_derv_c"]) + output_def["atom_virial"].squeeze(-3) + output_def["hessian"] = deepcopy(out_def_data["energy_derv_r_derv_r"]) # anchor added + # output_def["hessian"].squeeze(-2) # anchor added + if "mask" in out_def_data: + output_def["mask"] = deepcopy(out_def_data["mask"]) + return output_def + + def forward( + self, + coord, + atype, + box: Optional[torch.Tensor] = None, + fparam: Optional[torch.Tensor] = None, + aparam: Optional[torch.Tensor] = None, + do_atomic_virial: bool = False, + ) -> Dict[str, torch.Tensor]: + self.requires_hessian("energy") # anchor added + model_ret = self.forward_common( + coord, + atype, + box, + fparam=fparam, + aparam=aparam, + do_atomic_virial=do_atomic_virial, + ) + if self.get_fitting_net() is not None: + model_predict = {} + model_predict["atom_energy"] = model_ret["energy"] + model_predict["energy"] = model_ret["energy_redu"] + # ener_tmp = model_predict["energy"] # anchor added + # print(f"energy of {ener_tmp.size()} and grad {ener_tmp.requires_grad} in forward: {ener_tmp}") # anchor added + # coord_flat = coord.view(-1) # anchor added + # coord_flat.requires_grad_(True) # anchor added + # print(f"coord of {coord_flat.size()} and grad {coord_flat.requires_grad} in forward: {coord_flat}") # anchor added + # grad_tmp = torch.autograd.grad(ener_tmp, coord_flat, create_graph=True)[0] # anchor added + # print(f"grad of {grad_tmp.size()} and grad {grad_tmp.requires_grad} in forward: {grad_tmp}") # anchor added + if self.do_grad_r("energy"): + model_predict["force"] = model_ret["energy_derv_r"].squeeze(-2) + # force_tmp = torch.reshape(model_predict["force"], (-1,)) # anchor added + # print(f"model_predict['force'] of {force_tmp.size()} and {force_tmp.requires_grad} in forward: {force_tmp}") # anchor added + # print(f"grad of force in forward: {model_predict['force'].grad}") + # n_hess_elem = force_tmp.size()[0] # anchor added + # hess_tmp = torch.zeros(n_hess_elem, n_hess_elem, device=force_tmp.device, dtype=force_tmp.dtype) # anchor added + # coord_tmp = torch.reshape(coord, (-1,)) # anchor added + # coord_tmp.requires_grad_(True) # anchor added + # print(f"coord of {coord_tmp.size()} and {coord_tmp.requires_grad} in forward: {coord_tmp}") # anchor added + # for i in range(n_hess_elem): # anchor added + # grad2 = torch.autograd.grad(force_tmp[i], coord_tmp, retain_graph=True, allow_unused=True)[0] + # # if grad2 is None: + # # grad2 = torch.zeros_like(coord_tmp) + # hess_tmp[i] = grad2 + # print(f"hess_tmp of {hess_tmp.size()} in EnergyHessianModel.forward: {hess_tmp}") # anchor added + # anchor: tensor.squeeze(dim) remove -dim if its 1, or keep dim ((2,1,3)-->(2,3), (1,2,3)-->(1,2,3)) + # if self.do_grad_r_hess("energy"): # tbd; anchor created + # model_predict["hessian"] = model_ret["kk"] # kk is m_h_m value + if self.do_grad_c("energy"): + model_predict["virial"] = model_ret["energy_derv_c_redu"].squeeze(-2) + if do_atomic_virial: + model_predict["atom_virial"] = model_ret["energy_derv_c"].squeeze( + -3 + ) + else: + model_predict["force"] = model_ret["dforce"] + if "mask" in model_ret: + model_predict["mask"] = model_ret["mask"] + model_predict["hessian"] = model_ret["energy_derv_r_derv_r"] # anchor added squeeze? + # print(f"model_predict['hessian'] before squeeze: size of {model_predict['hessian'].size()}, " + # f"nan of {np.sum(np.isnan(model_predict['hessian'].cpu().detach().numpy()))}") # anchor added + # if np.sum(np.isnan(model_predict['hessian'].cpu().detach().numpy())) == 135: # anchor added + # np.save("./hess_45x45.npy", model_predict['hessian'].cpu().detach().numpy()) + # print("hess_45x45.npy is saved") + # if np.sum(np.isnan(model_predict['hessian'].cpu().detach().numpy())) == 162: # anchor added + # np.save("./hess_54x54.npy", model_predict['hessian'].cpu().detach().numpy()) + # print("hess_54x54.npy is saved") + model_predict["hessian"].squeeze(-2) # anchor added: no need to squeeze + # print(f"model_predict['hessian'] after squeeze: size of {model_predict['hessian'].size()}, " + # f"nan of {np.sum(np.isnan(model_predict['hessian'].cpu().detach().numpy()))}") # anchor added + else: + model_predict = model_ret + model_predict["updated_coord"] += coord + return model_predict + + @torch.jit.export + def forward_lower( + self, + extended_coord, + extended_atype, + nlist, + mapping: Optional[torch.Tensor] = None, + fparam: Optional[torch.Tensor] = None, + aparam: Optional[torch.Tensor] = None, + do_atomic_virial: bool = False, + comm_dict: Optional[Dict[str, torch.Tensor]] = None, + ): + model_ret = self.forward_common_lower( + extended_coord, + extended_atype, + nlist, + mapping, + fparam=fparam, + aparam=aparam, + do_atomic_virial=do_atomic_virial, + comm_dict=comm_dict, + ) + if self.get_fitting_net() is not None: + model_predict = {} + model_predict["atom_energy"] = model_ret["energy"] + model_predict["energy"] = model_ret["energy_redu"] + if self.do_grad_r("energy"): + model_predict["extended_force"] = model_ret["energy_derv_r"].squeeze(-2) + if self.do_grad_c("energy"): + model_predict["virial"] = model_ret["energy_derv_c_redu"].squeeze(-2) + if do_atomic_virial: + model_predict["extended_virial"] = model_ret[ + "energy_derv_c" + ].squeeze(-3) + else: + assert model_ret["dforce"] is not None + model_predict["dforce"] = model_ret["dforce"] + else: + model_predict = model_ret + return model_predict + From e56057381ccdd6b9df256973f92e8c78cc165d8c Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Fri, 9 Aug 2024 15:01:07 +0800 Subject: [PATCH 010/189] Update make_hessian_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/make_hessian_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/pt/model/model/make_hessian_model.py b/deepmd/pt/model/model/make_hessian_model.py index 163978757b..eb6402cd59 100644 --- a/deepmd/pt/model/model/make_hessian_model.py +++ b/deepmd/pt/model/model/make_hessian_model.py @@ -29,7 +29,7 @@ def compute_hessian(func, inputs): # anchor created n = len(inputs) hessian = torch.zeros(n, n, device=device) for i in range(n): - grad2 = torch.autograd.grad(grads[i], inputs, retain_graph=True)[0] + grad2 = torch.autograd.grad(grads[i], inputs, retain_graph=True, create_graph=True)[0] # print(f"{i} grad2: {grad2}") # for j in range(len(inputs)): # grad_ij = torch.autograd.grad(grads[i], inputs[j], retain_graph=True, allow_unused=True)[0] From 53847319dcf5643a9cf8943f174c1b9d66b36168 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:08:30 +0800 Subject: [PATCH 011/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 82 +++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index 1d2248aa0d..d90ef14b08 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -44,6 +44,9 @@ from deepmd.utils.weight_avg import ( weighted_average, ) +from deepmd.pt.model.model import ( + get_model, +) # anchor added if TYPE_CHECKING: from deepmd.infer.deep_tensor import ( @@ -117,6 +120,7 @@ def test( dp_random.seed(rand_seed % (2**32)) # init model + print(f"--run dp=DeepEval in test.py") # anchor added dp = DeepEval(model, head=head) for cc, system in enumerate(all_sys): @@ -243,6 +247,13 @@ def save_txt_file( np.savetxt(fp, data, header=header) +def whether_hessian(dp: "DeepPot"): # anchor created + if "Hessian" in str(type(get_model(dp.deep_eval.input_param))): + return True + else: + return False + + def test_ener( dp: "DeepPot", data: DeepmdData, @@ -279,6 +290,19 @@ def test_ener( data.add("energy", 1, atomic=False, must=False, high_prec=True) data.add("force", 3, atomic=True, must=False, high_prec=False) data.add("virial", 9, atomic=False, must=False, high_prec=False) + # data.add("hessian", 1, atomic=True, must=False, high_prec=False) # anchor added ndof=1? + print(f"--type of dp in test_ener in test.py: {type(dp)}") # anchor added + try: # anchor added + print(f"--type of dp.model in test_ener in test.py: {type(dp.model)}") + try: + print(f"--type of dp.model.default in test_ener in test.py: {type(dp.model['Default'])}") + except: + print(f"--dp.model doesnt has key 'Default' in test_ener in test.py") + except: + print(f"--dp doesnt has attr model in test_ener in test.py") # anchor noted: dp.model is invalid + print(f"--type of data in test_ener in test.py: {type(data)}") # anchor added + print(f"--type of dp in test_ener in test.py: {type(dp)}, {dp.__dict__}, {dir(dp)}") # anchor added + print(f"--type of dp.deep_eval in test_ener in test.py: {type(dp.deep_eval)}, {dir(dp.deep_eval)}") # anchor added if dp.has_efield: data.add("efield", 3, atomic=True, must=True, high_prec=False) if has_atom_ener: @@ -292,8 +316,11 @@ def test_ener( if dp.has_spin: data.add("spin", 3, atomic=True, must=True, high_prec=False) data.add("force_mag", 3, atomic=True, must=False, high_prec=False) + if whether_hessian(dp): # anchor added + data.add("hessian", 1, atomic=True, must=True, high_prec=False) test_data = data.get_test() + print(f"--test data in test_ener in test.py of {type(test_data)}: {test_data.keys()}") # anchor added mixed_type = data.mixed_type natoms = len(test_data["type"][0]) nframes = test_data["box"].shape[0] @@ -335,12 +362,20 @@ def test_ener( mixed_type=mixed_type, spin=spin, ) + print(f"--ret of {type(ret)} in test_ener in test.py: {[r.shape for r in ret]}") # anchor added: hessian missed energy = ret[0] force = ret[1] virial = ret[2] energy = energy.reshape([numb_test, 1]) force = force.reshape([numb_test, -1]) + print(f"force in test_ener in test.py of {type(force)}") # anchor added + print(f"force shape in test_ener in test.py: {force.shape}") # anchor added: (1, 54) virial = virial.reshape([numb_test, 9]) + if whether_hessian(dp): # anchor added + hessian = ret[-1] # anchor added + print(f"hessian in test_ener in test.py of {type(hessian)}") # anchor added + print(f"hessian shape in test_ener in test.py: {hessian.shape}") # anchor added: (1, 54, 54) + hessian = hessian.reshape([numb_test, -1]) # anchor added: (1, 2916) if has_atom_ener: ae = ret[3] av = ret[4] @@ -404,6 +439,10 @@ def test_ener( rmse_ea = rmse_e / natoms mae_va = mae_v / natoms rmse_va = rmse_v / natoms + if whether_hessian(dp): + diff_h = hessian - test_data["hessian"][:numb_test] # anchor added + mae_h = mae(diff_h) # anchor added + rmse_h = rmse(diff_h) # anchor added if has_atom_ener: diff_ae = test_data["atom_ener"][:numb_test].reshape([-1]) - ae.reshape([-1]) mae_ae = mae(diff_ae) @@ -436,6 +475,9 @@ def test_ener( if has_atom_ener: log.info(f"Atomic ener MAE : {mae_ae:e} eV") log.info(f"Atomic ener RMSE : {rmse_ae:e} eV") + if whether_hessian(dp): + log.info(f"Hessian MAE : {mae_h:e} eV/A^2") # anchor added: if EHM + log.info(f"Hessian RMSE : {rmse_h:e} eV/A^2") # anchor added if detail_file is not None: detail_path = Path(detail_file) @@ -519,8 +561,36 @@ def test_ener( "pred_vyy pred_vyz pred_vzx pred_vzy pred_vzz", append=append_detail, ) + if whether_hessian(dp): + print(f"test_data hessian shape: {test_data['hessian'][:numb_test].shape}") + print(f"hessian shape before reshape: {hessian.shape}") + print(f"hessian shape reshape to [-1, 1]: {hessian.reshape(-1, 1).shape}") + _n_frames_, _n_hessian_ = test_data['hessian'][:numb_test].shape + _n_atoms_ = np.int32(np.sqrt(_n_hessian_) / 3) # n_hessian = 3na*3na + triu_indices = np.triu_indices(_n_atoms_ * 3) # upper triangle hessian indices + data_h = test_data["hessian"][:numb_test].reshape(-1, _n_atoms_ * _n_atoms_ * 9) + pred_h = hessian.reshape(-1, _n_atoms_ * _n_atoms_ * 9) + data_h_triu = test_data["hessian"][:numb_test][:, triu_indices[0] * _n_atoms_ * 3 + triu_indices[1]].reshape(-1, 1) + pred_h_triu = hessian[:, triu_indices[0] * _n_atoms_ * 3 + triu_indices[1]].reshape(-1, 1) + print(f"data_h_triu shape: {data_h_triu.shape}") + print(f"pred_h_triu shape: {pred_h_triu.shape}") + h = np.concatenate( + ( + # np.reshape(test_data["hessian"][:numb_test], [-1, 1]), + # np.reshape(hessian, [-1, 1]), + data_h_triu, + pred_h_triu, + ), + axis=1, + ) + save_txt_file( + detail_path.with_suffix(".h.out"), + h, + header=f"{system}: data_h pred_h", + append=append_detail, + ) if not out_put_spin: - return { + dict_to_return = { "mae_e": (mae_e, energy.size), "mae_ea": (mae_ea, energy.size), "mae_f": (mae_f, force.size), @@ -533,7 +603,7 @@ def test_ener( "rmse_va": (rmse_va, virial.size), } else: - return { + dict_to_return = { "mae_e": (mae_e, energy.size), "mae_ea": (mae_ea, energy.size), "mae_fr": (mae_fr, force_r.size), @@ -547,6 +617,10 @@ def test_ener( "rmse_v": (rmse_v, virial.size), "rmse_va": (rmse_va, virial.size), } + if whether_hessian(dp): # anchor added + dict_to_return["mae_h"] = (mae_h, hessian.size) + dict_to_return["rmse_h"] = (rmse_h, hessian.size) + return dict_to_return def print_ener_sys_avg(avg: Dict[str, float]): @@ -573,6 +647,9 @@ def print_ener_sys_avg(avg: Dict[str, float]): log.info(f"Virial RMSE : {avg['rmse_v']:e} eV") log.info(f"Virial MAE/Natoms : {avg['mae_va']:e} eV") log.info(f"Virial RMSE/Natoms : {avg['rmse_va']:e} eV") + if "rmse_h" in avg.keys(): # anchor added + log.info(f"Hessian MAE : {avg['mae_h']:e} eV/A^2") # anchor added + log.info(f"Hessian RMSE : {avg['rmse_h']:e} eV/A^2") # anchor added def test_dos( @@ -1084,3 +1161,4 @@ def print_dipole_sys_avg(avg): array with summaries """ log.info(f"Dipole RMSE : {avg['rmse']:e} eV/A") + From ab73bdea60ecfec251baaa4e00deb8edb96b622e Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:08:58 +0800 Subject: [PATCH 012/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/infer/deep_eval.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/deepmd/infer/deep_eval.py b/deepmd/infer/deep_eval.py index 879455b942..c740f4d615 100644 --- a/deepmd/infer/deep_eval.py +++ b/deepmd/infer/deep_eval.py @@ -79,6 +79,7 @@ class DeepEvalBackend(ABC): # old models in v1 "global_polar": "global_polar", "wfc": "wfc", + "energy_derv_r_derv_r": "hessian", # anchor added } @abstractmethod @@ -313,7 +314,10 @@ def __new__(cls, model_file: str, *args, **kwargs): ModelOutputDef(FittingOutputDef([])), *args, **kwargs, - ) + ) # anchor noted: class DeepEval in pt/deep_eval.py is activated + print(f"--DeepEval __new__ in deep_eval.py is activated") # anchor added + print(f"--output_def in new in deep_eval.py of {type(ModelOutputDef(FittingOutputDef([])))}: " + f"{ModelOutputDef(FittingOutputDef([])).var_defs.keys()}") # anchor added: ['mask'] return super().__new__(deep_eval.model_type) return super().__new__(cls) @@ -333,6 +337,9 @@ def __init__( neighbor_list=neighbor_list, **kwargs, ) + print(f"--DeepEval __init__ in deep_eval.py is activated") # anchor added + # print(f"--output_def in __init__ in deep_eval.py of {type(self.output_def)}: " + # f"{self.output_def.var_defs.keys()}") # anchor added: no hessian yet if self.deep_eval.get_has_spin() and hasattr(self, "output_def_mag"): self.deep_eval.output_def = self.output_def_mag From 1befb6181034a0a2fb1f0c27782f8ad414aa4f28 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:09:48 +0800 Subject: [PATCH 013/189] Update deep_pot.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/infer/deep_pot.py | 64 ++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/deepmd/infer/deep_pot.py b/deepmd/infer/deep_pot.py index 0632fd1c84..ef06e801e0 100644 --- a/deepmd/infer/deep_pot.py +++ b/deepmd/infer/deep_pot.py @@ -20,9 +20,15 @@ from .deep_eval import ( DeepEval, ) +from deepmd.pt.model.model import ( + get_model, +) # anchor added class DeepPot(DeepEval): + print(f"--class DeepPot is called") # anchor added: firstly + def __init__(self, *args, **kwargs): # anchor added + super().__init__(*args, **kwargs) """Potential energy model. Parameters @@ -56,20 +62,43 @@ class DeepPot(DeepEval): @property def output_def(self) -> ModelOutputDef: """Get the output definition of this model.""" - return ModelOutputDef( - FittingOutputDef( - [ - OutputVariableDef( - "energy", - shape=[1], - reducible=True, - r_differentiable=True, - c_differentiable=True, - atomic=True, - ), - ] + print(f"--self of DeepPot in deep_pot.py of {type(self)}: {self.__dict__.keys()}, {dir(self)}") # anchor added + # print(f"--self.deep_eval type in output_def in deep_pot.py: {type(self.deep_eval)}") # anchor added + if "deep_eval" in list(self.__dict__.keys()): # anchor added + model_type = type(get_model(self.deep_eval.input_param)) + if "Hessian" in str(model_type): + print(f"--model_type in output_def in deep_pot.py: {model_type}") + return ModelOutputDef( + FittingOutputDef( + [ + OutputVariableDef( + "energy", + shape=[1], + reducible=True, + r_differentiable=True, + c_differentiable=True, + atomic=True, + r_hessian=True, # anchor added: if type of model is EHM + ), + ] + ) + ) + else: + return ModelOutputDef( + FittingOutputDef( + [ + OutputVariableDef( + "energy", + shape=[1], + reducible=True, + r_differentiable=True, + c_differentiable=True, + atomic=True, + r_hessian=True, # anchor added: if type of model is EHM + ), + ] + ) ) - ) @property def output_def_mag(self) -> ModelOutputDef: @@ -207,10 +236,13 @@ def eval( aparam=aparam, **kwargs, ) + print(f"--results.keys in eval in deep_pot.py of {type(results)}: {results.keys()}") # anchor added: no hessian yet + print(f"--results.values in eval in deep_pot.py: {[type(v) for v in results.values()]}") # anchor added: ndarray energy = results["energy_redu"].reshape(nframes, 1) force = results["energy_derv_r"].reshape(nframes, natoms, 3) virial = results["energy_derv_c_redu"].reshape(nframes, 9) + if atomic: if self.get_ntypes_spin() > 0: ntypes_real = self.get_ntypes() - self.get_ntypes_spin() @@ -230,17 +262,23 @@ def eval( virial, atomic_energy, atomic_virial, + # hessian, # anchor added ) else: result = ( energy, force, virial, + # hessian, # anchor added ) if self.deep_eval.get_has_spin(): force_mag = results["energy_derv_r_mag"].reshape(nframes, natoms, 3) mask_mag = results["mask_mag"].reshape(nframes, natoms, 1) result = (*list(result), force_mag, mask_mag) + if "energy_derv_r_derv_r" in list(results.keys()): # anchor added + hessian = results["energy_derv_r_derv_r"].reshape(nframes, 3 * natoms, 3 * natoms) + result += (hessian, ) + print(f"--hessian is added to the tail of result in eval in deep_pot.py") return result From 9ae7163bf5859831159b7e5c95c4742b0e28614a Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:10:16 +0800 Subject: [PATCH 014/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 76 +++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index 55c18d5d95..3aca52acb9 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -87,7 +87,7 @@ class DeepEval(DeepEvalBackend): def __init__( self, model_file: str, - output_def: ModelOutputDef, + output_def: ModelOutputDef, # anchor noted: not callable *args: Any, auto_batch_size: Union[bool, int, AutoBatchSize] = True, neighbor_list: Optional["ase.neighborlist.NewPrimitiveNeighborList"] = None, @@ -95,12 +95,30 @@ def __init__( **kwargs: Any, ): self.output_def = output_def + # print(f"--output_def.keys in __init__ in pt/deep_eval.py: {self.output_def.var_defs.keys()}") # anchor added + # if "energy" in list(self.output_def.var_defs.keys()): # anchor added + # self.output_def.var_defs["energy"].r_hessian = True # if model type is EHM: useless + # print(f"--output_def.var_defs['energy'].r_hessian is set to True in __init__ in pt/deep_eval.py") + # print(f"--output_def.keys in __init__ in pt/deep_eval.py with r_h True: {self.output_def.var_defs.keys()}") + # print(f"--output_def.values in __init__ in pt/deep_eval.py: " + # f"{type(list(self.output_def.var_defs.values())[0])}") # anchor added: OutputVariableDef + # print(f"--output_def.def_outp in __init__ in pt/deep_eval.py of {type(self.output_def.def_outp)}: " + # f"{self.output_def.def_outp}") # anchor added: FittingOutputDef + # print(f"--output_def.def_derv_r in __init__ in pt/deep_eval.py of {type(self.output_def.def_derv_r)}: " + # f"{self.output_def.def_derv_r}") # anchor added: dict: {energy_derv_r: OutputVariableDef} + # print(f"--output_def.def_hess_r in __init__ in pt/deep_eval.py of {type(self.output_def.def_hess_r)}: " + # f"{self.output_def.def_hess_r}") # anchor added: dict: {} self.model_path = model_file if str(self.model_path).endswith(".pt"): state_dict = torch.load(model_file, map_location=env.DEVICE) + # print(f"--state_dict of {type(state_dict)} in pt/deep_eval.py: {state_dict.keys()}") # anchor added if "model" in state_dict: state_dict = state_dict["model"] + # print(f"--state_dict['model'] of {type(state_dict)} in pt/deep_eval.py: {state_dict.keys()}") # anchor added self.input_param = state_dict["_extra_state"]["model_params"] + # print(f"--input param of {type(state_dict)} in pt/deep_eval.py: {self.input_param.keys()}") # anchor added + # print(f"--state_dict['_extra_state'] of {type(state_dict['_extra_state'])} in pt/deep_eval.py: " + # f"{state_dict['_extra_state'].keys()}") # anchor added self.multi_task = "model_dict" in self.input_param if self.multi_task: model_keys = list(self.input_param["model_dict"].keys()) @@ -119,9 +137,15 @@ def __init__( ] = state_dict[item].clone() state_dict = state_dict_head model = get_model(self.input_param).to(DEVICE) - model = torch.jit.script(model) + print(f"--type of model in __init__ in pt/deep_eval: {type(model)}") # anchor added -- EHM + # model = torch.jit.script(model) # anchor commented out + # print(f"--type of model in pt/infer/deep_eval after torch.jit: {type(model)}") # anchor added self.dp = ModelWrapper(model) + # print(f"--dp in pt/infer/deep_eval of {type(self.dp)}: {self.dp}") # anchor added self.dp.load_state_dict(state_dict) + # print(f"--dp in pt/infer/deep_eval after load of {type(self.dp)}: {self.dp}") # anchor added + print(f"--dp.model in pt/deep_eval.py of {type(self.dp.model)}: {self.dp.model.keys()}") # anchor added + print(f"--dp.model.default in pt/deep_eval.py of {type(self.dp.model['Default'])}") # anchor added -- EHM elif str(self.model_path).endswith(".pth"): model = torch.jit.load(model_file, map_location=env.DEVICE) self.dp = ModelWrapper(model) @@ -143,6 +167,7 @@ def __init__( self._has_spin = getattr(self.dp.model["Default"], "has_spin", False) if callable(self._has_spin): self._has_spin = self._has_spin() + print(f"--class DeepEval in pt/deep_eval.py is activated") # anchor added def get_rcut(self) -> float: """Get the cutoff radius of this model.""" @@ -168,7 +193,10 @@ def get_dim_aparam(self) -> int: def model_type(self) -> Type["DeepEvalWrapper"]: """The the evaluator of the model type.""" model_output_type = self.dp.model["Default"].model_output_type() + print(f"--model_output_type in model_type in pt/deep_eval.py of {type(model_output_type)}: " + f"{model_output_type} of {type(model_output_type[0])}") # anchor added -- list ['energy', 'mask'] str if "energy" in model_output_type: + print(f"--model_output_type of DeepPot is returned in model_type_in pt/deep_eval.py") # anchor added return DeepPot elif "dos" in model_output_type: return DeepDOS @@ -255,6 +283,7 @@ def eval( variables, and the values are the corresponding output arrays. """ # convert all of the input to numpy array + print(f"--eval in pt/deep_eval.py is activated") # anchor added atom_types = np.array(atom_types, dtype=np.int32) coords = np.array(coords) if cells is not None: @@ -263,7 +292,9 @@ def eval( coords, atom_types, len(atom_types.shape) > 1 ) request_defs = self._get_request_defs(atomic) + print(f"--request_defs via _get_request_defs in eval in pt/deep_eval.py: {len(request_defs)} {type(request_defs[0])}") # anchor added if "spin" not in kwargs or kwargs["spin"] is None: + print(f"--type of _eval_model in eval in pt/deep_eval.py: {type(self._eval_model)}") # anchor added -- out = self._eval_func(self._eval_model, numb_test, natoms)( coords, cells, atom_types, fparam, aparam, request_defs ) @@ -277,6 +308,10 @@ def eval( aparam, request_defs, ) + # anchor noted: out here are only of 5 contents without 'energy_derv_c_derv_c' + print(f"--request_defs in eval in pt/deep_eval.py: {[x.name for x in request_defs]}") # anchor added + print(f"--out in eval in pt/deep_eval.py: {[o.shape for o in out]}") # anchor added + # anchor noted: request_defs: ['energy', 'energy_redu', 'energy_derv_r', 'energy_derv_c_redu', 'mask'] return dict( zip( [x.name for x in request_defs], @@ -301,9 +336,15 @@ def _get_request_defs(self, atomic: bool) -> List[OutputVariableDef]: list[OutputVariableDef] The requested output definitions. """ + print(f"--output_def in _get_request_defs in pt/deep_eval.py: {self.output_def.var_defs.keys()}") # anchor added if atomic: + print(f"--atomic in _get_request_defs in pt/deep_eval.py is called") # anchor added return list(self.output_def.var_defs.values()) else: + print(f"--atomic in _get_request_defs in pt/deep_eval.py is not called") # anchor added + print(f"--self.output_def.var_defs.keys in _get_request_defs in pt/deep_eval.py: {self.output_def.var_defs.keys()}") # anchor added + print(f"--self.output_def.var_defs.value.category in _get_request_defs in pt/deep_eval.py: {[x.category for x in self.output_def.var_defs.values()]}") # anchor added + # print(f"--self.output_def.var_defs.values in _get_request_defs in pt/deep_eval.py: {self.output_def.var_defs.values()}") # anchor added return [ x for x in self.output_def.var_defs.values() @@ -313,6 +354,7 @@ def _get_request_defs(self, atomic: bool) -> List[OutputVariableDef]: OutputVariableCategory.REDU, OutputVariableCategory.DERV_R, OutputVariableCategory.DERV_C_REDU, + OutputVariableCategory.DERV_R_DERV_R, # anchor added ) ] @@ -342,6 +384,7 @@ def eval_func(*args, **kwargs): else: eval_func = inner_func + print(f"--type of eval_func in _eval_func in pt/deep_eval.py: {type(eval_func)}") # anchor added -- return eval_func def _get_natoms_and_nframes( @@ -416,19 +459,22 @@ def _eval_model( ) if isinstance(batch_output, tuple): batch_output = batch_output[0] + print(f"--batch_output in _eval_model in pt/deep_eval.py of {type(batch_output)}: " + f"{batch_output.keys()}") # anchor added: dict with hessian in results = [] + print(f"--request_defs in _eval_model in pt/deep_eval.py of {type(request_defs)}: {request_defs}") # anchor added for odef in request_defs: pt_name = self._OUTDEF_DP2BACKEND[odef.name] + # print(f"--pt_name in _eval_model in pt/deep_eval.py: {pt_name}") # anchor added if pt_name in batch_output: shape = self._get_output_shape(odef, nframes, natoms) + print(f"--pt {pt_name} in _eval_model in pt/deep_eval.py shape: {shape}, odef: {odef}") # anchor added out = batch_output[pt_name].reshape(shape).detach().cpu().numpy() results.append(out) else: shape = self._get_output_shape(odef, nframes, natoms) - results.append( - np.full(np.abs(shape), np.nan) # pylint: disable=no-explicit-dtype - ) # this is kinda hacky + results.append(np.full(np.abs(shape), np.nan)) # this is kinda hacky return tuple(results) def _eval_model_spin( @@ -504,9 +550,7 @@ def _eval_model_spin( results.append(out) else: shape = self._get_output_shape(odef, nframes, natoms) - results.append( - np.full(np.abs(shape), np.nan) # pylint: disable=no-explicit-dtype - ) # this is kinda hacky + results.append(np.full(np.abs(shape), np.nan)) # this is kinda hacky return tuple(results) def _get_output_shape(self, odef, nframes, natoms): @@ -527,6 +571,9 @@ def _get_output_shape(self, odef, nframes, natoms): # Something wrong here? # return [nframes, *shape, natoms, 1] return [nframes, natoms, *odef.shape, 1] + elif odef.category == OutputVariableCategory.DERV_R_DERV_R: # anchor added + return [nframes, 3 * natoms, 3 * natoms] + # return [nframes, *odef.shape, 3 * natoms, 3 * natoms] else: raise RuntimeError("unknown category") @@ -670,28 +717,28 @@ def eval_model( logits_out.append(batch_output["logits"]) if not return_tensor: energy_out = ( - np.concatenate(energy_out) if energy_out else np.zeros([nframes, 1]) # pylint: disable=no-explicit-dtype + np.concatenate(energy_out) if energy_out else np.zeros([nframes, 1]) ) atomic_energy_out = ( np.concatenate(atomic_energy_out) if atomic_energy_out - else np.zeros([nframes, natoms, 1]) # pylint: disable=no-explicit-dtype + else np.zeros([nframes, natoms, 1]) ) force_out = ( - np.concatenate(force_out) if force_out else np.zeros([nframes, natoms, 3]) # pylint: disable=no-explicit-dtype + np.concatenate(force_out) if force_out else np.zeros([nframes, natoms, 3]) ) force_mag_out = ( np.concatenate(force_mag_out) if force_mag_out - else np.zeros([nframes, natoms, 3]) # pylint: disable=no-explicit-dtype + else np.zeros([nframes, natoms, 3]) ) virial_out = ( - np.concatenate(virial_out) if virial_out else np.zeros([nframes, 3, 3]) # pylint: disable=no-explicit-dtype + np.concatenate(virial_out) if virial_out else np.zeros([nframes, 3, 3]) ) atomic_virial_out = ( np.concatenate(atomic_virial_out) if atomic_virial_out - else np.zeros([nframes, natoms, 3, 3]) # pylint: disable=no-explicit-dtype + else np.zeros([nframes, natoms, 3, 3]) ) updated_coord_out = ( np.concatenate(updated_coord_out) if updated_coord_out else None @@ -756,3 +803,4 @@ def eval_model( results_dict["atom_energy"] = atomic_energy_out results_dict["atom_virial"] = atomic_virial_out return results_dict + From 6dd723e2950ade6f58e133d1df71de53dca5a45c Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:10:45 +0800 Subject: [PATCH 015/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deepmd/pt/model/model/__init__.py b/deepmd/pt/model/model/__init__.py index cb85d6f779..443dcccc2a 100644 --- a/deepmd/pt/model/model/__init__.py +++ b/deepmd/pt/model/model/__init__.py @@ -191,8 +191,9 @@ def get_standard_model(model_params): elif fitting_net["type"] in ["ener", "direct_force_ener"]: modelcls = EnergyModel # print("model type is EnergyModel") # anchor added - if model_params["hessian_mode"]: # anchor added - modelcls = EnergyHessianModel + if "hessian_mode" in model_params.keys(): # anchor added + if model_params["hessian_mode"]: # anchor added + modelcls = EnergyHessianModel # print("model type is EnergyHessianModel in ener type") # elif fitting_net["type"] == "ener_hess": # anchor created # modelcls = EnergyHessianModel From 4f078b8ba1d013b87e6735c5a2d3deed6272f212 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:29:06 +0800 Subject: [PATCH 016/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index d90ef14b08..ac93d7c1e9 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -120,7 +120,6 @@ def test( dp_random.seed(rand_seed % (2**32)) # init model - print(f"--run dp=DeepEval in test.py") # anchor added dp = DeepEval(model, head=head) for cc, system in enumerate(all_sys): @@ -290,19 +289,6 @@ def test_ener( data.add("energy", 1, atomic=False, must=False, high_prec=True) data.add("force", 3, atomic=True, must=False, high_prec=False) data.add("virial", 9, atomic=False, must=False, high_prec=False) - # data.add("hessian", 1, atomic=True, must=False, high_prec=False) # anchor added ndof=1? - print(f"--type of dp in test_ener in test.py: {type(dp)}") # anchor added - try: # anchor added - print(f"--type of dp.model in test_ener in test.py: {type(dp.model)}") - try: - print(f"--type of dp.model.default in test_ener in test.py: {type(dp.model['Default'])}") - except: - print(f"--dp.model doesnt has key 'Default' in test_ener in test.py") - except: - print(f"--dp doesnt has attr model in test_ener in test.py") # anchor noted: dp.model is invalid - print(f"--type of data in test_ener in test.py: {type(data)}") # anchor added - print(f"--type of dp in test_ener in test.py: {type(dp)}, {dp.__dict__}, {dir(dp)}") # anchor added - print(f"--type of dp.deep_eval in test_ener in test.py: {type(dp.deep_eval)}, {dir(dp.deep_eval)}") # anchor added if dp.has_efield: data.add("efield", 3, atomic=True, must=True, high_prec=False) if has_atom_ener: @@ -320,7 +306,6 @@ def test_ener( data.add("hessian", 1, atomic=True, must=True, high_prec=False) test_data = data.get_test() - print(f"--test data in test_ener in test.py of {type(test_data)}: {test_data.keys()}") # anchor added mixed_type = data.mixed_type natoms = len(test_data["type"][0]) nframes = test_data["box"].shape[0] @@ -362,20 +347,15 @@ def test_ener( mixed_type=mixed_type, spin=spin, ) - print(f"--ret of {type(ret)} in test_ener in test.py: {[r.shape for r in ret]}") # anchor added: hessian missed energy = ret[0] force = ret[1] virial = ret[2] energy = energy.reshape([numb_test, 1]) force = force.reshape([numb_test, -1]) - print(f"force in test_ener in test.py of {type(force)}") # anchor added - print(f"force shape in test_ener in test.py: {force.shape}") # anchor added: (1, 54) virial = virial.reshape([numb_test, 9]) if whether_hessian(dp): # anchor added - hessian = ret[-1] # anchor added - print(f"hessian in test_ener in test.py of {type(hessian)}") # anchor added - print(f"hessian shape in test_ener in test.py: {hessian.shape}") # anchor added: (1, 54, 54) - hessian = hessian.reshape([numb_test, -1]) # anchor added: (1, 2916) + hessian = ret[-1] + hessian = hessian.reshape([numb_test, -1]) if has_atom_ener: ae = ret[3] av = ret[4] @@ -562,9 +542,6 @@ def test_ener( append=append_detail, ) if whether_hessian(dp): - print(f"test_data hessian shape: {test_data['hessian'][:numb_test].shape}") - print(f"hessian shape before reshape: {hessian.shape}") - print(f"hessian shape reshape to [-1, 1]: {hessian.reshape(-1, 1).shape}") _n_frames_, _n_hessian_ = test_data['hessian'][:numb_test].shape _n_atoms_ = np.int32(np.sqrt(_n_hessian_) / 3) # n_hessian = 3na*3na triu_indices = np.triu_indices(_n_atoms_ * 3) # upper triangle hessian indices @@ -572,8 +549,6 @@ def test_ener( pred_h = hessian.reshape(-1, _n_atoms_ * _n_atoms_ * 9) data_h_triu = test_data["hessian"][:numb_test][:, triu_indices[0] * _n_atoms_ * 3 + triu_indices[1]].reshape(-1, 1) pred_h_triu = hessian[:, triu_indices[0] * _n_atoms_ * 3 + triu_indices[1]].reshape(-1, 1) - print(f"data_h_triu shape: {data_h_triu.shape}") - print(f"pred_h_triu shape: {pred_h_triu.shape}") h = np.concatenate( ( # np.reshape(test_data["hessian"][:numb_test], [-1, 1]), From bedb6ec24fae115316fcb72a054263e8687a2a1c Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:29:56 +0800 Subject: [PATCH 017/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/infer/deep_eval.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/deepmd/infer/deep_eval.py b/deepmd/infer/deep_eval.py index c740f4d615..aeb40ced4d 100644 --- a/deepmd/infer/deep_eval.py +++ b/deepmd/infer/deep_eval.py @@ -314,10 +314,7 @@ def __new__(cls, model_file: str, *args, **kwargs): ModelOutputDef(FittingOutputDef([])), *args, **kwargs, - ) # anchor noted: class DeepEval in pt/deep_eval.py is activated - print(f"--DeepEval __new__ in deep_eval.py is activated") # anchor added - print(f"--output_def in new in deep_eval.py of {type(ModelOutputDef(FittingOutputDef([])))}: " - f"{ModelOutputDef(FittingOutputDef([])).var_defs.keys()}") # anchor added: ['mask'] + ) return super().__new__(deep_eval.model_type) return super().__new__(cls) @@ -337,9 +334,6 @@ def __init__( neighbor_list=neighbor_list, **kwargs, ) - print(f"--DeepEval __init__ in deep_eval.py is activated") # anchor added - # print(f"--output_def in __init__ in deep_eval.py of {type(self.output_def)}: " - # f"{self.output_def.var_defs.keys()}") # anchor added: no hessian yet if self.deep_eval.get_has_spin() and hasattr(self, "output_def_mag"): self.deep_eval.output_def = self.output_def_mag From 484c62e35f359e8cdb088728f41ba8af0ce12790 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:31:44 +0800 Subject: [PATCH 018/189] Update deep_pot.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/infer/deep_pot.py | 54 +++++++++++----------------------------- 1 file changed, 14 insertions(+), 40 deletions(-) diff --git a/deepmd/infer/deep_pot.py b/deepmd/infer/deep_pot.py index ef06e801e0..61db284815 100644 --- a/deepmd/infer/deep_pot.py +++ b/deepmd/infer/deep_pot.py @@ -26,7 +26,6 @@ class DeepPot(DeepEval): - print(f"--class DeepPot is called") # anchor added: firstly def __init__(self, *args, **kwargs): # anchor added super().__init__(*args, **kwargs) """Potential energy model. @@ -62,43 +61,21 @@ def __init__(self, *args, **kwargs): # anchor added @property def output_def(self) -> ModelOutputDef: """Get the output definition of this model.""" - print(f"--self of DeepPot in deep_pot.py of {type(self)}: {self.__dict__.keys()}, {dir(self)}") # anchor added - # print(f"--self.deep_eval type in output_def in deep_pot.py: {type(self.deep_eval)}") # anchor added - if "deep_eval" in list(self.__dict__.keys()): # anchor added - model_type = type(get_model(self.deep_eval.input_param)) - if "Hessian" in str(model_type): - print(f"--model_type in output_def in deep_pot.py: {model_type}") - return ModelOutputDef( - FittingOutputDef( - [ - OutputVariableDef( - "energy", - shape=[1], - reducible=True, - r_differentiable=True, - c_differentiable=True, - atomic=True, - r_hessian=True, # anchor added: if type of model is EHM - ), - ] - ) - ) - else: - return ModelOutputDef( - FittingOutputDef( - [ - OutputVariableDef( - "energy", - shape=[1], - reducible=True, - r_differentiable=True, - c_differentiable=True, - atomic=True, - r_hessian=True, # anchor added: if type of model is EHM - ), - ] - ) + return ModelOutputDef( + FittingOutputDef( + [ + OutputVariableDef( + "energy", + shape=[1], + reducible=True, + r_differentiable=True, + c_differentiable=True, + atomic=True, + r_hessian=True, # anchor added: if type of model is EHM + ), + ] ) + ) @property def output_def_mag(self) -> ModelOutputDef: @@ -236,8 +213,6 @@ def eval( aparam=aparam, **kwargs, ) - print(f"--results.keys in eval in deep_pot.py of {type(results)}: {results.keys()}") # anchor added: no hessian yet - print(f"--results.values in eval in deep_pot.py: {[type(v) for v in results.values()]}") # anchor added: ndarray energy = results["energy_redu"].reshape(nframes, 1) force = results["energy_derv_r"].reshape(nframes, natoms, 3) virial = results["energy_derv_c_redu"].reshape(nframes, 9) @@ -278,7 +253,6 @@ def eval( if "energy_derv_r_derv_r" in list(results.keys()): # anchor added hessian = results["energy_derv_r_derv_r"].reshape(nframes, 3 * natoms, 3 * natoms) result += (hessian, ) - print(f"--hessian is added to the tail of result in eval in deep_pot.py") return result From 7502ae82b29d8af9c113dc3f8e5505d6028bae7d Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:34:15 +0800 Subject: [PATCH 019/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 48 ------------------------------------ 1 file changed, 48 deletions(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index 3aca52acb9..5c4ba18f92 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -95,30 +95,12 @@ def __init__( **kwargs: Any, ): self.output_def = output_def - # print(f"--output_def.keys in __init__ in pt/deep_eval.py: {self.output_def.var_defs.keys()}") # anchor added - # if "energy" in list(self.output_def.var_defs.keys()): # anchor added - # self.output_def.var_defs["energy"].r_hessian = True # if model type is EHM: useless - # print(f"--output_def.var_defs['energy'].r_hessian is set to True in __init__ in pt/deep_eval.py") - # print(f"--output_def.keys in __init__ in pt/deep_eval.py with r_h True: {self.output_def.var_defs.keys()}") - # print(f"--output_def.values in __init__ in pt/deep_eval.py: " - # f"{type(list(self.output_def.var_defs.values())[0])}") # anchor added: OutputVariableDef - # print(f"--output_def.def_outp in __init__ in pt/deep_eval.py of {type(self.output_def.def_outp)}: " - # f"{self.output_def.def_outp}") # anchor added: FittingOutputDef - # print(f"--output_def.def_derv_r in __init__ in pt/deep_eval.py of {type(self.output_def.def_derv_r)}: " - # f"{self.output_def.def_derv_r}") # anchor added: dict: {energy_derv_r: OutputVariableDef} - # print(f"--output_def.def_hess_r in __init__ in pt/deep_eval.py of {type(self.output_def.def_hess_r)}: " - # f"{self.output_def.def_hess_r}") # anchor added: dict: {} self.model_path = model_file if str(self.model_path).endswith(".pt"): state_dict = torch.load(model_file, map_location=env.DEVICE) - # print(f"--state_dict of {type(state_dict)} in pt/deep_eval.py: {state_dict.keys()}") # anchor added if "model" in state_dict: state_dict = state_dict["model"] - # print(f"--state_dict['model'] of {type(state_dict)} in pt/deep_eval.py: {state_dict.keys()}") # anchor added self.input_param = state_dict["_extra_state"]["model_params"] - # print(f"--input param of {type(state_dict)} in pt/deep_eval.py: {self.input_param.keys()}") # anchor added - # print(f"--state_dict['_extra_state'] of {type(state_dict['_extra_state'])} in pt/deep_eval.py: " - # f"{state_dict['_extra_state'].keys()}") # anchor added self.multi_task = "model_dict" in self.input_param if self.multi_task: model_keys = list(self.input_param["model_dict"].keys()) @@ -137,15 +119,8 @@ def __init__( ] = state_dict[item].clone() state_dict = state_dict_head model = get_model(self.input_param).to(DEVICE) - print(f"--type of model in __init__ in pt/deep_eval: {type(model)}") # anchor added -- EHM - # model = torch.jit.script(model) # anchor commented out - # print(f"--type of model in pt/infer/deep_eval after torch.jit: {type(model)}") # anchor added self.dp = ModelWrapper(model) - # print(f"--dp in pt/infer/deep_eval of {type(self.dp)}: {self.dp}") # anchor added self.dp.load_state_dict(state_dict) - # print(f"--dp in pt/infer/deep_eval after load of {type(self.dp)}: {self.dp}") # anchor added - print(f"--dp.model in pt/deep_eval.py of {type(self.dp.model)}: {self.dp.model.keys()}") # anchor added - print(f"--dp.model.default in pt/deep_eval.py of {type(self.dp.model['Default'])}") # anchor added -- EHM elif str(self.model_path).endswith(".pth"): model = torch.jit.load(model_file, map_location=env.DEVICE) self.dp = ModelWrapper(model) @@ -167,7 +142,6 @@ def __init__( self._has_spin = getattr(self.dp.model["Default"], "has_spin", False) if callable(self._has_spin): self._has_spin = self._has_spin() - print(f"--class DeepEval in pt/deep_eval.py is activated") # anchor added def get_rcut(self) -> float: """Get the cutoff radius of this model.""" @@ -193,10 +167,7 @@ def get_dim_aparam(self) -> int: def model_type(self) -> Type["DeepEvalWrapper"]: """The the evaluator of the model type.""" model_output_type = self.dp.model["Default"].model_output_type() - print(f"--model_output_type in model_type in pt/deep_eval.py of {type(model_output_type)}: " - f"{model_output_type} of {type(model_output_type[0])}") # anchor added -- list ['energy', 'mask'] str if "energy" in model_output_type: - print(f"--model_output_type of DeepPot is returned in model_type_in pt/deep_eval.py") # anchor added return DeepPot elif "dos" in model_output_type: return DeepDOS @@ -283,7 +254,6 @@ def eval( variables, and the values are the corresponding output arrays. """ # convert all of the input to numpy array - print(f"--eval in pt/deep_eval.py is activated") # anchor added atom_types = np.array(atom_types, dtype=np.int32) coords = np.array(coords) if cells is not None: @@ -292,9 +262,7 @@ def eval( coords, atom_types, len(atom_types.shape) > 1 ) request_defs = self._get_request_defs(atomic) - print(f"--request_defs via _get_request_defs in eval in pt/deep_eval.py: {len(request_defs)} {type(request_defs[0])}") # anchor added if "spin" not in kwargs or kwargs["spin"] is None: - print(f"--type of _eval_model in eval in pt/deep_eval.py: {type(self._eval_model)}") # anchor added -- out = self._eval_func(self._eval_model, numb_test, natoms)( coords, cells, atom_types, fparam, aparam, request_defs ) @@ -308,10 +276,6 @@ def eval( aparam, request_defs, ) - # anchor noted: out here are only of 5 contents without 'energy_derv_c_derv_c' - print(f"--request_defs in eval in pt/deep_eval.py: {[x.name for x in request_defs]}") # anchor added - print(f"--out in eval in pt/deep_eval.py: {[o.shape for o in out]}") # anchor added - # anchor noted: request_defs: ['energy', 'energy_redu', 'energy_derv_r', 'energy_derv_c_redu', 'mask'] return dict( zip( [x.name for x in request_defs], @@ -336,15 +300,9 @@ def _get_request_defs(self, atomic: bool) -> List[OutputVariableDef]: list[OutputVariableDef] The requested output definitions. """ - print(f"--output_def in _get_request_defs in pt/deep_eval.py: {self.output_def.var_defs.keys()}") # anchor added if atomic: - print(f"--atomic in _get_request_defs in pt/deep_eval.py is called") # anchor added return list(self.output_def.var_defs.values()) else: - print(f"--atomic in _get_request_defs in pt/deep_eval.py is not called") # anchor added - print(f"--self.output_def.var_defs.keys in _get_request_defs in pt/deep_eval.py: {self.output_def.var_defs.keys()}") # anchor added - print(f"--self.output_def.var_defs.value.category in _get_request_defs in pt/deep_eval.py: {[x.category for x in self.output_def.var_defs.values()]}") # anchor added - # print(f"--self.output_def.var_defs.values in _get_request_defs in pt/deep_eval.py: {self.output_def.var_defs.values()}") # anchor added return [ x for x in self.output_def.var_defs.values() @@ -384,7 +342,6 @@ def eval_func(*args, **kwargs): else: eval_func = inner_func - print(f"--type of eval_func in _eval_func in pt/deep_eval.py: {type(eval_func)}") # anchor added -- return eval_func def _get_natoms_and_nframes( @@ -459,17 +416,12 @@ def _eval_model( ) if isinstance(batch_output, tuple): batch_output = batch_output[0] - print(f"--batch_output in _eval_model in pt/deep_eval.py of {type(batch_output)}: " - f"{batch_output.keys()}") # anchor added: dict with hessian in results = [] - print(f"--request_defs in _eval_model in pt/deep_eval.py of {type(request_defs)}: {request_defs}") # anchor added for odef in request_defs: pt_name = self._OUTDEF_DP2BACKEND[odef.name] - # print(f"--pt_name in _eval_model in pt/deep_eval.py: {pt_name}") # anchor added if pt_name in batch_output: shape = self._get_output_shape(odef, nframes, natoms) - print(f"--pt {pt_name} in _eval_model in pt/deep_eval.py shape: {shape}, odef: {odef}") # anchor added out = batch_output[pt_name].reshape(shape).detach().cpu().numpy() results.append(out) else: From 950a4daba134658f89f9583295d9a5a73426f170 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:35:43 +0800 Subject: [PATCH 020/189] Update ener_hess.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/ener_hess.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/deepmd/pt/loss/ener_hess.py b/deepmd/pt/loss/ener_hess.py index c0c00f6638..b664ade4a9 100644 --- a/deepmd/pt/loss/ener_hess.py +++ b/deepmd/pt/loss/ener_hess.py @@ -155,15 +155,6 @@ def forward(self, input_dict, model, label, natoms, learning_rate, mae=False): Other losses for display. """ model_pred = model(**input_dict) - # print(f"keys of input_dict: {input_dict.keys()}") # anchor added - # print(f"keys of model_pred: {model_pred.keys()}") # anchor added - # print(f"keys of label: {label.keys()}") # anchor added - # print(f"label['find_hessian']: {label['hessian'].size()}, {label['find_hessian']}") # anchor added - # na3 = int(label['hessian'].cpu().detach().numpy().shape[1] ** 0.5) # hess size is [1,na3*na3,1]; anchor added - # print(f"label['hessian']: {label['hessian'].cpu().detach().numpy().reshape(na3, -1)[:5,:5]}") # anchor added - # print(f"label['find_force']: {label['force'].size()}, {label['find_force']}") # anchor added - # print(f"label['find_energy']: {label['energy'].size()}, {label['find_energy']}") # anchor added - # print(f"model: {type(model)}") # anchor added coef = learning_rate / self.starter_learning_rate pref_e = self.limit_pref_e + (self.start_pref_e - self.limit_pref_e) * coef pref_f = self.limit_pref_f + (self.start_pref_f - self.limit_pref_f) * coef @@ -179,7 +170,6 @@ def forward(self, input_dict, model, label, natoms, learning_rate, mae=False): # more_loss['test_keys'] = [] # showed when doing dp test atom_norm = 1.0 / natoms if self.has_e and "energy" in model_pred and "energy" in label: - # print("{if self.has_e and 'energy' in model_pred and 'energy' in label} is valid") # anchor added energy_pred = model_pred["energy"] energy_label = label["energy"] if self.enable_atom_ener_coeff and "atom_energy" in model_pred: @@ -238,7 +228,6 @@ def forward(self, input_dict, model, label, natoms, learning_rate, mae=False): and "force" in model_pred and "force" in label ): - # print("{if self.has_f and 'force' in model_pred and 'force' in label} is valid") # anchor added find_force = label.get("find_force", 0.0) pref_f = pref_f * find_force force_pred = model_pred["force"] @@ -319,7 +308,6 @@ def forward(self, input_dict, model, label, natoms, learning_rate, mae=False): ) if self.has_v and "virial" in model_pred and "virial" in label: - # print("{if self.has_v and 'virial' in model_pred and 'virial' in label} is valid") # anchor added find_virial = label.get("find_virial", 0.0) pref_v = pref_v * find_virial diff_v = label["virial"] - model_pred["virial"].reshape(-1, 9) @@ -336,27 +324,16 @@ def forward(self, input_dict, model, label, natoms, learning_rate, mae=False): more_loss["mae_v"] = self.display_if_exist(mae_v.detach(), find_virial) if self.has_h and "hessian" in model_pred and "hessian" in label: # anchor added; sth to be corrected - # print("{if self.has_h and 'hessian' in model_pred and 'hessian' in label} is valid") find_hessian = label.get("find_hessian", 0.0) pref_h = pref_h * find_hessian - # print(f"label['hessian']: {type(label['hessian'])}, size from {label['hessian'].size()} " - # f"to {label['hessian'].reshape(-1,).size()}") # anchor added - # print(f"model_pred['hessian']: {type(model_pred['hessian'])}, size from {model_pred['hessian'].size()}" - # f"to {model_pred['hessian'].reshape(-1,).size()}") # anchor added - # print(f"label_hess has nan of {np.sum(np.isnan(label['hessian'].reshape(-1,).cpu().detach().numpy()))}") - # print(f"model_pred_hess has nan of {np.sum(np.isnan(model_pred['hessian'].reshape(-1,).cpu().detach().numpy()))}") diff_h = label["hessian"].reshape(-1,) - model_pred["hessian"].reshape(-1,) # tbd - # print(f"diff_h: {diff_h.size()}") - # print(f"diff_h has nan of {np.sum(np.isnan(diff_h.cpu().detach().numpy()))}") l2_hessian_loss = torch.mean(torch.square(diff_h)) - # print(f"l2_hessian_loss: {l2_hessian_loss:.8f}") if not self.inference: more_loss["l2_hessian_loss"] = self.display_if_exist( l2_hessian_loss.detach(), find_hessian ) loss += (pref_h * l2_hessian_loss) # tbd rmse_h = l2_hessian_loss.sqrt() # tbd - # print(f"rmse_h: {rmse_h:.8f}") more_loss["rmse_h"] = self.display_if_exist(rmse_h.detach(), find_hessian) if mae: mae_h = torch.mean(torch.abs(diff_h)) # tbd From 2ccc19a9f654c76a26edc1b8934b72fe5b1af4e2 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:37:25 +0800 Subject: [PATCH 021/189] Update ener_hess_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/ener_hess_model.py | 36 ------------------------ 1 file changed, 36 deletions(-) diff --git a/deepmd/pt/model/model/ener_hess_model.py b/deepmd/pt/model/model/ener_hess_model.py index 210ac9b3f4..9f1c992e5b 100644 --- a/deepmd/pt/model/model/ener_hess_model.py +++ b/deepmd/pt/model/model/ener_hess_model.py @@ -52,14 +52,12 @@ def translated_output_def(self): if self.do_grad_r("energy"): output_def["force"] = deepcopy(out_def_data["energy_derv_r"]) output_def["force"].squeeze(-2) - # print(f"output_def['force'] in translated_output_def: {output_def['force']}") # anchor added if self.do_grad_c("energy"): output_def["virial"] = deepcopy(out_def_data["energy_derv_c_redu"]) output_def["virial"].squeeze(-2) output_def["atom_virial"] = deepcopy(out_def_data["energy_derv_c"]) output_def["atom_virial"].squeeze(-3) output_def["hessian"] = deepcopy(out_def_data["energy_derv_r_derv_r"]) # anchor added - # output_def["hessian"].squeeze(-2) # anchor added if "mask" in out_def_data: output_def["mask"] = deepcopy(out_def_data["mask"]) return output_def @@ -86,32 +84,8 @@ def forward( model_predict = {} model_predict["atom_energy"] = model_ret["energy"] model_predict["energy"] = model_ret["energy_redu"] - # ener_tmp = model_predict["energy"] # anchor added - # print(f"energy of {ener_tmp.size()} and grad {ener_tmp.requires_grad} in forward: {ener_tmp}") # anchor added - # coord_flat = coord.view(-1) # anchor added - # coord_flat.requires_grad_(True) # anchor added - # print(f"coord of {coord_flat.size()} and grad {coord_flat.requires_grad} in forward: {coord_flat}") # anchor added - # grad_tmp = torch.autograd.grad(ener_tmp, coord_flat, create_graph=True)[0] # anchor added - # print(f"grad of {grad_tmp.size()} and grad {grad_tmp.requires_grad} in forward: {grad_tmp}") # anchor added if self.do_grad_r("energy"): model_predict["force"] = model_ret["energy_derv_r"].squeeze(-2) - # force_tmp = torch.reshape(model_predict["force"], (-1,)) # anchor added - # print(f"model_predict['force'] of {force_tmp.size()} and {force_tmp.requires_grad} in forward: {force_tmp}") # anchor added - # print(f"grad of force in forward: {model_predict['force'].grad}") - # n_hess_elem = force_tmp.size()[0] # anchor added - # hess_tmp = torch.zeros(n_hess_elem, n_hess_elem, device=force_tmp.device, dtype=force_tmp.dtype) # anchor added - # coord_tmp = torch.reshape(coord, (-1,)) # anchor added - # coord_tmp.requires_grad_(True) # anchor added - # print(f"coord of {coord_tmp.size()} and {coord_tmp.requires_grad} in forward: {coord_tmp}") # anchor added - # for i in range(n_hess_elem): # anchor added - # grad2 = torch.autograd.grad(force_tmp[i], coord_tmp, retain_graph=True, allow_unused=True)[0] - # # if grad2 is None: - # # grad2 = torch.zeros_like(coord_tmp) - # hess_tmp[i] = grad2 - # print(f"hess_tmp of {hess_tmp.size()} in EnergyHessianModel.forward: {hess_tmp}") # anchor added - # anchor: tensor.squeeze(dim) remove -dim if its 1, or keep dim ((2,1,3)-->(2,3), (1,2,3)-->(1,2,3)) - # if self.do_grad_r_hess("energy"): # tbd; anchor created - # model_predict["hessian"] = model_ret["kk"] # kk is m_h_m value if self.do_grad_c("energy"): model_predict["virial"] = model_ret["energy_derv_c_redu"].squeeze(-2) if do_atomic_virial: @@ -123,17 +97,7 @@ def forward( if "mask" in model_ret: model_predict["mask"] = model_ret["mask"] model_predict["hessian"] = model_ret["energy_derv_r_derv_r"] # anchor added squeeze? - # print(f"model_predict['hessian'] before squeeze: size of {model_predict['hessian'].size()}, " - # f"nan of {np.sum(np.isnan(model_predict['hessian'].cpu().detach().numpy()))}") # anchor added - # if np.sum(np.isnan(model_predict['hessian'].cpu().detach().numpy())) == 135: # anchor added - # np.save("./hess_45x45.npy", model_predict['hessian'].cpu().detach().numpy()) - # print("hess_45x45.npy is saved") - # if np.sum(np.isnan(model_predict['hessian'].cpu().detach().numpy())) == 162: # anchor added - # np.save("./hess_54x54.npy", model_predict['hessian'].cpu().detach().numpy()) - # print("hess_54x54.npy is saved") model_predict["hessian"].squeeze(-2) # anchor added: no need to squeeze - # print(f"model_predict['hessian'] after squeeze: size of {model_predict['hessian'].size()}, " - # f"nan of {np.sum(np.isnan(model_predict['hessian'].cpu().detach().numpy()))}") # anchor added else: model_predict = model_ret model_predict["updated_coord"] += coord From bc92bde0ecd116a4aaadec5a6756a01fbeb49e75 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:40:42 +0800 Subject: [PATCH 022/189] Update make_hessian_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/make_hessian_model.py | 54 +-------------------- 1 file changed, 1 insertion(+), 53 deletions(-) diff --git a/deepmd/pt/model/model/make_hessian_model.py b/deepmd/pt/model/model/make_hessian_model.py index eb6402cd59..ac3517e4f4 100644 --- a/deepmd/pt/model/model/make_hessian_model.py +++ b/deepmd/pt/model/model/make_hessian_model.py @@ -16,24 +16,15 @@ def compute_hessian(func, inputs): # anchor created - # print(f"func in compute_hessian of {type(func)}: {func}") - # print(f"inputs in compute_hessian of {type(inputs)}: {inputs}") device = torch.device('cuda:0') inputs.to(device) inputs = inputs.requires_grad_(True) y = func(inputs) grads = torch.autograd.grad(y, inputs, create_graph=True)[0] - # for j in range(len(inputs)): - # grad_j = torch.autograd.grad(y, inputs[j], retain_graph=True, allow_unused=True)[0] - # print(f"{j} grad: {grad_j}") n = len(inputs) hessian = torch.zeros(n, n, device=device) for i in range(n): grad2 = torch.autograd.grad(grads[i], inputs, retain_graph=True, create_graph=True)[0] - # print(f"{i} grad2: {grad2}") - # for j in range(len(inputs)): - # grad_ij = torch.autograd.grad(grads[i], inputs[j], retain_graph=True, allow_unused=True)[0] - # print(f"{i}{j} grad2: {grad_ij}") hessian[i] = grad2 return hessian @@ -152,8 +143,6 @@ def _cal_hessian_all( 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.atomic_output_def() - # print(f"fdef in _cal_hessian_all: {fdef}") # anchor added - # print(f"keys of fdef in _cal_hessian_all: {fdef.keys()}") # anchor added ['energy', 'mask'] # keys of values that require hessian hess_keys: List[str] = [] for kk in fdef.keys(): @@ -166,7 +155,6 @@ def _cal_hessian_all( vdef = fdef[kk] vshape = vdef.shape vsize = math.prod(vdef.shape) - # print(f"vdef is {vdef}, vshape is {vshape}, vsize is {vsize}") # anchor added # loop over frames for ii in range(nf): icoord = coord[ii] @@ -199,45 +187,14 @@ def _cal_hessian_one_component( # box: Optional[torch.Tensor] = None, # 9 # fparam: Optional[torch.Tensor] = None, # nfp # aparam: Optional[torch.Tensor] = None, # (nloc x nap) - # print(f"coord in _cal_hessian_one_component: {coord}") # anchor added - # print(f"atype in _cal_hessian_one_component: {atype}") # anchor added - # print(f"box in _cal_hessian_one_component: {box}") # anchor added wc = wrapper_class_forward_energy(self, ci, atype, box, fparam, aparam) - - # def energy_func(x): # anchor trying: success - # return (x ** 2).sum() - # def energy_func(x): # anchor trying: success - # return (2*x.pow(2)+3*x.pow(2)+x.pow(3)).sum() - # def energy_func(x): # anchor trying: success - # return ((x[0].pow(3))+5*(x[1].pow(2))+2*(x[0].pow(2))*(x[2].pow(5))) - # wc = energy_func - # print(f"wrapper_class in _cal_hessian_one_component: {type(wc)}") # anchor added - # print(f"wc in _cal_hessian_one_component: {wc}") # anchor added - # hess = torch.autograd.functional.hessian( # wc, # coord, # create_graph=False, - # # create_graph=True, # anchor changed to: FloatingPointError: gradients are Nan/Inf - # ) - # jacobian = torch.autograd.functional.jacobian( # anchor trying - # wc, - # coord, - # create_graph=True, + # # create_graph=True, # anchor changed to: FloatingPointError when nopbc # ) - # print(f"jacobian in _cal_hessian_one_component: {jacobian}") # anchor added - # jacobian.backward(torch.ones_like(coord)) - # tmp = torch.autograd.grad(jacobian[0], coord[0], create_graph=False, allow_unused=True) # anchor added - # er_from_wc = wc.__call__(coord) # anchor added - # print(f"er_from_wc in _cal_hessian_one_component: {er_from_wc}") # anchor added - # coord = torch.tensor(coord, requires_grad=True) # anchor trying - # tmp = torch.autograd.grad(er_from_wc, coord[3], create_graph=False, allow_unused=True) # anchor added - # print(f"tmp in _cal_hessian_one_component: {tmp}") # anchor added - # coord = torch.tensor(coord, requires_grad=True) # anchor trying - # print(f"len of coord is {len(coord)}") # anchor hess = compute_hessian(wc, coord) # anchor trying: identical to t.ag.f.hessian - # hess = torch.func.hessian(wc)(coord) # anchor tried - # print(f"hessian in _cal_hessian_one_component: {hess}") # anchor added return hess class wrapper_class_forward_energy: @@ -270,15 +227,6 @@ def __call__( do_atomic_virial=False, ) er = res["energy_redu"][0].view([-1])[ci] - # def energy_func(x): # anchor trying: success - # return (x ** 2).sum() - # res = energy_func(xx) # anchor added - # er = res # anchor added - # print(f"obj in wrapper_class_forward_energy: {self.obj}") # anchor added - # print(f"res in wrapper_class_forward_energy: {res}") # anchor added - # print(f"er in wrapper_class_forward_energy: {er}") # anchor added - # print(f"er grad_fn in wrapper_class_forward_energy: {er.grad_fn}") # anchor added return er return CM - From d69e5f76e4840eb4a35db5b57b9cbe5217e5aeb9 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:42:32 +0800 Subject: [PATCH 023/189] Update training.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/training.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index d4ef3b424e..27a89da03e 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -119,7 +119,6 @@ def __init__( resuming = resume_model is not None self.restart_training = restart_model is not None model_params = config["model"] - # print(f"keys of model_params: {model_params.keys()}") # anchor added training_params = config["training"] self.multi_task = "model_dict" in model_params self.finetune_links = finetune_links @@ -1232,7 +1231,6 @@ def whether_hessian(loss_params): # anchor created loss_type = loss_params.get("type", "ener") if loss_type == "ener": if loss_params["start_pref_h"] > 0.0: - # print("hessian mode is detected") return True else: return False @@ -1240,16 +1238,11 @@ def whether_hessian(loss_params): # anchor created def get_loss(loss_params, start_lr, _ntypes, _model): loss_type = loss_params.get("type", "ener") - # print(f"loss_type: {loss_type}") # anchor added - # print(f"loss_params: {loss_params}") # anchor added - # print(f"start_lr: {start_lr}") # anchor added if whether_hessian(loss_params): # anchor added loss_params["starter_learning_rate"] = start_lr - # print(f"EnergyHessianStdLoss(**loss_params): {EnergyHessianStdLoss(**loss_params)}") return EnergyHessianStdLoss(**loss_params) elif loss_type == "ener": loss_params["starter_learning_rate"] = start_lr - # print(f"EnergyStdLoss(**loss_params): {EnergyStdLoss(**loss_params)}") # anchor added return EnergyStdLoss(**loss_params) elif loss_type == "dos": loss_params["starter_learning_rate"] = start_lr @@ -1294,9 +1287,7 @@ def get_model_for_wrapper( ): if _loss_params is not None: # anchor added if whether_hessian(_loss_params): - # print("hessian model is utilized") _model_params["hessian_mode"] = True - # print("hessian_mode(True) is added to model_params") if "model_dict" not in _model_params: _model = get_single_model( _model_params, @@ -1308,13 +1299,6 @@ def get_model_for_wrapper( _model[_model_key] = get_single_model( _model_params["model_dict"][_model_key], ) - # if _loss_params is not None: # anchor added - # if whether_hessian(_loss_params): - # print("hessian model is utilized") - # # return make_hessian_model(_model) - # return _model - # else: - # return _model return _model # anchor noted: original return _model only From e2acfe24a784be7f1bada724200812f3b7101696 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:43:07 +0800 Subject: [PATCH 024/189] Update wrapper.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/wrapper.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/deepmd/pt/train/wrapper.py b/deepmd/pt/train/wrapper.py index 501af4eb56..5b2576e63e 100644 --- a/deepmd/pt/train/wrapper.py +++ b/deepmd/pt/train/wrapper.py @@ -173,8 +173,6 @@ def forward( model_pred = self.model[task_key](**input_dict) return model_pred, None, None else: - # torch.save(self.model[task_key].state_dict(), 'b.pt') # anchor added - # self.model[task_key].load_state_dict(torch.load('./b.pt')) # anchor added natoms = atype.shape[-1] model_pred, loss, more_loss = self.loss[task_key]( input_dict, From bffe6eb0d974e53274ae7aa8b6fcfcc44d104779 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:45:48 +0800 Subject: [PATCH 025/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index 5c4ba18f92..21e21ff424 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -119,6 +119,7 @@ def __init__( ] = state_dict[item].clone() state_dict = state_dict_head model = get_model(self.input_param).to(DEVICE) + # model = torch.jit.script(model) # anchor commented out self.dp = ModelWrapper(model) self.dp.load_state_dict(state_dict) elif str(self.model_path).endswith(".pth"): From 31de3e452b9d1713814b1ae9d951e8256e630f07 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:25:37 +0800 Subject: [PATCH 026/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index 21e21ff424..99cae646a6 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -119,7 +119,8 @@ def __init__( ] = state_dict[item].clone() state_dict = state_dict_head model = get_model(self.input_param).to(DEVICE) - # model = torch.jit.script(model) # anchor commented out + if "Hessian" not in str(type(model)): # anchor added: make_hessian_model is not jitable + model = torch.jit.script(model) self.dp = ModelWrapper(model) self.dp.load_state_dict(state_dict) elif str(self.model_path).endswith(".pth"): From ae06bfcc0b5ea8a4d3ded93d9eac5633705dd384 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:09:57 +0800 Subject: [PATCH 027/189] Create test_dp_hessian_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- .../tests/pt/model/test_dp_hessian_model.py | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 source/tests/pt/model/test_dp_hessian_model.py diff --git a/source/tests/pt/model/test_dp_hessian_model.py b/source/tests/pt/model/test_dp_hessian_model.py new file mode 100644 index 0000000000..fdadac59e5 --- /dev/null +++ b/source/tests/pt/model/test_dp_hessian_model.py @@ -0,0 +1,186 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later +import unittest + +import numpy as np +import torch + +from deepmd.dpmodel.descriptor import DescrptSeA as DPDescrptSeA +from deepmd.dpmodel.fitting import EnergyFittingNet as DPEnergyFittingNet +from deepmd.dpmodel.model.ener_model import EnergyModel as DPEnergyModel +from deepmd.pt.model.descriptor.se_a import ( + DescrptSeA, +) +from deepmd.pt.model.model import ( + EnergyModel, + EnergyHessianModel, +) +from deepmd.pt.model.task.ener import ( + EnergyFittingNet, +) +from deepmd.pt.utils import ( + env, +) +from deepmd.pt.utils.utils import ( + to_numpy_array, + to_torch_tensor, +) + +from ...seed import ( + GLOBAL_SEED, +) +from .test_env_mat import ( + TestCaseSingleFrameWithNlist, + TestCaseSingleFrameWithoutNlist, +) + +dtype = env.GLOBAL_PT_FLOAT_PRECISION + +class TestEnergyHessianModel(unittest.TestCase, TestCaseSingleFrameWithoutNlist): + def setUp(self): + TestCaseSingleFrameWithoutNlist.setUp(self) + + def test_self_consistency(self): + ds = DescrptSeA( + self.rcut, + self.rcut_smth, + self.sel, + ).to(env.DEVICE) + ft = EnergyFittingNet( + self.nt, + ds.get_dim_out(), + mixed_types=ds.mixed_types(), + ).to(env.DEVICE) + type_map = ["foo", "bar"] + md0 = EnergyHessianModel(ds, ft, type_map=type_map).to(env.DEVICE) + md1 = EnergyHessianModel.deserialize(md0.serialize()).to(env.DEVICE) + args = [to_torch_tensor(ii) for ii in [self.coord, self.atype, self.cell]] + ret0 = md0.forward(*args) + ret1 = md1.forward(*args) + np.testing.assert_allclose( + to_numpy_array(ret0["atom_energy"]), + to_numpy_array(ret1["atom_energy"]), + atol=self.atol, + ) + np.testing.assert_allclose( + to_numpy_array(ret0["energy"]), + to_numpy_array(ret1["energy"]), + atol=self.atol, + ) + np.testing.assert_allclose( + to_numpy_array(ret0["force"]), + to_numpy_array(ret1["force"]), + atol=self.atol, + ) + np.testing.assert_allclose( + to_numpy_array(ret0["virial"]), + to_numpy_array(ret1["virial"]), + atol=self.atol, + ) + np.testing.assert_allclose( + to_numpy_array(ret0["hessian"]), + to_numpy_array(ret1["hessian"]), + atol=self.atol, + ) + ret0 = md0.forward(*args, do_atomic_virial=True) + ret1 = md1.forward(*args, do_atomic_virial=True) + np.testing.assert_allclose( + to_numpy_array(ret0["atom_virial"]), + to_numpy_array(ret1["atom_virial"]), + atol=self.atol, + ) + + def test_energy_consistency(self): + ds = DescrptSeA( + self.rcut, + self.rcut_smth, + self.sel, + ).to(env.DEVICE) + ft = EnergyFittingNet( + self.nt, + ds.get_dim_out(), + mixed_types=ds.mixed_types(), + ).to(env.DEVICE) + type_map = ["foo", "bar"] + md0 = EnergyModel(ds, ft, type_map=type_map).to(env.DEVICE) + md1 = EnergyHessianModel.deserialize(md0.serialize()).to(env.DEVICE) + args = [to_torch_tensor(ii) for ii in [self.coord, self.atype, self.cell]] + ret0 = md0.forward(*args) + ret1 = md1.forward(*args) + np.testing.assert_allclose( + to_numpy_array(ret0["atom_energy"]), + to_numpy_array(ret1["atom_energy"]), + atol=self.atol, + ) + np.testing.assert_allclose( + to_numpy_array(ret0["energy"]), + to_numpy_array(ret1["energy"]), + atol=self.atol, + ) + np.testing.assert_allclose( + to_numpy_array(ret0["force"]), + to_numpy_array(ret1["force"]), + atol=self.atol, + ) + np.testing.assert_allclose( + to_numpy_array(ret0["virial"]), + to_numpy_array(ret1["virial"]), + atol=self.atol, + ) + ret0 = md0.forward(*args, do_atomic_virial=True) + ret1 = md1.forward(*args, do_atomic_virial=True) + np.testing.assert_allclose( + to_numpy_array(ret0["atom_virial"]), + to_numpy_array(ret1["atom_virial"]), + atol=self.atol, + ) + + def test_forward_consistency(self): + ds = DescrptSeA( + self.rcut, + self.rcut_smth, + self.sel, + ).to(env.DEVICE) + ft = EnergyFittingNet( + self.nt, + ds.get_dim_out(), + mixed_types=ds.mixed_types(), + ).to(env.DEVICE) + type_map = ["foo", "bar"] + md0 = EnergyHessianModel(ds, ft, type_map=type_map).to(env.DEVICE) + md1 = EnergyHessianModel.deserialize(md0.serialize()).to(env.DEVICE) + md0.requires_hessian("energy") + args = [to_torch_tensor(ii) for ii in [self.coord, self.atype, self.cell]] + ret0 = md0.forward_common(*args) + ret1 = md1.forward(*args) + np.testing.assert_allclose( + to_numpy_array(ret0["energy"].squeeze()), + to_numpy_array(ret1["atom_energy"].squeeze()), + atol=self.atol, + ) + np.testing.assert_allclose( + to_numpy_array(ret0["energy_redu"].squeeze()), + to_numpy_array(ret1["energy"].squeeze()), + atol=self.atol, + ) + np.testing.assert_allclose( + to_numpy_array(ret0["energy_derv_r"].squeeze()), + to_numpy_array(ret1["force"].squeeze()), + atol=self.atol, + ) + np.testing.assert_allclose( + to_numpy_array(ret0["energy_derv_c_redu"].squeeze()), + to_numpy_array(ret1["virial"].squeeze()), + atol=self.atol, + ) + np.testing.assert_allclose( + to_numpy_array(ret0["energy_derv_r_derv_r"].squeeze()), + to_numpy_array(ret1["hessian"].squeeze()), + atol=self.atol, + ) + ret0 = md0.forward_common(*args, do_atomic_virial=True) + ret1 = md1.forward(*args, do_atomic_virial=True) + np.testing.assert_allclose( + to_numpy_array(ret0["energy_derv_c"].squeeze()), + to_numpy_array(ret1["atom_virial"].squeeze()), + atol=self.atol, + ) From 24f76f1103c19fb394fff73510e1693313ba7026 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 21 Aug 2024 23:51:57 +0800 Subject: [PATCH 028/189] Update argcheck.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/utils/argcheck.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/deepmd/utils/argcheck.py b/deepmd/utils/argcheck.py index 1a4603dcd2..e44fa206a9 100644 --- a/deepmd/utils/argcheck.py +++ b/deepmd/utils/argcheck.py @@ -2336,6 +2336,13 @@ def loss_tensor(): default=None, doc=doc_local_weight, ), + Argument( + "pref_t_f", + [float, int], + optional=True, + default=None, + doc=doc_global_weight, + ) # anchor added: pref_t_f stands for first-order derivative of tensor ] From a933f7a1c07d7d0dc8a4a833d84e4869f340eba9 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 21 Aug 2024 23:52:34 +0800 Subject: [PATCH 029/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 80 ++++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 12 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index ac93d7c1e9..a004526a2a 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -157,7 +157,7 @@ def test( append_detail=(cc != 0), ) elif isinstance(dp, DeepDipole): - err = test_dipole(dp, data, numb_test, detail_file, atomic) + err = test_dipole(dp, data, system, numb_test, detail_file, atomic, append_detail=(cc != 0)) # anchor added elif isinstance(dp, DeepPolar): err = test_polar(dp, data, numb_test, detail_file, atomic=atomic) elif isinstance(dp, DeepGlobalPolar): # should not appear in this new version @@ -815,9 +815,13 @@ def run_test(dp: "DeepTensor", test_data: dict, numb_test: int, test_sys: Deepmd else: box = None atype = test_data["type"][0] - prediction = dp.eval(coord, box, atype) - - return prediction.reshape([numb_test, -1]), numb_test, atype + # print(f"test_data in run_test in test.py: {test_data.keys()}") # anchor added + if "find_dipole_force" in test_data.keys(): + if test_data["find_dipole_force"]: + prediction = dp.eval_full(coord, box, atype) + else: + prediction = dp.eval(coord, box, atype).reshape([numb_test, -1]) + return prediction, numb_test, atype def test_wfc( @@ -1027,9 +1031,11 @@ def print_polar_sys_avg(avg): def test_dipole( dp: "DeepDipole", data: DeepmdData, + system: str, # anchor added numb_test: int, detail_file: Optional[str], atomic: bool, + append_detail: bool = False, # anchor added ) -> Tuple[List[np.ndarray], List[int]]: """Test energy type model. @@ -1059,8 +1065,27 @@ def test_dipole( high_prec=False, type_sel=dp.get_sel_type(), ) + data.add( + "dipole_force", + 9, + atomic=True, + must=False, + high_prec=False, + type_sel=dp.get_sel_type(), + ) # anchor added test_data = data.get_test() - dipole, numb_test, atype = run_test(dp, test_data, numb_test, data) + + preds, numb_test, atype = run_test(dp, test_data, numb_test, data) # anchor: dipole --> preds + if "find_dipole_force" in test_data.keys(): # anchor added + if test_data["find_dipole_force"]: + if atomic: + dipole, dipole_force, dipole_virial, atomic_dipole, atomic_dipole_virial = preds + else: + dipole, dipole_force, dipole_virial = preds + rmse_t_f = rmse(dipole_force.reshape(dipole.shape[0], -1) - test_data["dipole_force"][:numb_test]) + else: + dipole = preds + rmse_t_f = None sel_type = dp.get_sel_type() sel_natoms = 0 @@ -1085,6 +1110,8 @@ def test_dipole( if not atomic: log.info(f"Dipole RMSE/sqrtN : {rmse_fs:e}") log.info(f"Dipole RMSE/N : {rmse_fa:e}") + if rmse_t_f: # anchor added + log.info(f"Dipole Derivative RMSE : {rmse_t_f:e}") log.info("The unit of error is the same as the unit of provided label.") if detail_file is not None: @@ -1097,7 +1124,7 @@ def test_dipole( ), axis=1, ) - header_text = "data_x data_y data_z pred_x pred_y pred_z" + header_text = f"{system}: data_x data_y data_z pred_x pred_y pred_z" # anchor inserted system else: pe = np.concatenate( ( @@ -1109,22 +1136,49 @@ def test_dipole( axis=1, ) header_text = [ - f"{letter}{number}" + f"{system}: {letter}{number}" # anchor inserted system for number in range(1, sel_natoms + 1) for letter in ["data_x", "data_y", "data_z"] ] + [ - f"{letter}{number}" + f"{system}: {letter}{number}" # anchor inserted system for number in range(1, sel_natoms + 1) for letter in ["pred_x", "pred_y", "pred_z"] ] header_text = " ".join(header_text) - np.savetxt( + # np.savetxt( + # detail_path.with_suffix(".out"), + # pe, + # header=header_text, + # ) # anchor commented out + save_txt_file( detail_path.with_suffix(".out"), pe, header=header_text, - ) - return {"rmse": (rmse_f, dipole.size)} + append=append_detail, + ) # anchor changed np.davetxt to + dict_to_return = {"rmse": (rmse_f, dipole.size)} + if "find_dipole_force" in test_data.keys(): # anchor added + if test_data["find_dipole_force"]: + pf = np.concatenate( + ( + np.reshape(test_data["dipole_force"][:numb_test], [-1, 9]), + np.reshape(dipole_force, [-1, 9]) + ), + axis=1, + ) + save_txt_file( + detail_path.with_suffix(".f.out"), + pf, + header=f"{system}: " + f"data_fxx data_fxy data_fxz data_fyx data_fyy data_fyz " + f"data_fzx data_fzy data_fzz pred_fxx pred_fxy pred_fxz " + f"pred_fyx pred_fyy pred_fyz pred_fzx pred_fzy pred_fzz", + append=append_detail, + ) + dict_to_return["rmse_t_f"] = (rmse_t_f, dipole_force.size) + + return dict_to_return def print_dipole_sys_avg(avg): @@ -1135,5 +1189,7 @@ def print_dipole_sys_avg(avg): avg : np.ndarray array with summaries """ - log.info(f"Dipole RMSE : {avg['rmse']:e} eV/A") + log.info(f"Dipole RMSE : {avg['rmse']:e}") # anchor del eV/A + if "rmse_t_f" in avg.keys(): + log.info(f"Dipole Derivative RMSE : {avg['rmse_t_f']:e}") From 37e9ba41987609419e531604286b297e07101375 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 21 Aug 2024 23:52:59 +0800 Subject: [PATCH 030/189] Update tensor.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/tensor.py | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/deepmd/pt/loss/tensor.py b/deepmd/pt/loss/tensor.py index 3dcf21af1d..94dad7d0e2 100644 --- a/deepmd/pt/loss/tensor.py +++ b/deepmd/pt/loss/tensor.py @@ -24,6 +24,7 @@ def __init__( label_name: str, pref_atomic: float = 0.0, pref: float = 0.0, + pref_t_f: float=0.0, # anchor added inference=False, **kwargs, ): @@ -41,6 +42,8 @@ def __init__( The prefactor of the weight of atomic loss. It should be larger than or equal to 0. pref : float The prefactor of the weight of global loss. It should be larger than or equal to 0. + pref_t_f : float # anchor added + The prefactor of the weight of first derivative loss of tensor. It should be larger than or equal to 0. inference : bool If true, it will output all losses found in output, ignoring the pre-factors. **kwargs @@ -52,6 +55,7 @@ def __init__( self.label_name = label_name self.local_weight = pref_atomic self.global_weight = pref + self.t_f_weight = pref_t_f # anchor added self.inference = inference assert ( @@ -59,6 +63,7 @@ def __init__( ), "Can not assign negative weight to `pref` and `pref_atomic`" self.has_local_weight = self.local_weight > 0.0 or inference self.has_global_weight = self.global_weight > 0.0 or inference + self.has_t_f_weight = self.t_f_weight > 0.0 or inference # anchor added assert self.has_local_weight or self.has_global_weight, AssertionError( "Can not assian zero weight both to `pref` and `pref_atomic`" ) @@ -90,6 +95,8 @@ def forward(self, input_dict, model, label, natoms, learning_rate=0.0, mae=False del learning_rate, mae loss = torch.zeros(1, dtype=env.GLOBAL_PT_FLOAT_PRECISION, device=env.DEVICE)[0] more_loss = {} + # print(f"--model_pred in forward in tensor.py: {model_pred.keys()}") # anchor + # print(f"--label in forward in tensor.py: {label.keys()}") # anchor if ( self.has_local_weight and self.tensor_name in model_pred @@ -148,6 +155,37 @@ def forward(self, input_dict, model, label, natoms, learning_rate=0.0, mae=False more_loss[f"rmse_global_{self.tensor_name}"] = self.display_if_exist( rmse_global.detach(), find_global ) + if ( + self.has_t_f_weight + and "force" in model_pred + and self.label_name + "_force" in label + ): # anchor added + find_t_f = label.get(f"find_{self.label_name}_force", 0.0) + t_f_weight = self.t_f_weight * find_t_f + t_f_pred = model_pred["force"].reshape( + [-1, natoms, self.tensor_size * 3] + ) + # print(f"--t_f_pred in forward in tensor.py of {t_f_pred.shape}") + t_f_label = label[self.label_name + "_force"].reshape( + [-1, natoms, self.tensor_size * 3] + ) + # print(f"--t_f_label in forward in tensor.py of {t_f_label.shape}") + diff = (t_f_pred - t_f_label).reshape( + -1, self.tensor_size * 3 + ) + # print(f"--diff in forward in tensor.py of {diff.shape}") + if "mask" in model_pred: + diff = diff[model_pred["mask"].reshape([-1]).bool()] + l2_t_f_loss = torch.mean(torch.square(diff)) + if not self.inference: + more_loss[f"l2_{self.tensor_name}_force_loss"] = self.display_if_exist( + l2_t_f_loss.detach(), find_t_f + ) + loss += t_f_weight * l2_t_f_loss + rmse_t_f = l2_t_f_loss.sqrt() + more_loss[f"rmse_{self.tensor_name}_force"] = self.display_if_exist( + rmse_t_f.detach(), find_t_f + ) return model_pred, loss, more_loss @property @@ -174,4 +212,14 @@ def label_requirement(self) -> List[DataRequirementItem]: high_prec=False, ) ) + if self.has_t_f_weight: # anchor + label_requirement.append( + DataRequirementItem( + self.label_name + "_force", + ndof=self.tensor_size*3, + atomic=True, + must=False, + high_prec=False, + ) + ) return label_requirement From 0dd8124c2d046ab672e84ef7828365e4b487e41f Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Mon, 26 Aug 2024 15:36:07 +0800 Subject: [PATCH 031/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index a004526a2a..6aa692b8cb 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -1114,6 +1114,10 @@ def test_dipole( log.info(f"Dipole Derivative RMSE : {rmse_t_f:e}") log.info("The unit of error is the same as the unit of provided label.") + dict_to_return = {"rmse": (rmse_f, dipole.size)} + if "find_dipole_force" in test_data.keys(): # anchor added + if test_data["find_dipole_force"]: + dict_to_return["rmse_t_f"] = (rmse_t_f, dipole_force.size) if detail_file is not None: detail_path = Path(detail_file) if not atomic: @@ -1157,7 +1161,7 @@ def test_dipole( header=header_text, append=append_detail, ) # anchor changed np.davetxt to - dict_to_return = {"rmse": (rmse_f, dipole.size)} + # dict_to_return = {"rmse": (rmse_f, dipole.size)} if "find_dipole_force" in test_data.keys(): # anchor added if test_data["find_dipole_force"]: pf = np.concatenate( @@ -1176,7 +1180,6 @@ def test_dipole( f"pred_fyx pred_fyy pred_fyz pred_fzx pred_fzy pred_fzz", append=append_detail, ) - dict_to_return["rmse_t_f"] = (rmse_t_f, dipole_force.size) return dict_to_return From 65ef47a19bc20173bf30a8a974709a1309e6d031 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 14:44:08 +0800 Subject: [PATCH 032/189] Update env_mat.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/descriptor/env_mat.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deepmd/pt/model/descriptor/env_mat.py b/deepmd/pt/model/descriptor/env_mat.py index e89e7467d3..f6db7cae12 100644 --- a/deepmd/pt/model/descriptor/env_mat.py +++ b/deepmd/pt/model/descriptor/env_mat.py @@ -22,9 +22,12 @@ def _make_env_mat( mask = nlist >= 0 # nlist = nlist * mask ## this impl will contribute nans in Hessian calculation. nlist = torch.where(mask, nlist, nall - 1) + nlist = torch.where(mask, nlist, nall) coord_l = coord[:, :natoms].view(bsz, -1, 1, 3) index = nlist.view(bsz, -1).unsqueeze(-1).expand(-1, -1, 3) coord_r = torch.gather(coord, 1, index) + coord_pad = torch.concat([coord, coord[:, -1:, :] + rcut], dim=1) + coord_r = torch.gather(coord_pad, 1, index) coord_r = coord_r.view(bsz, natoms, nnei, 3) diff = coord_r - coord_l length = torch.linalg.norm(diff, dim=-1, keepdim=True) @@ -53,7 +56,6 @@ def prod_env_mat( protection: float = 0.0, ): """Generate smooth environment matrix from atom coordinates and other context. - Args: - extended_coord: Copied atom coordinates with shape [nframes, nall*3]. - atype: Atom types with shape [nframes, nloc]. @@ -63,7 +65,6 @@ def prod_env_mat( - rcut_smth: Smooth hyper-parameter for pair force & energy. - radial_only: Whether to return a full description or a radial-only descriptor. - protection: Protection parameter to prevent division by zero errors during calculations. - Returns ------- - env_mat: Shape is [nframes, natoms[1]*nnei*4]. From 1004f76d9e89fb31409eacf3c860884e030a002c Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 14:59:08 +0800 Subject: [PATCH 033/189] Update env_mat.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/descriptor/env_mat.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/deepmd/pt/model/descriptor/env_mat.py b/deepmd/pt/model/descriptor/env_mat.py index f6db7cae12..e89e7467d3 100644 --- a/deepmd/pt/model/descriptor/env_mat.py +++ b/deepmd/pt/model/descriptor/env_mat.py @@ -22,12 +22,9 @@ def _make_env_mat( mask = nlist >= 0 # nlist = nlist * mask ## this impl will contribute nans in Hessian calculation. nlist = torch.where(mask, nlist, nall - 1) - nlist = torch.where(mask, nlist, nall) coord_l = coord[:, :natoms].view(bsz, -1, 1, 3) index = nlist.view(bsz, -1).unsqueeze(-1).expand(-1, -1, 3) coord_r = torch.gather(coord, 1, index) - coord_pad = torch.concat([coord, coord[:, -1:, :] + rcut], dim=1) - coord_r = torch.gather(coord_pad, 1, index) coord_r = coord_r.view(bsz, natoms, nnei, 3) diff = coord_r - coord_l length = torch.linalg.norm(diff, dim=-1, keepdim=True) @@ -56,6 +53,7 @@ def prod_env_mat( protection: float = 0.0, ): """Generate smooth environment matrix from atom coordinates and other context. + Args: - extended_coord: Copied atom coordinates with shape [nframes, nall*3]. - atype: Atom types with shape [nframes, nloc]. @@ -65,6 +63,7 @@ def prod_env_mat( - rcut_smth: Smooth hyper-parameter for pair force & energy. - radial_only: Whether to return a full description or a radial-only descriptor. - protection: Protection parameter to prevent division by zero errors during calculations. + Returns ------- - env_mat: Shape is [nframes, natoms[1]*nnei*4]. From 97f09b0f2aaeba67ef27d7b625e240c30efe79c6 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 15:22:04 +0800 Subject: [PATCH 034/189] Update training.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/training.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index 27a89da03e..dd0dca8382 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -1285,10 +1285,10 @@ def get_model_for_wrapper( _model_params, _loss_params=None, # anchor added ): - if _loss_params is not None: # anchor added - if whether_hessian(_loss_params): - _model_params["hessian_mode"] = True if "model_dict" not in _model_params: + if _loss_params is not None: # anchor added + if whether_hessian(_loss_params): + _model_params["hessian_mode"] = True _model = get_single_model( _model_params, ) @@ -1296,6 +1296,9 @@ def get_model_for_wrapper( _model = {} model_keys = list(_model_params["model_dict"]) for _model_key in model_keys: + if _loss_params is not None: # anchor added + if whether_hessian(_loss_params): + _model_params["model_dict"][_model_key]["hessian_mode"] = True _model[_model_key] = get_single_model( _model_params["model_dict"][_model_key], ) From 967952bd29cdbb969dc2cf583ee9efe4b9e2c42a Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 16:09:02 +0800 Subject: [PATCH 035/189] Create input.json Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hess/single-task/input.json | 103 +++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 examples/hess/single-task/input.json diff --git a/examples/hess/single-task/input.json b/examples/hess/single-task/input.json new file mode 100644 index 0000000000..bf3ca09aea --- /dev/null +++ b/examples/hess/single-task/input.json @@ -0,0 +1,103 @@ +{ + "_comment": "that's all", + "model": { + "type_map": [ + "C", + "H", + "N", + "O" + ], + "descriptor": { + "type": "dpa2", + "repinit": { + "tebd_dim": 8, + "rcut": 9.0, + "rcut_smth": 8.0, + "nsel": 120, + "neuron": [ + 25, + 50, + 100 + ], + "axis_neuron": 12, + "activation_function": "tanh" + }, + "repformer": { + "rcut": 4.0, + "rcut_smth": 3.5, + "nsel": 40, + "nlayers": 12, + "g1_dim": 128, + "g2_dim": 32, + "attn2_hidden": 32, + "attn2_nhead": 4, + "attn1_hidden": 128, + "attn1_nhead": 4, + "axis_neuron": 4, + "update_h2": false, + "update_g1_has_conv": true, + "update_g1_has_grrg": true, + "update_g1_has_drrd": true, + "update_g1_has_attn": true, + "update_g2_has_g1g1": true, + "update_g2_has_attn": true, + "attn2_has_gate": true + }, + "add_tebd_to_repinit_out": false + }, + "fitting_net": { + "neuron": [ + 240, + 240, + 240 + ], + "resnet_dt": true, + "seed": 1, + "_comment": " that's all" + }, + "_comment": " that's all" + }, + "learning_rate": { + "type": "exp", + "decay_steps": 5000, + "start_lr": 0.0002, + "stop_lr": 3.51e-08, + "_comment": "that's all" + }, + "loss": { + "type": "ener", + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 10, + "limit_pref_v": 1, + "start_pref_h": 10, + "limit_pref_h": 1, + "_comment": " that's all" + }, + "training": { + "training_data": { + "systems": [ + "/home/admin/workspace/yuzhaoxi/hessian/data/test_sys/H8C4N2O" + ], + "batch_size": 1, + "_comment": "that's all" + }, + "validation_data": { + "systems": [ + "/home/admin/workspace/yuzhaoxi/hessian/data/test_sys/H10C5N2O" + ], + "batch_size": 1, + "_comment": "that's all" + }, + "numb_steps": 1, + "warmup_steps": 0, + "gradient_max_norm": 5.0, + "seed": 10, + "disp_file": "lcurve.out", + "disp_freq": 100, + "save_freq": 2000, + "_comment": "that's all" + } +} From 7db368872e088e0504344b998dce315c0308074a Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 16:10:20 +0800 Subject: [PATCH 036/189] Delete examples/hess directory Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hess/single-task/input.json | 103 --------------------------- 1 file changed, 103 deletions(-) delete mode 100644 examples/hess/single-task/input.json diff --git a/examples/hess/single-task/input.json b/examples/hess/single-task/input.json deleted file mode 100644 index bf3ca09aea..0000000000 --- a/examples/hess/single-task/input.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "_comment": "that's all", - "model": { - "type_map": [ - "C", - "H", - "N", - "O" - ], - "descriptor": { - "type": "dpa2", - "repinit": { - "tebd_dim": 8, - "rcut": 9.0, - "rcut_smth": 8.0, - "nsel": 120, - "neuron": [ - 25, - 50, - 100 - ], - "axis_neuron": 12, - "activation_function": "tanh" - }, - "repformer": { - "rcut": 4.0, - "rcut_smth": 3.5, - "nsel": 40, - "nlayers": 12, - "g1_dim": 128, - "g2_dim": 32, - "attn2_hidden": 32, - "attn2_nhead": 4, - "attn1_hidden": 128, - "attn1_nhead": 4, - "axis_neuron": 4, - "update_h2": false, - "update_g1_has_conv": true, - "update_g1_has_grrg": true, - "update_g1_has_drrd": true, - "update_g1_has_attn": true, - "update_g2_has_g1g1": true, - "update_g2_has_attn": true, - "attn2_has_gate": true - }, - "add_tebd_to_repinit_out": false - }, - "fitting_net": { - "neuron": [ - 240, - 240, - 240 - ], - "resnet_dt": true, - "seed": 1, - "_comment": " that's all" - }, - "_comment": " that's all" - }, - "learning_rate": { - "type": "exp", - "decay_steps": 5000, - "start_lr": 0.0002, - "stop_lr": 3.51e-08, - "_comment": "that's all" - }, - "loss": { - "type": "ener", - "start_pref_e": 0.02, - "limit_pref_e": 1, - "start_pref_f": 1000, - "limit_pref_f": 1, - "start_pref_v": 10, - "limit_pref_v": 1, - "start_pref_h": 10, - "limit_pref_h": 1, - "_comment": " that's all" - }, - "training": { - "training_data": { - "systems": [ - "/home/admin/workspace/yuzhaoxi/hessian/data/test_sys/H8C4N2O" - ], - "batch_size": 1, - "_comment": "that's all" - }, - "validation_data": { - "systems": [ - "/home/admin/workspace/yuzhaoxi/hessian/data/test_sys/H10C5N2O" - ], - "batch_size": 1, - "_comment": "that's all" - }, - "numb_steps": 1, - "warmup_steps": 0, - "gradient_max_norm": 5.0, - "seed": 10, - "disp_file": "lcurve.out", - "disp_freq": 100, - "save_freq": 2000, - "_comment": "that's all" - } -} From d140afab4034737983c7815ff45c3a27e1c449eb Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 16:10:44 +0800 Subject: [PATCH 037/189] Create input.json Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/single-task/input.json | 103 ++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 examples/hessian/single-task/input.json diff --git a/examples/hessian/single-task/input.json b/examples/hessian/single-task/input.json new file mode 100644 index 0000000000..bf3ca09aea --- /dev/null +++ b/examples/hessian/single-task/input.json @@ -0,0 +1,103 @@ +{ + "_comment": "that's all", + "model": { + "type_map": [ + "C", + "H", + "N", + "O" + ], + "descriptor": { + "type": "dpa2", + "repinit": { + "tebd_dim": 8, + "rcut": 9.0, + "rcut_smth": 8.0, + "nsel": 120, + "neuron": [ + 25, + 50, + 100 + ], + "axis_neuron": 12, + "activation_function": "tanh" + }, + "repformer": { + "rcut": 4.0, + "rcut_smth": 3.5, + "nsel": 40, + "nlayers": 12, + "g1_dim": 128, + "g2_dim": 32, + "attn2_hidden": 32, + "attn2_nhead": 4, + "attn1_hidden": 128, + "attn1_nhead": 4, + "axis_neuron": 4, + "update_h2": false, + "update_g1_has_conv": true, + "update_g1_has_grrg": true, + "update_g1_has_drrd": true, + "update_g1_has_attn": true, + "update_g2_has_g1g1": true, + "update_g2_has_attn": true, + "attn2_has_gate": true + }, + "add_tebd_to_repinit_out": false + }, + "fitting_net": { + "neuron": [ + 240, + 240, + 240 + ], + "resnet_dt": true, + "seed": 1, + "_comment": " that's all" + }, + "_comment": " that's all" + }, + "learning_rate": { + "type": "exp", + "decay_steps": 5000, + "start_lr": 0.0002, + "stop_lr": 3.51e-08, + "_comment": "that's all" + }, + "loss": { + "type": "ener", + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 10, + "limit_pref_v": 1, + "start_pref_h": 10, + "limit_pref_h": 1, + "_comment": " that's all" + }, + "training": { + "training_data": { + "systems": [ + "/home/admin/workspace/yuzhaoxi/hessian/data/test_sys/H8C4N2O" + ], + "batch_size": 1, + "_comment": "that's all" + }, + "validation_data": { + "systems": [ + "/home/admin/workspace/yuzhaoxi/hessian/data/test_sys/H10C5N2O" + ], + "batch_size": 1, + "_comment": "that's all" + }, + "numb_steps": 1, + "warmup_steps": 0, + "gradient_max_norm": 5.0, + "seed": 10, + "disp_file": "lcurve.out", + "disp_freq": 100, + "save_freq": 2000, + "_comment": "that's all" + } +} From 2e41d8a3af034581c7c693381c9ec15f3707e999 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 16:11:57 +0800 Subject: [PATCH 038/189] Create tmp Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/data/H8C4N2O/set.000/tmp | 1 + 1 file changed, 1 insertion(+) create mode 100644 examples/hessian/data/H8C4N2O/set.000/tmp diff --git a/examples/hessian/data/H8C4N2O/set.000/tmp b/examples/hessian/data/H8C4N2O/set.000/tmp new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/examples/hessian/data/H8C4N2O/set.000/tmp @@ -0,0 +1 @@ + From f54a1fd731eead42661c13182105bb0f39e2d1be Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 16:12:25 +0800 Subject: [PATCH 039/189] Add files via upload Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/data/H8C4N2O/set.000/box.npy | Bin 0 -> 920 bytes .../hessian/data/H8C4N2O/set.000/coord.npy | Bin 0 -> 4088 bytes .../hessian/data/H8C4N2O/set.000/energy.npy | Bin 0 -> 216 bytes .../hessian/data/H8C4N2O/set.000/force.npy | Bin 0 -> 4088 bytes .../hessian/data/H8C4N2O/set.000/hessian.npy | Bin 0 -> 178328 bytes .../hessian/data/H8C4N2O/set.000/virial.npy | Bin 0 -> 920 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/hessian/data/H8C4N2O/set.000/box.npy create mode 100644 examples/hessian/data/H8C4N2O/set.000/coord.npy create mode 100644 examples/hessian/data/H8C4N2O/set.000/energy.npy create mode 100644 examples/hessian/data/H8C4N2O/set.000/force.npy create mode 100644 examples/hessian/data/H8C4N2O/set.000/hessian.npy create mode 100644 examples/hessian/data/H8C4N2O/set.000/virial.npy diff --git a/examples/hessian/data/H8C4N2O/set.000/box.npy b/examples/hessian/data/H8C4N2O/set.000/box.npy new file mode 100644 index 0000000000000000000000000000000000000000..9fbff5b5c4837fd77cd99c79ee0ee0203b5e3809 GIT binary patch literal 920 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 literal 0 HcmV?d00001 diff --git a/examples/hessian/data/H8C4N2O/set.000/energy.npy b/examples/hessian/data/H8C4N2O/set.000/energy.npy new file mode 100644 index 0000000000000000000000000000000000000000..4761a45a4183c19d1a7d6c87d6e2777bdd3aad9a GIT binary patch literal 216 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-hB}%$3bhL411|3H4<8J54js7KcepVfNMCsxz}XL^*Ijh+DF@QqIkN7` z=pH(-=H{J+TY&uQ@wxV$T89p7KFabq0LWh`x?3e)`_KWIkT;LDf%JFFB&$0>c>oA- BN`e3Y literal 0 HcmV?d00001 diff --git a/examples/hessian/data/H8C4N2O/set.000/force.npy b/examples/hessian/data/H8C4N2O/set.000/force.npy new file mode 100644 index 0000000000000000000000000000000000000000..c69ac3552d881cb46f45e099b3cae1cd2727553e GIT binary patch literal 4088 zcmbW1=_3@1+lFn)sg9IWoFY^QCrd&p%AYnBL&WymNq$TF5^ zkg63a(+vv;XQ zFxJ>qI6y&g_WUrk^cXv$1jDXID}q3!o#|g`b@+6;S5VEB0oVGCEctHG!At7BZZ?Y! z4qpU$oxi+68n;7saa#>&Z3!b+hQ7sn@5gFBZCwDV5SIDIeHP9|P#%jFvVbNvITNKe zgZaDsKc6w5hl&2dnr$t^xVcL=sECV&#>6Ba56M=vwQ^IB=4Zh@8_C02q&jqw^dVOt z8pd_K7}<;10k&!z(j7KTpgj6iL>()FYirS$-1P#{M7*{1w|OeI{7^L}{Fx8-*V^Q! zs*p$mmX-gGISAytT94OXfroT;+?M z$p}uuBZrQ=uB%0&))8|TR(uA;D-%LT|EV*76Z=>4L^_Ig-bp=EQ3|=|cb|77lCf|-&y8XK1h!`x%&)NOK+XDG(0j8isBsjU zGf-w>l(%$hVMiDSr`(ZV`VoVgx6Qb{Rr=vzfT|1qP6b%YZxeYskd3VLD+6=JlR)mj zDX?M5P9Grmhh8F#0X3tbD|`6|QaRoaE2oX1XsIXMdi)Dwj>mKunT+Acoy^Ta+KZqN zle{_2EEV>q4UB2j3;|s(=G5=iZ{T3`!uXoSGS=%jdR%W>0N=8jvwibD;I%MQ|Dtam zQ`d#VoX#DG0s+kH`kC>B4SQ z@Guru_NAis=(x}9ax^OH@w1)PeuC8ozg|2J>Vk{L9s~BYDrkE6aQAgv1UCg^@$&n1 z@H*=Pf8lH$N>84gG_iXP$u1+k%Yk$-P_UMYfH)X;kUSx%Tn!!^Gf&r+TJX;tMsIUu z3H~$V<1M>HN8u}vkEO5QgW>5TmmAqA$ed8y^3A00rJV+hv=W*?TXDZfpv9D`kh+e-<4gJAeTdQxuk2V5ZSyt?UH0hl!A z6NFPnpw&u9P`{@eyKOpMnT~Wk&-Ufe>efc6x;mL#RQMT7uhjY43uxk(Udj;_N*`2> za`Ab%b>Y4(%@PgTEC}eOnTDj-;tA7fA(N7K&@VM-t4XfHCp;fy}n$OX{9@SDt*(`l((TUfWbo)RaIZ<%|?9N*XnX_q73h~HBrRs7J%4dli5-}13?dMk16@I;)s#8ow~?Ryz6O7`Jw+C zn=1Br9L#tHLKc&!w&&D=ccR|qnVKdz(c@7dZ^J;dGEw%ZaSsT!5z}9A?gja0X%%WA zck#2@`;Xp1186}F92_fW06n|#`E}Pk$TeLRSd@E%+q1MU=lxrWv9IobACyT&-YW&o zGwf@yRZ8q*#^fk`4)C0kruU$z?qP*P^%KB-Qo}1~vJ3WB3#)Uojlilp`-D2DgOlxt;16jx%++SatUs~?el zYUf4itVYNot}(m=dO>qw=M!h8cKmj$ASceV@xLEL?LgifNbk>Z;j%4-tYaOIcU|a% zxm?N5>SyV&Em-UAl28v?MXeN0I5IIUy4lWcwg_h!3*N-DU0~d`$)D)di3a(t5djK~ zQ2a<^y6QtQI2#=v%kgZ17aut7^5jckYWn+t)nWw(8t|pr1&e!jo^zV`DE zk{e+_!CX98G}bisZGM1g;4gIAj4^248OD2{o^jTq6#-p z?ftAayij}SZeD&bDEW==6g*FcTWTE_{VojSm@8YC=dVw=_`yUnL7#z){D(aXflN5_ zp|?F%tOzxRieuVU6H!v~)5Wd2B~b9Ie7mq?21p#c`f*2gD5yl;W%j54*YDx-V>&)B z(f7#G=Eu@`m|F2p%p@Wb2e>}&n967XxAx^7I`P#od^dd}&D8@J*(L_V_$n~$P=r9j z!3vapOO0opqrw0Tfx-uzq6pm&AfnSKxKFznMW@7foS@WToX+uk zu7ecpGTh~muSy5e4`0sm9Hl}hRq#TqS2-kMI&JWE5-3e6t!h|#;)6W}-*}fx;bD{e zQ>VYU~uBWGNk0+9)Y|p41^BC!FsyvjJzL za`eM%`cbi=uhp0o19l4E*d)$ZM zjb%yB3;S{;O{QmUn{7ZPHj}1lX)3n9?Y!2V(}GgxEq4B;Ru8A+xsQD6_QV;kU0mr0 z!*H+Z^;;b)2^dzaazMwA2&GoRS;03V;F2tJfT|G*iqpqb_^k@yp;bjh*M(F#qLpp6 zbM`r=^+*?-G718g{6>AnmTXw!xv4rk=naBTvItMLV{l}T=#rU8G*n7KMAow;6#j;P zfAx=nw#cFvmYF`-{Y8TNr=1bd7CtOpsZT^HwLcFpoeu|#d8@mUAu%AE_SZW>hqpMY z+_agfQvw`@MQ+y?S|D?^Qm-~G8S}Xw03@{Fh>zh~O;8JPd>TJ?;?@cdbZGnh?f)HK zZGB+gaFT@fIn7>QGS3(E+!MG6Vg>K7tdQsklsT z7oN*CJD|_m30^wlTK>N#(Atr#*Lb5IW@Hkq&)PB|Vo5jins6KZBvn+$jOD^tiEG0R zQnxVtl|=uWgg8tHdjo-Io?)>2pJE|DgYjqD#&fUdNg(_+%2u=SG2Y|u=AR>{VSc;4 z*t<@16dIndV{wM!kv&u7aVHPl`h4DYJv|%@cu$HYcX)xHPgxc7KmbUtJ5JA41!DH= zUw+RbeL>tjbUtje4@f90f0NAcgs;n#KRotF0N2Dp&E@0CC?5KS*ixH_snH9hj367B zTu2fv7O2JF_0qdELg)~k7V?k0sTNQT$4ta&)ev(hrOKVy30&JwY84-@hQ3gh@j<@( zSkwH%#wb4-hV%vs7o#a?x}&gW!LSxZh6Z(Q4pu|)w-o`t(@*fiMRhxVVg>##jdoM| z@gDmvA`f5xMT0!O+;^0tvDlp^@#Eiww|KBh>zl*j73&eBHZ2> zN!8H2i^XO7na2S$SUZf8c^d>YIejBn8_On`qb z?cP0H7!OzF{$)<`nPV|dyx>rYMhV`~McK1YfUrZcx-{7VYQKL|KiwaN`vz4jc2FK+ zwt&(yCy5tM(-jzV-LW{nEr6X7bOv5NdVU#$1Mum5q_9l(a|{{aa5uP}1$5$2lYwJ2 z#MDP}l+;y2efy0=)*)p;_@(Oq2caB=LPGMCL!z;y$WLjW84os!3HDku&yX0gY%k#3 zfP49sV-~XWAiBhy`fgJ#?tpqJ5wSWDGNlQ&up>ks^}YV*tP5T(ccG2%eg%5YUaFln zwaApsvSue#pjvjXWPcJ9{fzwfH!&mOrt{>@JsEwl#4CJrSdE6i`=xZ;n_l4wz2SMf zT@c#Xy*sa4MFPL1zKm0sNqEmQo#%`-4ao-HZ|+D`!mF>x#SKkfqf_g^7M|2nY!P#_ ztf{Pk^eJ7fs7x|0O58TB>a7Lb|1-t7D+&C=726k2v07g)6qPk&&c4&;4F2~)c%5t-&xoi zSikfTwVA|<7+(DYU-JzMk8rK0%_4b3EgneGj;D+Ftl^)Ad(qu7a!ku<{egW6+phhA zoV0ujZjA%4LDiF0f@8p!Q^3h2yqR_kqvN>r1gY!>Y0-+muffl+pJ%<$2$UXQ9BCnp z6#qiKCyV(9%j$^=OG85ILM=JVVl*LP9ZUqDk@oek#pq=l*pi-K=Av|C9HOQR#Hp#o z75>JrBpmHxwhDLabE>Wrv6MMRdq^ZuE{+NY!BL1<)T!LtP-c;n3h>TD+L9g9;_VzPXc`Pk8`T$F--gZbCD;&K#1j!ZFywY61NXkrzN_y%Dk-2oId(F%Q zfnnCbEqQO?;H7PY?>Sqb-Sei?GOk+EnRL@bfH9V2$!_QPYW|F~TWv*;E z7+OR~^T6@T(&Z%R^v7QkKT`kOUz~4Wv_B{N9}Q~FaFm^z`3kx?d8kN0;XzBzbp$`)&Jw3uuP8BUcIC?b+#l5?AO#6jdSgTSq0VpKwyg8r=R{wKVjV6w7$+ThqAC}f-~X>V&H%s-^vI0{?Hq++CwttG&{ z=e}|^ieAvRNAiFmk$zaz6iFnH)eg}@1*A!9vGSu$4ym6KV2pTDLJ%Kte{sIL zt_;G@UaM2)!HoiCSz>f{M%m2t$Wpq8OEj!7b3M(WbTQt_g@b+*rdVk8hLz%Ye{Zd= z#xnYnvZ~LCeO@G{LF}bKWWT!n2vjv&?LNC5R z!I3NX&+Qw9v574`4afQk;xFO@?k~>QNcd!lY0qJ5KX23|+pcaxm_>4~WpL0g0nH)W z>o(D?LPI&C#yr%4&m}IqVPY zmXzAbJX4^n1@;}Q1wJZtCP>C>u>*Fj%VT@K<~y7hi~W0rd?tyTJomqNKMPi8{Z}jc z{e@5==4<<+X2@ZFXW<7y{p9pV9_6GpBjm;9`mbRk1OMGO#9zb*++Uop@>23}ob?FR z_hA202bE=1OSSKc?{~N~w zJ!N7l31mB6tm1CB5b-@KTOYfhAuhjWmmKrWCdO9&r_ZmKCci6>ERoyANY}h~(@qo| z2ceq73ldUu@L+VwQB{qLTcBFkUwF8~!dvMQ;sR7`QA(o3HBe zndRL<-!H+l`X= zuq~H}9e=_4EUx1ar}@d4&KXn7C68PdOQUW&G3q@r@tO8@Ww7x@F;H^g7W z2i#wr?}$=`RGp~?`NQi)a_noPu1h(PD)|}fjHQ<#v9Md{O}-0kO_i>6GJZsw`}bN1Nt`FG@jvR! z6=T7A)$^$xa&92=_)bX2pEl9Ugsudvlk~YgiE`cH#1t2H5}i=7F^}1CUK#C_MBU zgD}p@;;mH!Bv0O@D1&)|@LliSBK~QbBz6i0p58bHuXPh5viue4nX`Rg#~D}CB3X@0 zVaufG(-*tyi}-;1i}UTb-`T31beM9Ub>g4B%}tdn&pv9`iy~19dk^U!c}1!!C&cf! zl!Cj0g+hfx3~^QIXel+hN-BOe|J^frAHLKcAudY;$nM6({LG?dMEGMB*Vfr^a(TUi zs*6$)+;yDsV>?E}*wwdRecpTpcq*&3M?W*;BmaJN#9DmqDQPV_4lZAc?Z zqveyCS)N2w{bYlQXdyXE=|->GUkQ8AZ(#Y?6m#iWk{YqhyLzatLqI->Z?^1RuT0uZ0#!-wGDe{Qjo+3sg z|3!X=`~mM9;xFO@?k~=F+Wj|=ii8Fw_wH!b7K=R6y31d2?qUnPe0MEr|7qxR{VX0C7_k-IrC?dx02`VphXY-2{J3zcqK{OvL83DjN+q-V(ii2yBq2L>dm3W zCzjKn4t!q_-Ni)(8y(ukeNT|q%8LK9s%I@VaEdwlMz0t>@^ETCYH=rx`U3ec@-yTQ zc;EiR-~ZtQ?k~>wd^zWq$TT&oT3&ADE=~n{k3xD$>jf^#=tj?h)8Z0z=*SL<+KdG< zvnnuCp{*Ynvsg8}ns{lInN@F|9TcG2nYuJ4Qddy6hkm;>pIlEHo3ULlc_c*XWJqwH zZ{nw)ERic{_a6mW(^aW~GryqfIHTx3mLBrp?_>U}|8$Z|r02FDj944< zmV0fnzBA<0y^On`{`>+aR;QPG>2*ZbGX7Ea@@ldIo)&r!XA>va>LRDIDxw&pFI6d; z1N;}>U9f#o4MUbm!sYH|kQTOFa3rXRI6Aps;W?T|7W8;K8N(kyV+mWZa9s&VH(Xw+ zrJM^+8Pn~b)$KsV_P(ufju()iGM?<15LjGm794iy8bSSr`U3ec@-yTQc;66z5g%}W zalT&c%e0^99;SGtzJJy#+f6szGQM=<*LN}{Hp3R&AWqLSJ~vwVbO<)SJDS|yE=ZA6 z_l}FFhYC1tx030?Bploq1AHP& zXs@qLJU@Q)1Jm{cVbbH@$VI}k&oyWW)(s?G@v`|qQs3fbU?Qi@mXm^Pe)lV4ie zcn80b?7E$b*KVeRy6O~Lpj$K~PO?3fajb?{;~s_uiG6T#|J?Uh{}u?@y!?IWWEW(A zjdohj6$er)tP(5gD`7ILz5JPZF**F}n7{qVOE8WekWn6f17}*zW;(7I0`s)zs!``~ z!dqFQE6s5qTJFc&)vt^tsE<*qOoZhDd)>m8R?1dA*{IS*cRJlBpuGKj4)dKh$DBPi_ocQww}6NJm;T zB^G?#O~u}Gv<_xIN`uj^%e}gNu!7cmsnqs~=vnWcm{IS9M7rkNrU*uAqigEJhT}|h zr=xQ5#jMXH`5D)}(xfT4v-y^UyXrJydZ@8oHt0JDILOL1F3p3H>Y$MoJ5z}6R%>pX zrN@rikd3Y6*GTU#Xm*>jKlC*30w!35#;U;#S%H;`Aa5a_HW?#gA{sZb`)NiOS zkpCh-L;isG4e=N80rwZ@>puNvi|m?X6r1l;!RBlgTKR~H(0&$CO1-o$yoLQR{aiw0 z=$8qhRIZC{Vx5tu)$QK8JFMy>65l!)?PmI5s@W_h+Jcj+IsfB(3Qs3USs%IR{)3I0 z<$mU0>bn5GijTZfvui+x@!2F(+&dz`!T~R$)sw#w&)3g5Hc*JrLw6cg(N)ci&dV@h?v{J2KY-$DNY^)c!<)ECHqk)I)d!25>yi}-;1i}T&teM2^>j-s>; zvVJW2yn%k6Gs%3Waxc~VSuSQUV>kV?RlR!pg#@+QPQzk-dKQU0_*b{RrB{6NW=%2KVx1A#ok+Xe?A{Y!WVeUbQyz5W?AQjecN6Vc?JR1_f!IT zGw&{#kpF^|FUoB4JC{#Vl^iPa_ueC?*#}m}pMOAp|1i&PXSzwI)*3JE>avFq5q^)} zx;hiaZ_h--Ducm%y;E_``v;J7dB>YSTAQFgPI1?o^MM5YGxT@Re?WbV`VI93@?Yd< z$RF^&A^svh;Qr!#Nki@d8-0qpx%rbrPo^-vb;3*T@8*@%z22RL{AV`O)d#ds+~((| zxW8TTT4Bsc{r%~iw9jEJ?H9O1&9$75p1SCKFnF4odbRNki@Mna6geNNJ4d~SsD}r) z{pO*K# zcF6OY4u$J3M`Evgr;|NYysdQcBf`eRSVJGXPiW!Gm1fMB;DF)Vb8c}Ye{y0zDDOQ)j@3pv{ZV)b>xAbO9Ai=j`lhTZ{`PL|@LgzPx-Tub7 zn$mBS_$-`SM^=1pO=de3PB`y;ig_MB3&!V0kDt)zqs?BgW3=BnMq*aYm)$pHriHFL zb^m354Fhwf9Hl#p$diU6M-HoI!-Tp8{Zoi0&nu003JRx#>FCu(6RUg>`_y}A_=O|% zEV;sEX=ej0oV)v&ICQ}GYt)U_^myQ3-IC{VB?_W{EkxKkL_w|DG3UHqXNbM4tfkR+ z3y#HGJva6^0F}X~xcL6z!<|`^tQIi~2v$!`+rsNd%G;&Jqx!u_SC8MfxqTL3W+=QE zVYi;3|BL=4`e*3xp#OmS81)ND3~PT{-UzeVkF{Sa`lJ+&A4lWr2U4K)G3DL zi$QJ=h-G@+pE0!@_+r}9zy4u3$@FBuqM-YT$h?iJz4bK^jJR>j|Dr#M{u%l^=s%!7M*W8R0{JiUGvp6=-w=NhA8>zhz6*C%m6z2XprSS$ zyye0@NVfR0L_CV`hg~LO4QsXx!-0^vET61@ek@}}eJCRjrL0rS(zBP)ho&BKjt>@) zJtzDd7st!t+h3c?wyNjE&%3s(=k5z~)OPgDI?Wu2tfuBH?*{?bv*H=%tY@Tc{W^o+ ziV5WQ(y&Zz$qX_SE`RvuYk$b7c%!lTrzi0hQ#o{zC5mX4e3bK>^di-%?5T~C7YL)) zYPSta)&wq@y^!!XgphrVJ+IdI!RMWWiT)Y-JLo^4K1Th9`U3ec@-yTQc;66z5g%}WalT76duL3Y#i-`&;-5`> zR?|m@HOg0qt)SRew8;2*w~-Blj*9EUX{ZR3VVbagLWb`SUpkjjNw~Yobtj$IQ2*6* z)@k|y(0RrevS7Mnp5_{o&I|5m2rcoc(6uAz9zjtuhfF2Wk~7|1Qf1 zh}dj6v2=4XX;;*Z7~)Wdo9*xUTc@K*rEiLxaFsLs^naO<&S3_a4`F_R{yO@<=ue`5 zhW-xv52%k(zoEWB{)_w!`2*fJ#9zb*++Un;?jOy=Gi#No2b=!LN4Qe7f4#3~S5F2} z<9}}TG366sKG3JW<@P(Ud2=GW>T5G>F?q&%Rkn&qZ9X=u+K>U@V0XJUwUN9tOwX}+ zRtLW{<9n+%my_Z;aqrNG$7I|@>t@gXSn~JF(ybg_>BOR*t5UD8hKQe(-zKx9ntVCE zdeJ+%f^0P3?c4DC9>lc86^tItBj2UW8gyh*;D%58{==P-U}sRzN6*oux?GcG>rPW* zIl1+K*&iq3;c;*}dXFLb&A7DBXHzg>{)YJw<`?L%qyLNkB>HFQ@1XyH`WW>a>I>w* z$j^{J;C(~;)DS4GNZzEew6@gL3oVbw zf?}BCmt?Z5CsyFc=R{b&Yc^`fkvvf2mh9f_kpZ7+=GWJJQ(t%68$suchG-8eT@1I^#$@@Z&_R!nz zLp_L(1-`I*&snznk^0_Km(KCIkuOZc#3c7Rsa;s;?DV(`=|;W!ZLaaelPqnnFS|#k zBDfDYUiBlxe|3WS{ce!9q2JH-mT1BKg%9*QejAc1?Y{Z&QAdc@Iu~uGeiwYRR%?_m zm_UyO%i6z}%^{tos!rhkdH8ksklEyCZNU5(^G(d(FdxGF0{wOLf6<>r{|x;d^dC?k zqkcnuf&3Tw8S)3bZ-~E$54gWLU*>o}oyi_S>dAt7JF-hqQF>r_^YYxBqbw<(`PG&uSyyk^Jw7+Ef z&c^f{X8xWHm0g!aDoahO#TlJRb$592v$5N7l1bYz=ZXzj{5mmQRB;k+HE!V65ZDWE z<2y@JZe9XC+RD`?(u!1SY*vzde3L96Qel6Tbc5`BqutS(c8WYr)$`lfVF#GcV}6YJ zCgyLL4`F_R{yO@<=ue`5hW-xv52%k(zoEWB{)_w!`2*fJ#9zb*++UpU(g2Y5T}4rA zf;tbcWwRqer+RW(Rkwkm+{2KYf}X^`{Eho_h=W1@JFC9)coBu;ql1gboXErMy*@WL zJCc>#I}>!RHHlW`c>T%9GZ0dhZZOU20&YHPF~XcC|M)WhD$bi)p!<0FsDP*~42Hzc zs{gnErk(rbuL*-liTNAmLzrKnzmEPd`jhCNp}&Lv1L|YcZ>TSj{~|v_{($!l z@fYy{_ZR0YIC_k>i!p_?%h0epXo-UL1qa${iEucW5Ru4P%^?JmDW#qMig2+ zp8X&zWLQkv``f{H$F|iJ{T959zZHmwH^W`4W33HFg`~iKd+@5p=j32h)5(^GRFc1> zIWbb$oAf99itY%FB|_{wGy?3ZBw0rPpxk1^lG{0;LV%rDSiNByi}-;1i}N+nOET==BS8g+c5hugAwflp$HcSUoy2%Y-wJ34W!k&p1Ee=2NRcm>q+*PLX;?<77za|FlZC~nfW@2=zV_8E}8d& z)Eesg{$#sGa&k*s!{j4K^{wZry+IK~wRWRYLv&vm*Y9nAtS+jOuPiLG`y;f8%axS+ zbx)te_?N9RVeJpW=kQ(qFW;UL?C)T|1p5z|&tra!`6lLXm=9rof&M!Bzvxe*e}?`J z`VXj&QNN+SK>myT4EY1zH^g7W2i#wrZ=9avIj2!2Ds!1wg!pV1$)Bo$5z|4~5ol0e z6)_HVUZP$O>}|0B>fW44vjX7y!FiGCbrzIrsWYF{zClFa&RgtINFi!%0s{xP1`&&j z^G$PZSKu}0MefD+SQ2zdO(Un{A({7%x2Tl40s$he@xJ2PL_oIcTM2^+Os6$0vwoQf zcc-+Djjl<7;s)-LM@4A_`%&26!F~z$A26TC{2231%-=8{!u$gLb@YGHpG5x*{T=il zP#>dyLw$k#7x@|T2fS~HzlaaGzc}Ag2NND(+dx^e>zZkoi&IyO?|hw3Uq#F9W;nL3 z{w3LKXk!~76H9aq4X4C349GgMf}6f7X++J2!QE(mHt{%6xb(isOR`y$wf)(_Ea<$| zcJSloa#-@qAf(kNgHT4^@B9CF6XDXW*-!SkfmyT4EY1zH^g7W2i#wr@63jBZ7-*76ld@%^9?4F^mNO4<{aY)GH=GcUPs>p+H&?> zd^O}r9t!`|zrPSm&TyFzoro+TCloziem{N=n8 z{V443V7~XMhJm$xkZ({z2`4Hw8=&z&yi~c0~XXx*s|A6`! z^&9F7!@Y@A|;y*vq1(HdYQ>vnxOD=gjdowwcJqHpcKSXWbnMG86EoCLcN=Vrz2Kg-J zCt&}8y)?eO1$sVJzI4!SgRhaCMl0l7|Lgx^KNb6v*zd#s8TO;Fzk~e}>_1>WkNGj? zo0z|0K7{!N`s?WbqCbiL8TvcuKcGHF{f7Di`7iP_WD=Rjx4Utl! zzOdHc&b|H)awM}0tlqYh&2!Vi0bjn8J8Yu*4TAT`WK5~zsS#! zKj3{s{6&1g{l)oOl;-lEage7<4GtFSrf;N8^sL`B3G-1q^QE#iC-TYui#v}hG^7xo zh@}#>n-gJt`={)kiDkgT-sYtKu@!v1QcuZVs3#sB;~B#*o{>rBHn&mpO!9L#uzi_JpMOvc?SAynxKW)~p7@ci^f1Eh@pJ9zKfP_i&hQA|0*T^Q;%{z~hAEvl@zp zL`U_k$|2F41m6c>e;xbP*#E_TD)uL_--rD(>_=gL2m2-1f53bm^JC06F@M8+2=fc{ z*U|q)e-iyO^movIKz)q*4fO@`U*u=VAMm~*{vtl${^ERR4v%EM7F$i}J>K7OHr9&F zbNhUCIhqY&L+XVZQaL2A=(q~|rD~F5b$%D$p++L(l_&xyp>)5Zx{x9}Zu|J9ZKJ1@iKMMOh*e}8U1LpIXA7j3W`5Wd# zm|vj3j{YzDljxtJzk~h*>SNSzs4tNJB0od^fcFjY7x4l27v~$dPk~Q|YX_xbyJkrghsOxFJ;cA1?Ix1Z zk^0X40|ei<;QI@FAAtRJ>{ny|7yGH$pTvG2_Rp{%h5a4umtg+^^LfmVG2g`e4f7$) zFVJ5{{}=s9^v}@WLH_~uG3qze7s!8+pCNz1`-b?7_<;M1^QDdinn9T;wRl~Q!!UuL zGPO7qJG*;ZzXG_#r^V)9k5a~6KUb7kY=ahD!wUn?fDFYwcU?}zYx z3%1My(UNZw84Pt2BCDl zTCf_l=)3Tw18(p9OZRMNq=yHp+O0NygCe=LQ7fXR0pDlg`yqVag6}WzeE{~?v0sh- zU+kx1e-itB*gwO56!v$pUxNJy%;zyb#(Wd=H_V4Hzd(N-{a^Ga(LY0f2mJ@s$Ee>> zUm*WQeun%3?;GMT;sfq4&Q~jU`_(6FvhWr8V8{#kG1MV-* z_ie&9xvoFksYc(g)4Zj;bi_E@#5b?cMB5@_R)%31c6Vj9w=HS@?|mV@f5Z1#_?@Qb?jGT{}=nI*q_9HANJ3%ABFuL?3ZBw0rPpxk1^lG{0;LV%rDSiNByi}-;1i}Rg$r#%vRNQjaZr)n?D{wA@@vK{WI zFw$4WxnvzQ2MNC4#P@~x{te$};rk(c--7Qi@O=RG*RfxX{a@^-Vt*3*eb_(4eiZh1 zuwR1x2h8U&KgN6$^Eb?gFuy>59sOVQC(%Dce+T^s)W@jbP+uVbMSh0-0q+~)FX994 zFV463JL8e`!995Bq1>kHY>A_Dit;fcZS;$Cz(o{)YJw<`?L%qyLNkB>HFQ@1XyH z`WW>a>I>w*$j^{J;C(~!XU7W)A$M`-L-*4jkLVW** z@3Zjz5Wa80_ZRp+0Q>9Mug3l__EWJxiTyt8pJ6`=`#ab#!Ttm0^OzrFzKQu8=0liY zpudj(FZz?{pP|2l{sZb`)NiOSkpCh-L;isG4e=N80rwZ@E14v|kjA7;39vG}Y-&16 zRVb(KI-b6So;6X-6udk^*!-td^&Ynp*40AP9{noVo$BVw(fyV%yGU)_8C(c0!TZwZ zSGdF6r_imJIrHI_%G8~_o8=%Zx2o}qYc>>oT+}n>|MyIzeeB>CzIF6Cr}Kr1sVVqf zxezi|+fNGauHV90nhkD|CH8l_Zo_MVx6^H_f{5l8QMWs%`l0#s{C)lo4m$eUa9@Pw zD3OTlaJ;_F5_AI?$_q-pK)}>+%lMTrVp2NeyL(AA`DpTm_4t8!;=c4o@?Tje@>*SY zL!nY2BuA!{zT9#bm|9IYs1>>Ze z@MnKIV9P*VZREIZ?KlW5JVMvnHFwc6+Y4SV@0cNStClN%kdvgkqNSq1LxB$3xViC0 zz5sR4@@nDDN-6qbYW8l(FpuvP3pzwO=7d; zGl$C`MYFHu<@8|_LBCcAX7Nor@81qxGxrMZDx*MHb4P&J13mI&1!u)x-Y3K^PK|j6 za!H-Rlki2$dXjDz+P2u!1p6H~j!c~T0>vd6m8QxHw98egJ=<6CQD>a~SgvlYfO)M% z&eYLcMEld5oO}P?8@oGL9*a+0LVM?lyzvelgDtg-D-ZY2k(FsYmot7|CyH%KA-v-r zM0swFQ{|HT2zCm*9O_(o8P= z8(au_+ikxqmo&tDD)#)H2q&B0m@hssCb?J3jzY#qLTR75G*RnIuq!8Noo8ZOTaB?*8k}do#6BdoO;QZ4|+r`L(ghZ{;4t?ZF?r2td7A&hEy}x@lrzEG5g|5b1 zI@`lZ#$X1uMzWQN&S{?eer1|u1#tYi9@9r+JT-J*T#%%sdi*=8^}Ard=j7IO{|)ph zW}`>qqf)e9Rk%iqAU`!&p<6s*DofjJs0ly(?iYNJw$#Z}+C(#PZ*NsPq(CD+;Qr!# z>&GqhZr;Xh3fUF+7-QEMX-E^U>dB>(*_{j%Fg|NOSq^YjyOdI$R&`H)ot^jd>n z*D;xmG|%1T{5FvyRDy|~Vx7n|nb=z6xc=lSx;&co#lG4RawP2I@}0+LL1<0EwoCmy zv}^vObLV(?sV+x*jhbr>U?J=hBA&UEo|bEflDRENePH3;G@i>r-zz)7QBz(4YgoUs zNg9k1sg}5%sjC`^VkOI6p7ozddA!vo$Mzp2Do{azh?4T6tIbZv&laVZ|&O2lJE{9HM`adYjgm3N;4|?4G?bSf(J|W$KZsH0OtX=H(*iF5fbhk z3)zf6zGQiJkSkV+(E++2h!wZq;e59oBI*`UXXO$H8oNZVCU>Ti+dOxwQG z>T@C3ht?EK$-L;OX2!2QMfdghd*i`yzv zYJa3=mcy>;(9vMn94;(Ez2I2f}pJ#)}1=r&rU0T|eg! z>s3I=JAYmnhQ_WMh&jO|sQ-@yI7Qo-;@zU)MWu$dXrTaI>Pofs<8l1@3 zM=pPsYHTrVCsQH}@8GF@M@+Gc!kJsQJINVLE zyZf-1ygcv5VsNnd51Xg z-IvI4o4<%eKHj?dM4d0ue7IqiFmoobeb5NouXTdtE*(-7G^>TJCKoQh(n$M{Kj3{s z{6&1g{l)p-e58>2@s2XJBX(Zxe$!_#4G?BuGA&Or%sKip@11}=8Hx1meQluY$hBcA zsSP@n(^4jDYKe&7z_WndI->9GzHRnIEohtd>l;b^BJL|*TIa3!L=GzFzvj5#L9VUK zDbw@kr{CQU4XmhILPvAnJ1Vw}k@gifQP(a_Bo6})`aS*h0USBje-^ov13IghSWicY z&^<$$hi3HFQDw?kg6}5r&_lcGs?8tF1K(PM%?gX-Aa}oGWup2T+S7lz;8Qy{*sxQ7 z!s22*89#KJ?QDMyneSxU%eJlr*pttkaDCuJW-m&AyIA~!Y~RmDrx@4)mt4#y&kIqY z{WU4g;Mr61#!g$k$YvWccNMG=(7p+!M${g|`bRJ}wc6_2X${i7?XXk)=}?lsO>8Er z#^pbLhWr8V8{#kG1MV-*H+*SO`?bx-DCrj6cSaig^o1eL;kfxFbc+Ft{)aqHTF4?f zda2J0?0IG0Bb8fBY;OCsgu+Xp-U^z%vW|z$zd2Dw+yn4s*IHWp&ljk9sO{bK?>imO zIk&gRmR7+|2UVf<3{xaw&D(92&TVALvEwYNVQ)eHqv=hNh+dNKwd+t)RvFp1*Y)D* z`!WHjVafgZvJr1fUF+V_tm_96aFfepO+&MaBBJAGb6#Fu<_)9?crU) z#8_X(@V4V!IBaumYx?3-!tjFWHXVME^l}$@@o78-qt`O~O>7?#do9zs{frkNV$J&Q z&t<*@`7iP_WhV1t_IYKn3mP56VPmlG(<9nygZbnSOu63MxCKi3f z-g4+ret!+D8@kP*BUT2wxpbV1T?6q57V+49whZ1qFyCFW@R z=|*Nh_8n4l3LsxsPybjO5&-Hc%){bOuMwRgH&`a%OF9mxNPOjd0PD=2dX}(z5!4sR zf03Udf57{O_>1`P-~Rq@zDGYb3nZ!>r;?Tng5<>?z_9yhB(+O|+8x4We(O^|xuD~I za6sw<(bFp0dtpUAVZ3{My7=85T3W*Q`O`TTYR>uRFWybdXoqJ$RhxVnXcxUhF2eM3 z%Gdtk1_g-<*fF89up?syt-8l;TSQ%xx%EFX4oUerwbPrsqQK>myT4EY1z zH^g7W2i#wrZ+#Efp@du7l)@v)!i;5dl*`05i>9^HaI~;Hlzq4cb}9?Rv}wKu{T8|6 zFUD)BAX7d5qvxdQh2EYtxydGw+E+|z269kqGdGT?9vKGj)NOs;Cx%Gb*T?np+c{~C z_XDbTBF2e`L0_-jwq6MTJ=I&?Q4REnRIsvB9W-*rRtWDYhisQIn~n|jWMi;tXy%hw zAn~H;=GbU75pFzbXW1i7m$3+SvheSuomYuh)xcVsFIWAgwR;Q%co)9yn2Uvyi(Q+I z*5{JY!I!U+2cN=;lgH08+)oAGedEhmOp;+)R1j06XEU*CWi+bTZM?|(# zotB54Zols)))&48eTu3fE6>_27o4~R58CYKCKFib;LF>c+d@Y{d!wOq?nz#{ba%UY zUB)B~UsW&NpfybxZ}qP6%S$I2N{3?Po{z#IVe_P4yI+zrsSZtzOX+aiwBzLBiW+!Z z>#FQ46As&I4TQc3IKdU^JCdq;`6N3ic>jg`GWfhZ=oFuSArWD3Uu!BOKp%VX=kdw| zYiZ7bFO5wL!gOdd@4e>OXHYlO_?T784bFrF^Gqi{C3^Xues8MP0zZR#~Q#Y+v7+yKJobpMX zyCu1{66^{K*O}~iNLVE|_}MkL!{xHTi8H!~X_@EG>JB<7(B9j+hrg(>QwO<<0!sTW zfl=8_W6QD^WX9@}o0?}LSu+q%e|=E_!{2wXr@BXj?}qHAN3WB>yTJ33P+BF}4}aD+ za7%;lKDV4$93GHx*2zDAtu>Jmr_dvb$Ar2 ze}YuJ_^OeCx3E+EV)D_IpJD9dhtiEUs*QU|&!!lxAYc!rC= z@%JmppX=AWo+(MyHLg?t60(W@8Zxr4Crf}bSjimj%kvPnUU2^I%~wY@_-QbGpgN%Q z&7K^E)ve@X)21!DnGm?!53_yl7?b|xnxHZFxkKUNpBK6Q^oIB$Crvr8V1 z-#hmC@zvYVC^&z0`+HY7ATe^;eCs39FYJBm*4QdKM#c5-OHM(mZI0)dmZS(Bv^W~3 zm6k(XwoO?&zMdt&1^lTh`+kVgFNRP2J;b<~f1}+V2I_mmyT4EY1zH^g7W2i#wrZ)2_2uh3L= z>b`t;fR*4n>Vb2GZo17HN-wL!bw|CFBPU{@BR||X!Y-VEX)z#lS7+8mN~HX>|`rQt3>APU6iF? zdYu>UOWaS(_|?Q6n-HKq{H?MC7@G-S=wcwWz9epzqd&En8X?c-{6Ur6JaDwyaj)h~ zA^ECbG5K9E5t<+IpS%9=yFVvw)vwn|OpqT-B38I(zkNnIE$bXTaA%FPq zzWon>5g%}WalVs}UnamyT4EY1zH^g7W z2i#wr?_f@1-9nZQ)p=9Wq`_O3GM(qX#CLUuoNvCqmzmiRk8XnK`=+W^+GXUPkb=y&b%n&vx4vF= zI0E(=)+j{Uz6S3G7hj>gPU4@}X2uOXw1u75I&_@zi85V8R;k*ewx>60z3 zfnY!m2xJKz&|zr=xd)%!8AnnezTTqUL;E?IJy9RV?Op)v8MApOk7NHFQ@1XyH`WW>a>I>w*$j^{J;C(~nAp_hoFHBXukMPJ4^1Y(=fG^n z?tDjhbll_v^Wp4&-<|sVyXz=TwuCdQPYble@kf@^7rpu+?~Frsg!C9ZDefK{RPKUL zUumP=y=BDi+lK&-Tvw>B^dE0omQ4aLZ2n-;W={I}w+={|wLrnAs?9mv6(nH#mOcN# zTjF?N7q6@G0ErZS_jC8HM!59sOVQC(%Dce+T^s)W@jbP+uVbMSh0- z0q+~)FX994FV5HC@xjXG&k7WiXf(@q!)=s->U>n^FgK+>OkQa$FC*-iB#g(^Vj--A z?M*oK64=hwU;e}%4Xtyh7jtd@y-#ykco{bP@4nSkbg1!ufFZM9sjo&YgrlW?!;(vh zWLZ?Irr@1_-!pR7pVr&pP6So&j_nP!B8$m4dVaQ@A!74EuQfh<5sNsBpNYNpa4gAj z$y>L3F#jm+>7l@FKc>BEn~1G#_JO!zl7c)W!dw?`#2p4=EszsS#!Kj3{s{6&1g{l)q6=Uqt+o~9@ns{dl* zU14gJwIm>1e~-LIIXT-WPdhUd!Y_EQdZRpZn>TkN@&{%#ShO#QY8OA1^}`-}7axaMQ2Nx@R;TDqOu&*VZl?W%XwNzVw@Hrv_d?hGZCw&kKF-59sOVQC(%Dce+T^s z)W@jbP+uVbMSh0-0q+~)FX994FV2@MJE+oiVht6yWsUwfskBAa)4eE&Z#A|py#LS?21MMLYlE0shV zq(rHVjD)15q(nlpqa`aOBda8?+X`7xRtlBKNFhSS&+nY?^Yg!RoO7M)ysqbXyxwlZOEHuc&s{>J?}Vi zDhc-q3Oh!@a6e4~6|~ex7M`TnURa!6!nvKk?7z?(E4r7ME-XC${nc@@b7)~% z6^{$~?#ntn?>a%jM}gk~UjqIC{XF_(^qc74&<~-%K)#Or7x^UeGvqtSAK;JS-{3Ex zf1%HyAMn0m{$f7h{^EQ~#)B$KngtlsHjUDXL;XZlAiVpv=>o>vX=ByLUk~V@M?{zD z!bg+7S%deAZaVQqh0+RwK;bqK=I=~V)5KJ{a2AOZD~l>;#zry za5gPVtBZA|e~YiH+;(!IOCImoXH)J@W>ZJYk{_nzuyxv(-%Csh_&)Gw;G@9rfG+|6 zfPNnRG5SsPZ|H~6Um#yc{)>DP`5E#ZW-?F0G zJicl$yV4R}wgj6JQ}GK5g*MmdaiiQuqs5UVKj^S*xY;FAF{Aul#h4sjuKOuZZt~os zqVx7o-)ci6=4TZpGbgChukO<8(xx=@^()!QET728-iml4;7BxXEGp#wvXRd$QT`dFY(1(>i z(kJuj^DP`5E#ZW$Ep@|ifXK3qtkUN{{ug{I_(|}6;LpHEf!_gN0{#L0Jo;nwo9N%r523$6zK;AC`6Ti) zq`-630Ea;lt;jH3Ut>WjS{9+Jq@^$3D$S0AXA>Tp%0Dlbs27dwl3w;LtfcFja7xMx47w4;>nY>~4wlz$8 z_4i*d&&#nZKDSGl^olcFcf%yw$1{o1#^(p^zda$tGtUk;G)B@Q(wb)cDvema>fKTu z_KL>eIa|K-ZaQhpw~aB5{FmpHSev<}J)Bs!IJuN;dP2dkgRchv3qBS6B=|n?XW*m2 z?|?4>|A2lT{W1DY^l#{g&|e^5NB)a^68RbO9pn%2$MA3P7tp`ZXV4FL-!OkMA8>zh zzQO;Z-Y4EvV9sQIS6SyF%Ek-me+>AOL|q~RJA9PW$QxnzfYuUsvgo+h!XwU=boEBP z!Q_=6>0jZf)s0&w^S=;byTXOOG$?g@_N$u9RAa65N8Nqt6#D_-*TGkV{{^24eiD2i z_%rZP;CH~6fPX+gkNz0_Ci*w@L+CG%uOt6OK8gGc`3~|2_+$7t_zUP?=riaCyl_yJLN98vWb6^wqcFyTs41v;Ji~OB*++D4hCU zLXPe~ddrn#lKYBVv|MFYD0LC#sa6^;C(o`-b{BT@KNA*z?Xo3KtGTE82u*t zH}pg3FOaVz|3yBD{0#XH@(1{1_&4|q=wIkF=m)%Sn7^10xW72xfXI!lwL9gQoUNm_ zt9!?275Az&%kR69mIWEhelde2;Fs69xDE#+VBvMsb7wSloNih1^H@K{eirtJu-}6H z3+xAgUk6_e{ug{I_(|}6;LpHEf!_gN0{#L0Jo;nwo9N%r523$6zK;AC`6Ti)q@^$3D$S0AXA>Tp%0Dlbs_MgA_pZq@^$3D$S0AX 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%0Dlbs27dwl3w;LtfcFja z7xMx47w5~t8Z@n#wPN!39+js@FN~y|3BT2X-#C#2nR`|n6~<1!Z*R*+=0?yZ;XjS^ zZ)8*d1HOIn#!qPHK$YR;@I-MAtmfaBH{g5uuvwYtt;z=%~ zRbR8S4<|zrN1J>1vvic&?c+7hC9*}o_U>Bwf`Xp}-v|B-d=&T{@Fn0M(9fekM!$*v z4gC=M3*_s_f00ijKSREQ`~m(L{tf;D`WN~P`T_47<}c<0?k~=FT;DZ;j;k`wsww$9 zUM^=hT$H%Btd^76BsXn;$@4+F!A#L_JmoL3u+UE|zEV$1b#A8#jcSuTkE3Ug?%qIy zhPMqS)|gUuU%O-$rzx>2a~7_?vyGU`6&ZUc@1WH)))@Gf{n>P?K0j=gwF8OsDl8eg zWlO=Qf}aH62mTCv6!;zRCEy>>&!ay^zlr`0{Sf*K(@CP!x*@YkRE0L(YG}#`m_u`F z<-10;^=SS0;K#H{f24La*0jUIk%IpPp9+2wd>{BT@KNA*z?Xo3KtGTE82u*tH}pg3 zFOaVz|3yBD{0#XH@(1{1_&4|q=wIkF=m)%Sn7^10xW72xe_nZ>`ngk?q1_ib-D3(# z@OZM(XT>tQ?VZhA!z0DSUcaSIYPu$U`dDH4d2LC-SA+irp9+2wd>{BT@KNA* zz?Xo3KtGTE82u*tH}pg3FOaVz|3yBD{0#XH@(1{1_&4|q=wIkF=m)%Sn7^10xW72x zz)f~KQ{@;&Zm(YcZso;HY3g&$X)G^$r|ny3!9_DFD;Xy5`TiJncyZ+7*!6SNQNpK? z5i}&JqPNeivzMYtHP^MW0|n^U=K%#V37e=0m%rjplby8u-w%<8>61&d|Kr!eSA+ir zp9+2wd>{BT@KNA*z?Xo3KtGTE82u*tH}pg3FOaVz|3yBD{0#XH@(1{1_&4|q=wIkF z=m)%Sn7^10xW72x{qnncFHK#@-25lb!~0{HuJSv>f9JFS5qoNJGhyH$vGvamtnN8Q z&mN7rqu6?aL#H$a96!ODVHx$><55f z2VV{T7kn!CN$`E(&%j54-vM6&{sH|w`eXE)=-r_P_}LW0-ldg$+Sq&mXo zni?@X$?P8vnO~OX;lY*l?#r_5M1Hi9? zuLl1MJ{9~V_&)Gw;G@9rfG+|6fPNnRG5SsPZ|H~6Um#yc{)>DP`5E#ZWq8m=SialJ$th+cxpkj|KvplA7=h60sjFtb%2riRBGCx0< zn5-Kj5l-T=<^{bZdgI3$osk||@uWC&@8&+b>xkOgov*w8+i$`C1@;5LuY<1!{|i19 z{3Q53@Mqwo!0&)B0snx09{n-;P4sW*htOXjUq}9nd=mK?@*U(4@W=3P@E6d(&}YyO zc;7I8F&}V$alSdmzt$hlT)`}s;GfpcBgKdqc@4EG4AAvreOvn@2I-<-c|T56bzi}`^2i}U?CamM!0=yIk=J^d47FUu;f zk{#A>U(Bk$y&0-z-AgB~Y?2mv-bdCP0Dc{OHTYlf zso*EU_klkH9|e8~d&yepRe}F%Re}lh({)Ik+ ze!%;N`HT60`-}5k{#5dx^B*N`0KX2t8vHN#RPdAF`@o-pj{?5~z6AUO`g!!n=r_^7p&vqj zfqWhLFY-y`XUKPuKfoWuzrkNX|3aTZKj3}C{Kb60{l)nv9zFJNLRp?U&2@98+vNV< zDxb}j-4c`E^%_zT|6tHZvA>D^LhQd`KMVUq*l)r91@;5LuY<1!{|i19{3Q53@Mqwo z!0&)B0snx09{n-;P4sW*htOXjUq}9nd=mK?@*U(4@W=3P@E6d(&}YyOc;7I8F&}V$ zalVbVhyAzYE@eLUe3MRSmSBwkuHb!=Da~R(7Wq@^$3D$S0AXA>Tp%0Dlbs27dwl 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 D3P99R literal 0 HcmV?d00001 diff --git a/examples/hessian/data/H8C4N2O/set.000/virial.npy b/examples/hessian/data/H8C4N2O/set.000/virial.npy new file mode 100644 index 0000000000000000000000000000000000000000..87954885b1c05ea368674db624c0d19cd8724366 GIT binary patch literal 920 zcmbV{|5FnL0LAA8ZeYh1hEUNlQrzIiB^Wl5_Q31l7zYQE#R`pj%z$+B9D`nO+B;XirT!74dp(b;ljIqJK=|;tNCDfvSq4(SS+~Y zd`M`daJZrxAtjqk$>Gr{G%|%(Clm|WwMXlO9PWQSpM6rq-Q*%ZTfp7y)Qk)od4DR6 zEFu5jMAKDetaRxZIR8A5*FL#}Q;!~P44LlYxe)11pHPdLVD`94;l+fum-NnyR>XAg zI}$ZxM|JAd*25267;C*8HLo{-ke?XN3;Xe`pyEtTlOOw@cmAzp*&vlEF0_ty;>QU; zyQSWTgg>qows`NO%6zV&#OJ_9iqcr+nnRa#u6B?5F{GC5BfY`<$m&+8$|*}&%}7ZP zhz*$ho)ubi>Y(_Be~a?@0Qf&q3^hGV2x&jdVVw0s&vYesjJr{Ilf70}?8j5w3(+CD z3#Pi5gR*nWkSQf1p~Z#wzfRqoa&!^YapQ6;aRKBmMtW|a6K_`rlM>$WB3CsRmHVXz z1lppA-`t7H*Nd6_atkKSHlJg~iU#4Sdd(IOM3P?&Qyd?7<;1$6W(ED*#HV%K>li?3 z;noNT4tn*8=5;5iXGwc4(<%t06^-aqA@|r{@lJH&;;JyStkQs?QNlu-+J>ty5ozte zT0q`oKNS4!2Cl`;^gNvO;gh)VdAijKW?cK-_RYOi2P0218~|Ot8m5l~!2LpYv7Wz# z*!3O1S6}xb*&w|a-25k(McSt${Fsnhcb8;3@M`IkQKnLd;j9b}JJXAfOFKzF4Ej-B zKk)L?Z+Wr9=9rwx_kpS%G}8nod|STAXmlGf+THLFZp;0MnWhv33g*y!bzm*c=s{`1dnw_|Hn>@_ zdBG$Pg6x2`sBIa>1Db-S?SoM5Pa2+8FCbPMX^WA}V>tSzAm_+D#1&_vca5$fvj5JB zuJizuIR^UdkQ+TascA%w8%5iCXHPVHprD+*C4bk1{y?_A>i7*r=h9GiZVb2a{%cmqQEB95uqg~iuC Date: Sun, 29 Sep 2024 16:13:09 +0800 Subject: [PATCH 040/189] Delete examples/hessian/data/H8C4N2O/set.000/tmp Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/data/H8C4N2O/set.000/tmp | 1 - 1 file changed, 1 deletion(-) delete mode 100644 examples/hessian/data/H8C4N2O/set.000/tmp diff --git a/examples/hessian/data/H8C4N2O/set.000/tmp b/examples/hessian/data/H8C4N2O/set.000/tmp deleted file mode 100644 index 8b13789179..0000000000 --- a/examples/hessian/data/H8C4N2O/set.000/tmp +++ /dev/null @@ -1 +0,0 @@ - From e8c4b75b7e71005d83152f9d0216a1683022f3f3 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 16:13:24 +0800 Subject: [PATCH 041/189] Add files via upload Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/data/H8C4N2O/type.raw | 15 +++++++++++++++ examples/hessian/data/H8C4N2O/type_map.raw | 4 ++++ 2 files changed, 19 insertions(+) create mode 100644 examples/hessian/data/H8C4N2O/type.raw create mode 100644 examples/hessian/data/H8C4N2O/type_map.raw diff --git a/examples/hessian/data/H8C4N2O/type.raw b/examples/hessian/data/H8C4N2O/type.raw new file mode 100644 index 0000000000..a6510b1c81 --- /dev/null +++ b/examples/hessian/data/H8C4N2O/type.raw @@ -0,0 +1,15 @@ +0 +0 +0 +2 +2 +0 +3 +1 +1 +1 +1 +1 +1 +1 +1 diff --git a/examples/hessian/data/H8C4N2O/type_map.raw b/examples/hessian/data/H8C4N2O/type_map.raw new file mode 100644 index 0000000000..5d0a0b4b31 --- /dev/null +++ b/examples/hessian/data/H8C4N2O/type_map.raw @@ -0,0 +1,4 @@ +C +H +N +O From af9544e87d999b161e7d3a603192d69c09a977f4 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 16:14:04 +0800 Subject: [PATCH 042/189] Create tmp Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/data/H10C5N2O/set.000/tmp | 1 + 1 file changed, 1 insertion(+) create mode 100644 examples/hessian/data/H10C5N2O/set.000/tmp diff --git a/examples/hessian/data/H10C5N2O/set.000/tmp b/examples/hessian/data/H10C5N2O/set.000/tmp new file mode 100644 index 0000000000..35897b0a3f --- /dev/null +++ b/examples/hessian/data/H10C5N2O/set.000/tmp @@ -0,0 +1 @@ +sas From 4a7ac975ea0cc8774543593ba5ad8f1c329234bb Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 16:14:22 +0800 Subject: [PATCH 043/189] Add files via upload Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/data/H10C5N2O/set.000/box.npy | Bin 0 -> 776 bytes .../hessian/data/H10C5N2O/set.000/coord.npy | Bin 0 -> 4016 bytes .../hessian/data/H10C5N2O/set.000/energy.npy | Bin 0 -> 200 bytes .../hessian/data/H10C5N2O/set.000/force.npy | Bin 0 -> 4016 bytes .../hessian/data/H10C5N2O/set.000/hessian.npy | Bin 0 -> 210080 bytes .../hessian/data/H10C5N2O/set.000/virial.npy | Bin 0 -> 776 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/hessian/data/H10C5N2O/set.000/box.npy create mode 100644 examples/hessian/data/H10C5N2O/set.000/coord.npy create mode 100644 examples/hessian/data/H10C5N2O/set.000/energy.npy create mode 100644 examples/hessian/data/H10C5N2O/set.000/force.npy create mode 100644 examples/hessian/data/H10C5N2O/set.000/hessian.npy create mode 100644 examples/hessian/data/H10C5N2O/set.000/virial.npy diff --git a/examples/hessian/data/H10C5N2O/set.000/box.npy b/examples/hessian/data/H10C5N2O/set.000/box.npy new file mode 100644 index 0000000000000000000000000000000000000000..663e5b7b764049bf55ca04b33d2bc6d5b34a1613 GIT binary patch literal 776 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%2ItrGWItsN4WCJb+Fo<-3(6~rq<%v~C$evMiu!j&VTwpYIRSXQo$`h-O JkUf-}0{}W+E1&=X literal 0 HcmV?d00001 diff --git a/examples/hessian/data/H10C5N2O/set.000/coord.npy b/examples/hessian/data/H10C5N2O/set.000/coord.npy new file mode 100644 index 0000000000000000000000000000000000000000..9fa44dbd217462e54622bc8ae48bcdd16872d9f4 GIT binary patch literal 4016 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 T#N-39r?Z&i5a%mB)(`&!R4W)| literal 0 HcmV?d00001 diff --git a/examples/hessian/data/H10C5N2O/set.000/energy.npy b/examples/hessian/data/H10C5N2O/set.000/energy.npy new file mode 100644 index 0000000000000000000000000000000000000000..94e73c8303210509ce3a9c15dc42b0e05fdb0278 GIT binary patch literal 200 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%2I+{8PwF(pft`i3x+pV0B9N3(DZ;ratkptpuDyrjveBL+NQRP7T$zKPi l3dbV{)Q)%Y?g7%S=Epc%f#M%FoW64hsJ^;(OX77P9{_X2LZbix literal 0 HcmV?d00001 diff --git a/examples/hessian/data/H10C5N2O/set.000/force.npy b/examples/hessian/data/H10C5N2O/set.000/force.npy new file mode 100644 index 0000000000000000000000000000000000000000..b7b378fac63ada7e2ffb4de959b2dcb0916d20f4 GIT binary patch literal 4016 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%0DL*zGCVAtCHx*-7`zc23w#8=JAN)cA^sD-26{O< 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 F{s)Fwez*Vt literal 0 HcmV?d00001 diff --git a/examples/hessian/data/H10C5N2O/set.000/virial.npy b/examples/hessian/data/H10C5N2O/set.000/virial.npy new file mode 100644 index 0000000000000000000000000000000000000000..a216a9d5b7553bd39984c199489950141bab6292 GIT binary patch literal 776 zcmbV{>rc`F07Y??pf$HL&0Y&@^uvZLH66XU+L}(9PBTlIl_VgwQUO;krC6(^H6NI% zsq1Brl}SZ!b!zISG!>*wq$w&G0tHs$Dl4*oq4VvYPxoAkPmW9Ac#w{h@(3PZk}f7T zQ;DqtCc&T*f-G^4n45VpOU&c{^U>U+68@~02)Wt(*-hWXpl%9cP^HxW#+#{1Qloc;9a*Ymy}ES{$9wu{@Lc5QC%E3u)qy^6ALxdA@q5eK}~qeu(t^i2L> zK~up^uYq0-NYN|YQEmqoW~ZD_&vzhVsrk2(V}wjzQd!VcidU@;?irCC0pIFk&)5dB zzyD%xyxoLfG^KXGMTat(E^|Z6XUO!v4OOl|gqN47l87-(hR}kYIU004=1fnSyHRpa z_>{O(3*l=*lU^}~>GsQ8Sr=^RWm^Ij%@c@y!ktQtb6^g?(tEntg1#)D?edG`kSnDU zvEG6scZ0%c2S-3}){YmDhe0i41xHq!u{YJVDqx2Vkt&^61JoiR)^n~2+ikro`e)3b zU!a8PyHv=QrR1Sng~*H_OUx)m&7?RqF;#=-jilki79;MgBr}TM=|SBvy36(DA?p0p z6+<0%obq#DVj2u!`xW&R&HkmESW(iKPB2?0-OL6j_*dmMBH&!dtgVPM)uw{ z+gB`~H`+DVfmWGeZG5N+8xwvuvX$L<86LvphT5RGv4--j-hnhx?dk=4Z16XlI@Hm2 z(A(?#8QFcfpESbCo6w@MJa@gK& Date: Sun, 29 Sep 2024 16:14:45 +0800 Subject: [PATCH 044/189] Delete examples/hessian/data/H10C5N2O/set.000/tmp Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/data/H10C5N2O/set.000/tmp | 1 - 1 file changed, 1 deletion(-) delete mode 100644 examples/hessian/data/H10C5N2O/set.000/tmp diff --git a/examples/hessian/data/H10C5N2O/set.000/tmp b/examples/hessian/data/H10C5N2O/set.000/tmp deleted file mode 100644 index 35897b0a3f..0000000000 --- a/examples/hessian/data/H10C5N2O/set.000/tmp +++ /dev/null @@ -1 +0,0 @@ -sas From 0cd69516d129319ceee63b56b6e802cca1a9d44f Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 16:14:59 +0800 Subject: [PATCH 045/189] Add files via upload Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/data/H10C5N2O/type.raw | 18 ++++++++++++++++++ examples/hessian/data/H10C5N2O/type_map.raw | 4 ++++ 2 files changed, 22 insertions(+) create mode 100644 examples/hessian/data/H10C5N2O/type.raw create mode 100644 examples/hessian/data/H10C5N2O/type_map.raw diff --git a/examples/hessian/data/H10C5N2O/type.raw b/examples/hessian/data/H10C5N2O/type.raw new file mode 100644 index 0000000000..034f24e9c3 --- /dev/null +++ b/examples/hessian/data/H10C5N2O/type.raw @@ -0,0 +1,18 @@ +0 +0 +0 +2 +0 +0 +2 +3 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 diff --git a/examples/hessian/data/H10C5N2O/type_map.raw b/examples/hessian/data/H10C5N2O/type_map.raw new file mode 100644 index 0000000000..5d0a0b4b31 --- /dev/null +++ b/examples/hessian/data/H10C5N2O/type_map.raw @@ -0,0 +1,4 @@ +C +H +N +O From a556e414492381af7e57a4b68c5c2921319b90c2 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 16:16:06 +0800 Subject: [PATCH 046/189] Update input.json Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/single-task/input.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/hessian/single-task/input.json b/examples/hessian/single-task/input.json index bf3ca09aea..19ddc12494 100644 --- a/examples/hessian/single-task/input.json +++ b/examples/hessian/single-task/input.json @@ -79,14 +79,14 @@ "training": { "training_data": { "systems": [ - "/home/admin/workspace/yuzhaoxi/hessian/data/test_sys/H8C4N2O" + "../data/H8C4N2O" ], "batch_size": 1, "_comment": "that's all" }, "validation_data": { "systems": [ - "/home/admin/workspace/yuzhaoxi/hessian/data/test_sys/H10C5N2O" + "../data/H10C5N2O" ], "batch_size": 1, "_comment": "that's all" From f1ed807324dfb7c9716f24991f550062cd1a4427 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 16:20:33 +0800 Subject: [PATCH 047/189] Create input.json Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/multi-task/input.json | 183 +++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 examples/hessian/multi-task/input.json diff --git a/examples/hessian/multi-task/input.json b/examples/hessian/multi-task/input.json new file mode 100644 index 0000000000..2ebbf8e3b1 --- /dev/null +++ b/examples/hessian/multi-task/input.json @@ -0,0 +1,183 @@ +{ + "_comment": "that's all", + "model": { + "shared_dict": { + "type_map_all": [ + "C", + "H", + "N", + "O" + ], + "dpa1_descriptor": { + "type": "dpa1", + "sel": 120, + "rcut_smth": 0.5, + "rcut": 6.0, + "neuron": [ + 25, + 50, + 100 + ], + "tebd_dim": 256, + "axis_neuron": 16, + "type_one_side": true, + "attn": 128, + "attn_layer": 0, + "attn_dotr": true, + "attn_mask": false, + "activation_function": "tanh", + "scaling_factor": 1.0, + "normalize": true, + "temperature": 1.0 + }, + "_comment": "that's all" + }, + "model_dict": { + "H10C5N2O": { + "type_map": [ + "C", + "H", + "N", + "O" + ], + "descriptor": { + "type": "dpa1", + "sel": 120, + "rcut_smth": 0.5, + "rcut": 6.0, + "neuron": [ + 25, + 50, + 100 + ], + "tebd_dim": 256, + "axis_neuron": 16, + "type_one_side": true, + "attn": 128, + "attn_layer": 0, + "attn_dotr": true, + "attn_mask": false, + "activation_function": "tanh", + "scaling_factor": 1.0, + "normalize": true, + "temperature": 1.0 + }, + "fitting_net": { + "neuron": [ + 240, + 240, + 240 + ], + "activation_function": "tanh", + "resnet_dt": true, + "seed": 1, + "_comment": " that's all" + } + }, + "H8C4N2O": { + "type_map": [ + "C", + "H", + "N", + "O" + ], + "descriptor": { + "type": "dpa1", + "sel": 120, + "rcut_smth": 0.5, + "rcut": 6.0, + "neuron": [ + 25, + 50, + 100 + ], + "tebd_dim": 256, + "axis_neuron": 16, + "type_one_side": true, + "attn": 128, + "attn_layer": 0, + "attn_dotr": true, + "attn_mask": false, + "activation_function": "tanh", + "scaling_factor": 1.0, + "normalize": true, + "temperature": 1.0 + }, + "fitting_net": { + "neuron": [ + 240, + 240, + 240 + ], + "activation_function": "tanh", + "resnet_dt": true, + "seed": 1, + "_comment": " that's all" + } + } + } + }, + "learning_rate": { + "type": "exp", + "decay_steps": 20000, + "start_lr": 0.0002, + "stop_lr": 3.51e-08, + "_comment": "that's all" + }, + "loss_dict": { + "H10C5N2O": { + "type": "ener", + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0.02, + "limit_pref_v": 1 + }, + "H8C4N2O": { + "type": "ener", + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0.02, + "limit_pref_v": 1, + "start_pref_h": 10, + "limit_pref_h": 1 + } + }, + "training": { + "model_prob": { + "H10C5N2O": 2.0, + "H8C4N2O": 3.0 + }, + "data_dict": { + "H10C5N2O": { + "training_data": { + "systems": [ + "../data/H10C5N2O/" + ], + "batch_size": 1, + "_comment": "that's all" + } + }, + "H8C4N2O": { + "training_data": { + "systems": [ + "../data/H8C4N2O/" + ], + "batch_size": 1, + "_comment": "that's all" + } + } + }, + "numb_steps": 1, + "warmup_steps": 0, + "gradient_max_norm": 5.0, + "seed": 10, + "disp_file": "lcurve.out", + "disp_freq": 100, + "save_freq": 2000, + "_comment": "that's all" + } +} From f68983fe1bac176305eed367fc5243fe06ba6b45 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 17:18:36 +0800 Subject: [PATCH 048/189] Update env_mat.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/descriptor/env_mat.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deepmd/pt/model/descriptor/env_mat.py b/deepmd/pt/model/descriptor/env_mat.py index e89e7467d3..dc7142249a 100644 --- a/deepmd/pt/model/descriptor/env_mat.py +++ b/deepmd/pt/model/descriptor/env_mat.py @@ -21,10 +21,11 @@ def _make_env_mat( nall = coord.shape[1] mask = nlist >= 0 # nlist = nlist * mask ## this impl will contribute nans in Hessian calculation. - nlist = torch.where(mask, nlist, nall - 1) + nlist = torch.where(mask, nlist, nall) coord_l = coord[:, :natoms].view(bsz, -1, 1, 3) index = nlist.view(bsz, -1).unsqueeze(-1).expand(-1, -1, 3) - coord_r = torch.gather(coord, 1, index) + coord_pad = torch.concat([coord, coord[:, -1:, :] + rcut], dim=1) + coord_r = torch.gather(coord_pad, 1, index) coord_r = coord_r.view(bsz, natoms, nnei, 3) diff = coord_r - coord_l length = torch.linalg.norm(diff, dim=-1, keepdim=True) From fea198e4b3f9191dc278eb7f68ff277b0532be20 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 17:23:16 +0800 Subject: [PATCH 049/189] Update input.json Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/single-task/input.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/hessian/single-task/input.json b/examples/hessian/single-task/input.json index 19ddc12494..ed6e6640b4 100644 --- a/examples/hessian/single-task/input.json +++ b/examples/hessian/single-task/input.json @@ -70,8 +70,8 @@ "limit_pref_e": 1, "start_pref_f": 1000, "limit_pref_f": 1, - "start_pref_v": 10, - "limit_pref_v": 1, + "start_pref_v": 0, + "limit_pref_v": 0, "start_pref_h": 10, "limit_pref_h": 1, "_comment": " that's all" From 19f4879e72eb0a5d329ab073dd83d078408d5bb2 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 17:24:27 +0800 Subject: [PATCH 050/189] Update input.json Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/multi-task/input.json | 32 +++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/examples/hessian/multi-task/input.json b/examples/hessian/multi-task/input.json index 2ebbf8e3b1..d5a68e7b24 100644 --- a/examples/hessian/multi-task/input.json +++ b/examples/hessian/multi-task/input.json @@ -3,10 +3,10 @@ "model": { "shared_dict": { "type_map_all": [ - "C", - "H", - "N", - "O" + "C", + "H", + "N", + "O" ], "dpa1_descriptor": { "type": "dpa1", @@ -35,10 +35,10 @@ "model_dict": { "H10C5N2O": { "type_map": [ - "C", - "H", - "N", - "O" + "C", + "H", + "N", + "O" ], "descriptor": { "type": "dpa1", @@ -76,10 +76,10 @@ }, "H8C4N2O": { "type_map": [ - "C", - "H", - "N", - "O" + "C", + "H", + "N", + "O" ], "descriptor": { "type": "dpa1", @@ -131,8 +131,8 @@ "limit_pref_e": 1, "start_pref_f": 1000, "limit_pref_f": 1, - "start_pref_v": 0.02, - "limit_pref_v": 1 + "start_pref_v": 0, + "limit_pref_v": 0 }, "H8C4N2O": { "type": "ener", @@ -140,8 +140,8 @@ "limit_pref_e": 1, "start_pref_f": 1000, "limit_pref_f": 1, - "start_pref_v": 0.02, - "limit_pref_v": 1, + "start_pref_v": 0, + "limit_pref_v": 0, "start_pref_h": 10, "limit_pref_h": 1 } From 87d765ab29c057d6e472341e70b49abe47498d51 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 17:24:57 +0800 Subject: [PATCH 051/189] Delete examples/hessian/data/H10C5N2O/set.000/virial.npy Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/data/H10C5N2O/set.000/virial.npy | Bin 776 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/hessian/data/H10C5N2O/set.000/virial.npy diff --git a/examples/hessian/data/H10C5N2O/set.000/virial.npy b/examples/hessian/data/H10C5N2O/set.000/virial.npy deleted file mode 100644 index a216a9d5b7553bd39984c199489950141bab6292..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 776 zcmbV{>rc`F07Y??pf$HL&0Y&@^uvZLH66XU+L}(9PBTlIl_VgwQUO;krC6(^H6NI% zsq1Brl}SZ!b!zISG!>*wq$w&G0tHs$Dl4*oq4VvYPxoAkPmW9Ac#w{h@(3PZk}f7T zQ;DqtCc&T*f-G^4n45VpOU&c{^U>U+68@~02)Wt(*-hWXpl%9cP^HxW#+#{1Qloc;9a*Ymy}ES{$9wu{@Lc5QC%E3u)qy^6ALxdA@q5eK}~qeu(t^i2L> zK~up^uYq0-NYN|YQEmqoW~ZD_&vzhVsrk2(V}wjzQd!VcidU@;?irCC0pIFk&)5dB zzyD%xyxoLfG^KXGMTat(E^|Z6XUO!v4OOl|gqN47l87-(hR}kYIU004=1fnSyHRpa z_>{O(3*l=*lU^}~>GsQ8Sr=^RWm^Ij%@c@y!ktQtb6^g?(tEntg1#)D?edG`kSnDU zvEG6scZ0%c2S-3}){YmDhe0i41xHq!u{YJVDqx2Vkt&^61JoiR)^n~2+ikro`e)3b zU!a8PyHv=QrR1Sng~*H_OUx)m&7?RqF;#=-jilki79;MgBr}TM=|SBvy36(DA?p0p z6+<0%obq#DVj2u!`xW&R&HkmESW(iKPB2?0-OL6j_*dmMBH&!dtgVPM)uw{ z+gB`~H`+DVfmWGeZG5N+8xwvuvX$L<86LvphT5RGv4--j-hnhx?dk=4Z16XlI@Hm2 z(A(?#8QFcfpESbCo6w@MJa@gK& Date: Sun, 29 Sep 2024 17:25:14 +0800 Subject: [PATCH 052/189] Delete examples/hessian/data/H8C4N2O/set.000/virial.npy Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/data/H8C4N2O/set.000/virial.npy | Bin 920 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/hessian/data/H8C4N2O/set.000/virial.npy diff --git a/examples/hessian/data/H8C4N2O/set.000/virial.npy b/examples/hessian/data/H8C4N2O/set.000/virial.npy deleted file mode 100644 index 87954885b1c05ea368674db624c0d19cd8724366..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 920 zcmbV{|5FnL0LAA8ZeYh1hEUNlQrzIiB^Wl5_Q31l7zYQE#R`pj%z$+B9D`nO+B;XirT!74dp(b;ljIqJK=|;tNCDfvSq4(SS+~Y zd`M`daJZrxAtjqk$>Gr{G%|%(Clm|WwMXlO9PWQSpM6rq-Q*%ZTfp7y)Qk)od4DR6 zEFu5jMAKDetaRxZIR8A5*FL#}Q;!~P44LlYxe)11pHPdLVD`94;l+fum-NnyR>XAg zI}$ZxM|JAd*25267;C*8HLo{-ke?XN3;Xe`pyEtTlOOw@cmAzp*&vlEF0_ty;>QU; zyQSWTgg>qows`NO%6zV&#OJ_9iqcr+nnRa#u6B?5F{GC5BfY`<$m&+8$|*}&%}7ZP zhz*$ho)ubi>Y(_Be~a?@0Qf&q3^hGV2x&jdVVw0s&vYesjJr{Ilf70}?8j5w3(+CD z3#Pi5gR*nWkSQf1p~Z#wzfRqoa&!^YapQ6;aRKBmMtW|a6K_`rlM>$WB3CsRmHVXz z1lppA-`t7H*Nd6_atkKSHlJg~iU#4Sdd(IOM3P?&Qyd?7<;1$6W(ED*#HV%K>li?3 z;noNT4tn*8=5;5iXGwc4(<%t06^-aqA@|r{@lJH&;;JyStkQs?QNlu-+J>ty5ozte zT0q`oKNS4!2Cl`;^gNvO;gh)VdAijKW?cK-_RYOi2P0218~|Ot8m5l~!2LpYv7Wz# z*!3O1S6}xb*&w|a-25k(McSt${Fsnhcb8;3@M`IkQKnLd;j9b}JJXAfOFKzF4Ej-B zKk)L?Z+Wr9=9rwx_kpS%G}8nod|STAXmlGf+THLFZp;0MnWhv33g*y!bzm*c=s{`1dnw_|Hn>@_ zdBG$Pg6x2`sBIa>1Db-S?SoM5Pa2+8FCbPMX^WA}V>tSzAm_+D#1&_vca5$fvj5JB zuJizuIR^UdkQ+TascA%w8%5iCXHPVHprD+*C4bk1{y?_A>i7*r=h9GiZVb2a{%cmqQEB95uqg~iuC Date: Sun, 29 Sep 2024 17:52:27 +0800 Subject: [PATCH 053/189] Create train-energy-hessian.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/train-energy-hessian.md | 50 +++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 doc/model/train-energy-hessian.md diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md new file mode 100644 index 0000000000..ade6c00242 --- /dev/null +++ b/doc/model/train-energy-hessian.md @@ -0,0 +1,50 @@ +# Fit energy Hessian + +To train a model that takes Hessian matrices, i.e., the second order deriviates of energies w.r.t coordinates as input, you only need to prepare full hessian matries and modify the `loss` section to define the Hessian-specific settings, keeping other sections the same as the normal energy model's input script. + +Note that fitting energy Hessian is only supported in the **PyTorch** backend as for now. + + +## Energy Hessian Loss + +If you want to train with Hessian, you are expected to add the start and limit prefactors of Hessian, i.e.,`start_pref_h` and `limit_pref_h` to the `loss` section in the `input.json`: + +```json + "loss": { + "type": "ener", + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + "start_pref_h": 10, + "limit_pref_h": 1 + }, +``` + +The options `start_pref_e`, `limit_pref_e`, `start_pref_f`, `limit_pref_f`, `start_pref_v`, and `limit_pref_v`, determine the start and limit prefactors of energy, force, and virial, respectively. The calculation and definition of Hessian loss are the same as for the other terms. + +If one does not want to train with virial, then he/she may set the virial prefactors `start_pref_v` and `limit_pref_v` to 0. + + +## Hessian format in PyTorch + +In the PyTorch backend, Hessian matrices are listed in `hessian.npy` files, and the data format may contain the following files: + +``` +type.raw +set.*/box.npy +set.*/coord.npy +set.*/energy.npy +set.*/force.npy +set.*/hessian.npy +``` + +This system contains `Nframes` frames with the same atom number `Natoms`, the total number of element contained in all frames is `Ntypes`. Most files are the same as those in [standard formats](../data/system.md), here we only list the distinct ones: + +| ID | Property | Raw file | Unit | Shape | Description | +| -------------- | ---------------- | ------------- | ------- | --------------------------------------- | ----------------------------------------------------------------- | +| hessian | Hessian matrices | hessian.npy | eV/Å^2 | Nframes \* (Natoms \* 3 \* Natoms \* 3) | Second-order deriviates of energies w.r.t coordinates. | + +Note that the `hessian.npy` should contain the **full** Hessian matrices with shape of `(3Natoms * 3Natoms)` for each frame, rather than the upper or lower triangular matrices with shape of `(3Natoms * (3Natoms + 1) / 2)` for each frame. From c6d651997cb3b74fcbfdf07df1aede86fa122de3 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 22:04:25 +0800 Subject: [PATCH 054/189] Restore tensor.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/tensor.py | 48 ---------------------------------------- 1 file changed, 48 deletions(-) diff --git a/deepmd/pt/loss/tensor.py b/deepmd/pt/loss/tensor.py index 94dad7d0e2..3dcf21af1d 100644 --- a/deepmd/pt/loss/tensor.py +++ b/deepmd/pt/loss/tensor.py @@ -24,7 +24,6 @@ def __init__( label_name: str, pref_atomic: float = 0.0, pref: float = 0.0, - pref_t_f: float=0.0, # anchor added inference=False, **kwargs, ): @@ -42,8 +41,6 @@ def __init__( The prefactor of the weight of atomic loss. It should be larger than or equal to 0. pref : float The prefactor of the weight of global loss. It should be larger than or equal to 0. - pref_t_f : float # anchor added - The prefactor of the weight of first derivative loss of tensor. It should be larger than or equal to 0. inference : bool If true, it will output all losses found in output, ignoring the pre-factors. **kwargs @@ -55,7 +52,6 @@ def __init__( self.label_name = label_name self.local_weight = pref_atomic self.global_weight = pref - self.t_f_weight = pref_t_f # anchor added self.inference = inference assert ( @@ -63,7 +59,6 @@ def __init__( ), "Can not assign negative weight to `pref` and `pref_atomic`" self.has_local_weight = self.local_weight > 0.0 or inference self.has_global_weight = self.global_weight > 0.0 or inference - self.has_t_f_weight = self.t_f_weight > 0.0 or inference # anchor added assert self.has_local_weight or self.has_global_weight, AssertionError( "Can not assian zero weight both to `pref` and `pref_atomic`" ) @@ -95,8 +90,6 @@ def forward(self, input_dict, model, label, natoms, learning_rate=0.0, mae=False del learning_rate, mae loss = torch.zeros(1, dtype=env.GLOBAL_PT_FLOAT_PRECISION, device=env.DEVICE)[0] more_loss = {} - # print(f"--model_pred in forward in tensor.py: {model_pred.keys()}") # anchor - # print(f"--label in forward in tensor.py: {label.keys()}") # anchor if ( self.has_local_weight and self.tensor_name in model_pred @@ -155,37 +148,6 @@ def forward(self, input_dict, model, label, natoms, learning_rate=0.0, mae=False more_loss[f"rmse_global_{self.tensor_name}"] = self.display_if_exist( rmse_global.detach(), find_global ) - if ( - self.has_t_f_weight - and "force" in model_pred - and self.label_name + "_force" in label - ): # anchor added - find_t_f = label.get(f"find_{self.label_name}_force", 0.0) - t_f_weight = self.t_f_weight * find_t_f - t_f_pred = model_pred["force"].reshape( - [-1, natoms, self.tensor_size * 3] - ) - # print(f"--t_f_pred in forward in tensor.py of {t_f_pred.shape}") - t_f_label = label[self.label_name + "_force"].reshape( - [-1, natoms, self.tensor_size * 3] - ) - # print(f"--t_f_label in forward in tensor.py of {t_f_label.shape}") - diff = (t_f_pred - t_f_label).reshape( - -1, self.tensor_size * 3 - ) - # print(f"--diff in forward in tensor.py of {diff.shape}") - if "mask" in model_pred: - diff = diff[model_pred["mask"].reshape([-1]).bool()] - l2_t_f_loss = torch.mean(torch.square(diff)) - if not self.inference: - more_loss[f"l2_{self.tensor_name}_force_loss"] = self.display_if_exist( - l2_t_f_loss.detach(), find_t_f - ) - loss += t_f_weight * l2_t_f_loss - rmse_t_f = l2_t_f_loss.sqrt() - more_loss[f"rmse_{self.tensor_name}_force"] = self.display_if_exist( - rmse_t_f.detach(), find_t_f - ) return model_pred, loss, more_loss @property @@ -212,14 +174,4 @@ def label_requirement(self) -> List[DataRequirementItem]: high_prec=False, ) ) - if self.has_t_f_weight: # anchor - label_requirement.append( - DataRequirementItem( - self.label_name + "_force", - ndof=self.tensor_size*3, - atomic=True, - must=False, - high_prec=False, - ) - ) return label_requirement From 023ce20b2f5aa83ffb553cc4f62acd3b4599093c Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 22:12:07 +0800 Subject: [PATCH 055/189] Update deep_pot.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/infer/deep_pot.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/deepmd/infer/deep_pot.py b/deepmd/infer/deep_pot.py index 61db284815..3a2aedebb2 100644 --- a/deepmd/infer/deep_pot.py +++ b/deepmd/infer/deep_pot.py @@ -22,11 +22,11 @@ ) from deepmd.pt.model.model import ( get_model, -) # anchor added +) class DeepPot(DeepEval): - def __init__(self, *args, **kwargs): # anchor added + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) """Potential energy model. @@ -71,7 +71,7 @@ def output_def(self) -> ModelOutputDef: r_differentiable=True, c_differentiable=True, atomic=True, - r_hessian=True, # anchor added: if type of model is EHM + r_hessian=True, ), ] ) @@ -237,20 +237,20 @@ def eval( virial, atomic_energy, atomic_virial, - # hessian, # anchor added + # hessian, ) else: result = ( energy, force, virial, - # hessian, # anchor added + # hessian, ) if self.deep_eval.get_has_spin(): force_mag = results["energy_derv_r_mag"].reshape(nframes, natoms, 3) mask_mag = results["mask_mag"].reshape(nframes, natoms, 1) result = (*list(result), force_mag, mask_mag) - if "energy_derv_r_derv_r" in list(results.keys()): # anchor added + if "energy_derv_r_derv_r" in list(results.keys()): hessian = results["energy_derv_r_derv_r"].reshape(nframes, 3 * natoms, 3 * natoms) result += (hessian, ) return result From 5166351f5bbe7ea3d7244b8d2caaae257d2a7653 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 22:25:09 +0800 Subject: [PATCH 056/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/infer/deep_eval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/infer/deep_eval.py b/deepmd/infer/deep_eval.py index aeb40ced4d..9433990b71 100644 --- a/deepmd/infer/deep_eval.py +++ b/deepmd/infer/deep_eval.py @@ -79,7 +79,7 @@ class DeepEvalBackend(ABC): # old models in v1 "global_polar": "global_polar", "wfc": "wfc", - "energy_derv_r_derv_r": "hessian", # anchor added + "energy_derv_r_derv_r": "hessian", } @abstractmethod From a0a93b471626e2af85bb6ef55d46fd27562e1c03 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 22:44:16 +0800 Subject: [PATCH 057/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 111 +++++++++---------------------------- 1 file changed, 26 insertions(+), 85 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index 6aa692b8cb..5986942df6 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -46,7 +46,7 @@ ) from deepmd.pt.model.model import ( get_model, -) # anchor added +) if TYPE_CHECKING: from deepmd.infer.deep_tensor import ( @@ -157,7 +157,7 @@ def test( append_detail=(cc != 0), ) elif isinstance(dp, DeepDipole): - err = test_dipole(dp, data, system, numb_test, detail_file, atomic, append_detail=(cc != 0)) # anchor added + err = test_dipole(dp, data, numb_test, detail_file, atomic) elif isinstance(dp, DeepPolar): err = test_polar(dp, data, numb_test, detail_file, atomic=atomic) elif isinstance(dp, DeepGlobalPolar): # should not appear in this new version @@ -246,7 +246,8 @@ def save_txt_file( np.savetxt(fp, data, header=header) -def whether_hessian(dp: "DeepPot"): # anchor created +def whether_hessian(dp: "DeepPot"): + # If model type is EnergyHessianModel, return True and print hessian loss info if "Hessian" in str(type(get_model(dp.deep_eval.input_param))): return True else: @@ -302,7 +303,7 @@ def test_ener( if dp.has_spin: data.add("spin", 3, atomic=True, must=True, high_prec=False) data.add("force_mag", 3, atomic=True, must=False, high_prec=False) - if whether_hessian(dp): # anchor added + if whether_hessian(dp): data.add("hessian", 1, atomic=True, must=True, high_prec=False) test_data = data.get_test() @@ -353,7 +354,7 @@ def test_ener( energy = energy.reshape([numb_test, 1]) force = force.reshape([numb_test, -1]) virial = virial.reshape([numb_test, 9]) - if whether_hessian(dp): # anchor added + if whether_hessian(dp): hessian = ret[-1] hessian = hessian.reshape([numb_test, -1]) if has_atom_ener: @@ -420,9 +421,9 @@ def test_ener( mae_va = mae_v / natoms rmse_va = rmse_v / natoms if whether_hessian(dp): - diff_h = hessian - test_data["hessian"][:numb_test] # anchor added - mae_h = mae(diff_h) # anchor added - rmse_h = rmse(diff_h) # anchor added + diff_h = hessian - test_data["hessian"][:numb_test] + mae_h = mae(diff_h) + rmse_h = rmse(diff_h) if has_atom_ener: diff_ae = test_data["atom_ener"][:numb_test].reshape([-1]) - ae.reshape([-1]) mae_ae = mae(diff_ae) @@ -456,8 +457,8 @@ def test_ener( log.info(f"Atomic ener MAE : {mae_ae:e} eV") log.info(f"Atomic ener RMSE : {rmse_ae:e} eV") if whether_hessian(dp): - log.info(f"Hessian MAE : {mae_h:e} eV/A^2") # anchor added: if EHM - log.info(f"Hessian RMSE : {rmse_h:e} eV/A^2") # anchor added + log.info(f"Hessian MAE : {mae_h:e} eV/A^2") + log.info(f"Hessian RMSE : {rmse_h:e} eV/A^2") if detail_file is not None: detail_path = Path(detail_file) @@ -592,7 +593,7 @@ def test_ener( "rmse_v": (rmse_v, virial.size), "rmse_va": (rmse_va, virial.size), } - if whether_hessian(dp): # anchor added + if whether_hessian(dp): dict_to_return["mae_h"] = (mae_h, hessian.size) dict_to_return["rmse_h"] = (rmse_h, hessian.size) return dict_to_return @@ -622,9 +623,9 @@ def print_ener_sys_avg(avg: Dict[str, float]): log.info(f"Virial RMSE : {avg['rmse_v']:e} eV") log.info(f"Virial MAE/Natoms : {avg['mae_va']:e} eV") log.info(f"Virial RMSE/Natoms : {avg['rmse_va']:e} eV") - if "rmse_h" in avg.keys(): # anchor added - log.info(f"Hessian MAE : {avg['mae_h']:e} eV/A^2") # anchor added - log.info(f"Hessian RMSE : {avg['rmse_h']:e} eV/A^2") # anchor added + if "rmse_h" in avg.keys(): + log.info(f"Hessian MAE : {avg['mae_h']:e} eV/A^2") + log.info(f"Hessian RMSE : {avg['rmse_h']:e} eV/A^2") def test_dos( @@ -815,13 +816,9 @@ def run_test(dp: "DeepTensor", test_data: dict, numb_test: int, test_sys: Deepmd else: box = None atype = test_data["type"][0] - # print(f"test_data in run_test in test.py: {test_data.keys()}") # anchor added - if "find_dipole_force" in test_data.keys(): - if test_data["find_dipole_force"]: - prediction = dp.eval_full(coord, box, atype) - else: - prediction = dp.eval(coord, box, atype).reshape([numb_test, -1]) - return prediction, numb_test, atype + prediction = dp.eval(coord, box, atype) + + return prediction.reshape([numb_test, -1]), numb_test, atype def test_wfc( @@ -1031,11 +1028,9 @@ def print_polar_sys_avg(avg): def test_dipole( dp: "DeepDipole", data: DeepmdData, - system: str, # anchor added numb_test: int, detail_file: Optional[str], atomic: bool, - append_detail: bool = False, # anchor added ) -> Tuple[List[np.ndarray], List[int]]: """Test energy type model. @@ -1065,27 +1060,8 @@ def test_dipole( high_prec=False, type_sel=dp.get_sel_type(), ) - data.add( - "dipole_force", - 9, - atomic=True, - must=False, - high_prec=False, - type_sel=dp.get_sel_type(), - ) # anchor added test_data = data.get_test() - - preds, numb_test, atype = run_test(dp, test_data, numb_test, data) # anchor: dipole --> preds - if "find_dipole_force" in test_data.keys(): # anchor added - if test_data["find_dipole_force"]: - if atomic: - dipole, dipole_force, dipole_virial, atomic_dipole, atomic_dipole_virial = preds - else: - dipole, dipole_force, dipole_virial = preds - rmse_t_f = rmse(dipole_force.reshape(dipole.shape[0], -1) - test_data["dipole_force"][:numb_test]) - else: - dipole = preds - rmse_t_f = None + dipole, numb_test, atype = run_test(dp, test_data, numb_test, data) sel_type = dp.get_sel_type() sel_natoms = 0 @@ -1110,14 +1086,8 @@ def test_dipole( if not atomic: log.info(f"Dipole RMSE/sqrtN : {rmse_fs:e}") log.info(f"Dipole RMSE/N : {rmse_fa:e}") - if rmse_t_f: # anchor added - log.info(f"Dipole Derivative RMSE : {rmse_t_f:e}") log.info("The unit of error is the same as the unit of provided label.") - dict_to_return = {"rmse": (rmse_f, dipole.size)} - if "find_dipole_force" in test_data.keys(): # anchor added - if test_data["find_dipole_force"]: - dict_to_return["rmse_t_f"] = (rmse_t_f, dipole_force.size) if detail_file is not None: detail_path = Path(detail_file) if not atomic: @@ -1128,7 +1098,7 @@ def test_dipole( ), axis=1, ) - header_text = f"{system}: data_x data_y data_z pred_x pred_y pred_z" # anchor inserted system + header_text = "data_x data_y data_z pred_x pred_y pred_z" else: pe = np.concatenate( ( @@ -1140,48 +1110,22 @@ def test_dipole( axis=1, ) header_text = [ - f"{system}: {letter}{number}" # anchor inserted system + f"{letter}{number}" for number in range(1, sel_natoms + 1) for letter in ["data_x", "data_y", "data_z"] ] + [ - f"{system}: {letter}{number}" # anchor inserted system + f"{letter}{number}" for number in range(1, sel_natoms + 1) for letter in ["pred_x", "pred_y", "pred_z"] ] header_text = " ".join(header_text) - # np.savetxt( - # detail_path.with_suffix(".out"), - # pe, - # header=header_text, - # ) # anchor commented out - save_txt_file( + np.savetxt( detail_path.with_suffix(".out"), pe, header=header_text, - append=append_detail, - ) # anchor changed np.davetxt to - # dict_to_return = {"rmse": (rmse_f, dipole.size)} - if "find_dipole_force" in test_data.keys(): # anchor added - if test_data["find_dipole_force"]: - pf = np.concatenate( - ( - np.reshape(test_data["dipole_force"][:numb_test], [-1, 9]), - np.reshape(dipole_force, [-1, 9]) - ), - axis=1, - ) - save_txt_file( - detail_path.with_suffix(".f.out"), - pf, - header=f"{system}: " - f"data_fxx data_fxy data_fxz data_fyx data_fyy data_fyz " - f"data_fzx data_fzy data_fzz pred_fxx pred_fxy pred_fxz " - f"pred_fyx pred_fyy pred_fyz pred_fzx pred_fzy pred_fzz", - append=append_detail, - ) - - return dict_to_return + ) + return {"rmse": (rmse_f, dipole.size)} def print_dipole_sys_avg(avg): @@ -1192,7 +1136,4 @@ def print_dipole_sys_avg(avg): avg : np.ndarray array with summaries """ - log.info(f"Dipole RMSE : {avg['rmse']:e}") # anchor del eV/A - if "rmse_t_f" in avg.keys(): - log.info(f"Dipole Derivative RMSE : {avg['rmse_t_f']:e}") - + log.info(f"Dipole RMSE : {avg['rmse']:e} eV/A") From c8fb85136199e4756e3f75a6c5f608f6d18150f8 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 22:49:25 +0800 Subject: [PATCH 058/189] Update argcheck.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/utils/argcheck.py | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/deepmd/utils/argcheck.py b/deepmd/utils/argcheck.py index e44fa206a9..c994be595c 100644 --- a/deepmd/utils/argcheck.py +++ b/deepmd/utils/argcheck.py @@ -1850,8 +1850,8 @@ def loss_ener(): doc_limit_pref_f = limit_pref("force") doc_start_pref_v = start_pref("virial", abbr="v") doc_limit_pref_v = limit_pref("virial") - doc_start_pref_h = start_pref("hessian", abbr="h") # anchor added - doc_limit_pref_h = limit_pref("hessian") # anchor added + doc_start_pref_h = start_pref("hessian", abbr="h") # prefactor of hessian + doc_limit_pref_h = limit_pref("hessian") doc_start_pref_ae = start_pref("atomic energy", label="atom_ener", abbr="ae") doc_limit_pref_ae = limit_pref("atomic energy") doc_start_pref_pf = start_pref( @@ -1912,14 +1912,14 @@ def loss_ener(): optional=True, default=0.00, doc=doc_start_pref_h, - ), # anchor added + ), Argument( "limit_pref_h", [float, int], optional=True, default=0.00, doc=doc_limit_pref_h, - ), # anchor added + ), Argument( "start_pref_ae", [float, int], @@ -1980,15 +1980,15 @@ def loss_ener(): ] @loss_args_plugin.register("ener_hess") -def loss_ener_hess(): # anchor added +def loss_ener_hess(): # loss of fitting EnergyHessianModel doc_start_pref_e = start_pref("energy", abbr="e") doc_limit_pref_e = limit_pref("energy") doc_start_pref_f = start_pref("force", abbr="f") doc_limit_pref_f = limit_pref("force") doc_start_pref_v = start_pref("virial", abbr="v") doc_limit_pref_v = limit_pref("virial") - doc_start_pref_h = start_pref("hessian", abbr="h") # anchor added - doc_limit_pref_h = limit_pref("hessian") # anchor added + doc_start_pref_h = start_pref("hessian", abbr="h") + doc_limit_pref_h = limit_pref("hessian") doc_start_pref_ae = start_pref("atomic energy", label="atom_ener", abbr="ae") doc_limit_pref_ae = limit_pref("atomic energy") doc_start_pref_pf = start_pref( @@ -2049,14 +2049,14 @@ def loss_ener_hess(): # anchor added optional=True, default=0.00, doc=doc_start_pref_h, - ), # anchor added + ), Argument( "limit_pref_h", [float, int], optional=True, default=0.00, doc=doc_limit_pref_h, - ), # anchor added + ), Argument( "start_pref_ae", [float, int], @@ -2126,8 +2126,8 @@ def loss_ener_spin(): doc_limit_pref_fm = limit_pref("force_magnetic") doc_start_pref_v = start_pref("virial") doc_limit_pref_v = limit_pref("virial") - doc_start_pref_h = start_pref("hessian") # anchor added - doc_limit_pref_h = limit_pref("hessian") # anchor added + doc_start_pref_h = start_pref("hessian") + doc_limit_pref_h = limit_pref("hessian") doc_start_pref_ae = start_pref("atom_ener") doc_limit_pref_ae = limit_pref("atom_ener") doc_start_pref_pf = start_pref("atom_pref") @@ -2197,14 +2197,14 @@ def loss_ener_spin(): optional=True, default=0.00, doc=doc_start_pref_h, - ), # anchor added + ), Argument( "limit_pref_h", [float, int], optional=True, default=0.00, doc=doc_limit_pref_h, - ), # anchor added + ), Argument( "start_pref_ae", [float, int], @@ -2336,13 +2336,6 @@ def loss_tensor(): default=None, doc=doc_local_weight, ), - Argument( - "pref_t_f", - [float, int], - optional=True, - default=None, - doc=doc_global_weight, - ) # anchor added: pref_t_f stands for first-order derivative of tensor ] From 1d117ca98b0aa9d6e018f127d1f55c4707ab2bff Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 22:51:33 +0800 Subject: [PATCH 059/189] Update data.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/utils/data.py | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/deepmd/utils/data.py b/deepmd/utils/data.py index 30408ba295..d626825789 100644 --- a/deepmd/utils/data.py +++ b/deepmd/utils/data.py @@ -479,12 +479,6 @@ def reformat_data_torch(self, data): pass else: if kk in data and self.data_dict[kk]["atomic"]: - # if kk == "hessian": # anchor added - # print( - # f"{kk} original shape is {data[kk].shape}\n" - # f"{kk} is reformated to {data[kk].shape}\n" - # f"{data[kk][:5]}\n" - # ) data[kk] = data[kk].reshape(-1, self.data_dict[kk]["ndof"]) data["atype"] = data["type"] if not self.pbc: @@ -506,9 +500,6 @@ def _load_set(self, set_name: DPPath): assert coord.shape[1] == self.data_dict["coord"]["ndof"] * self.natoms # load keys data = {} - # print(f"data_dict is {self.data_dict.keys()}") # anchor added - # print(f"self.data_dict['hessian'] is {self.data_dict['hessian']}") # anchor added - # print(f"self.data_dict['force'] is {self.data_dict['force']}") # anchor added for kk in self.data_dict.keys(): if self.data_dict[kk]["reduce"] is None: data["find_" + kk], data[kk] = self._load_data( @@ -618,7 +609,6 @@ def _load_data( path = set_name / (key + ".npy") if path.is_file(): data = path.load_numpy().astype(dtype) - # print(f"{path} is loaded") # anchor added try: # YWolfeee: deal with data shape error if atomic: if type_sel is not None: @@ -654,9 +644,8 @@ def _load_data( f"({nframes}, {natoms_sel}, {ndof_}) or" f"({nframes}, {natoms}, {ndof_})" ) - if key == "hessian": # handle hessian data-I; anchor created + if key == "hessian": data = data.reshape(nframes, 3 * natoms, 3 * natoms) - # print(f"hessian reshape before idx_map: {data.shape}") # get idx_map for hessian num_chunks, chunk_size = len(idx_map), 3 idx_map_hess = np.arange(num_chunks * chunk_size) @@ -666,17 +655,12 @@ def _load_data( data = data[:, idx_map_hess, :] data = data[:, :, idx_map_hess] data = data.reshape([nframes, -1]) - # print(f"hessian reshape after idx_map: {data.shape}") ndof = 3 * ndof * 3 * ndof # size of hessian is 3Natoms * 3Natoms - # print(f"n_dof of hessian is {ndof}") else: data = data.reshape([nframes, natoms, -1]) data = data[:, idx_map, :] data = data.reshape([nframes, -1]) data = np.reshape(data, [nframes, ndof]) - # data = np.reshape(data, [nframes, -1]) # anchor modified - # if key == "hessian": # anchor added - # print(f"hessian out if_atomic: {data.shape}") except ValueError as err_message: explanation = "This error may occur when your label mismatch it's name, i.e. you might store global tensor in `atomic_tensor.npy` or atomic tensor in `tensor.npy`." log.error(str(err_message)) @@ -684,8 +668,6 @@ def _load_data( raise ValueError(str(err_message) + ". " + explanation) from err_message if repeat != 1: data = np.repeat(data, repeat).reshape([nframes, -1]) - # if key == "hessian": # anchor added - # print(f"{key} in _load_data: {data.shape}\n") return np.float32(1.0), data elif must: raise RuntimeError(f"{path} not found!") From 570477b005eca5a6119dec82d9bd8b52cf7b7d10 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 22:53:27 +0800 Subject: [PATCH 060/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index 99cae646a6..7d00c64431 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -87,7 +87,7 @@ class DeepEval(DeepEvalBackend): def __init__( self, model_file: str, - output_def: ModelOutputDef, # anchor noted: not callable + output_def: ModelOutputDef, *args: Any, auto_batch_size: Union[bool, int, AutoBatchSize] = True, neighbor_list: Optional["ase.neighborlist.NewPrimitiveNeighborList"] = None, @@ -119,7 +119,7 @@ def __init__( ] = state_dict[item].clone() state_dict = state_dict_head model = get_model(self.input_param).to(DEVICE) - if "Hessian" not in str(type(model)): # anchor added: make_hessian_model is not jitable + if "Hessian" not in str(type(model)): model = torch.jit.script(model) self.dp = ModelWrapper(model) self.dp.load_state_dict(state_dict) @@ -314,7 +314,7 @@ def _get_request_defs(self, atomic: bool) -> List[OutputVariableDef]: OutputVariableCategory.REDU, OutputVariableCategory.DERV_R, OutputVariableCategory.DERV_C_REDU, - OutputVariableCategory.DERV_R_DERV_R, # anchor added + OutputVariableCategory.DERV_R_DERV_R, ) ] @@ -525,7 +525,7 @@ def _get_output_shape(self, odef, nframes, natoms): # Something wrong here? # return [nframes, *shape, natoms, 1] return [nframes, natoms, *odef.shape, 1] - elif odef.category == OutputVariableCategory.DERV_R_DERV_R: # anchor added + elif odef.category == OutputVariableCategory.DERV_R_DERV_R: return [nframes, 3 * natoms, 3 * natoms] # return [nframes, *odef.shape, 3 * natoms, 3 * natoms] else: From a781b169aa065279ff0db482e9929bb8818ba9ec Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 22:55:55 +0800 Subject: [PATCH 061/189] Update training.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/training.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index dd0dca8382..df85a01db6 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -28,7 +28,7 @@ DOSLoss, EnergySpinLoss, EnergyStdLoss, - EnergyHessianStdLoss, # anchor added + EnergyHessianStdLoss, TensorLoss, ) from deepmd.pt.model.model import ( @@ -70,7 +70,7 @@ ) from deepmd.pt.model.model import ( make_hessian_model -) # anchor added +) if torch.__version__.startswith("2"): import torch._dynamo @@ -276,7 +276,7 @@ def get_lr(lr_params): else: self.opt_type, self.opt_param = get_opt_param(training_params) - # anchor added: loss_param_tmp for Hessian activation + # loss_param_tmp for Hessian activation loss_param_tmp = None if not self.multi_task: loss_param_tmp = config["loss"] @@ -1227,7 +1227,7 @@ def get_additional_data_requirement(_model): return additional_data_requirement -def whether_hessian(loss_params): # anchor created +def whether_hessian(loss_params): loss_type = loss_params.get("type", "ener") if loss_type == "ener": if loss_params["start_pref_h"] > 0.0: @@ -1238,7 +1238,7 @@ def whether_hessian(loss_params): # anchor created def get_loss(loss_params, start_lr, _ntypes, _model): loss_type = loss_params.get("type", "ener") - if whether_hessian(loss_params): # anchor added + if whether_hessian(loss_params): loss_params["starter_learning_rate"] = start_lr return EnergyHessianStdLoss(**loss_params) elif loss_type == "ener": @@ -1283,10 +1283,10 @@ def get_single_model( def get_model_for_wrapper( _model_params, - _loss_params=None, # anchor added + _loss_params=None, ): if "model_dict" not in _model_params: - if _loss_params is not None: # anchor added + if _loss_params is not None: if whether_hessian(_loss_params): _model_params["hessian_mode"] = True _model = get_single_model( @@ -1296,13 +1296,13 @@ def get_model_for_wrapper( _model = {} model_keys = list(_model_params["model_dict"]) for _model_key in model_keys: - if _loss_params is not None: # anchor added + if _loss_params is not None: if whether_hessian(_loss_params): _model_params["model_dict"][_model_key]["hessian_mode"] = True _model[_model_key] = get_single_model( _model_params["model_dict"][_model_key], ) - return _model # anchor noted: original return _model only + return _model def model_change_out_bias( From 63da9a4ee61d769fe9dbe8aca97514613abf3653 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 22:59:50 +0800 Subject: [PATCH 062/189] Update ener_hess.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/ener_hess.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deepmd/pt/loss/ener_hess.py b/deepmd/pt/loss/ener_hess.py index b664ade4a9..a43f27888e 100644 --- a/deepmd/pt/loss/ener_hess.py +++ b/deepmd/pt/loss/ener_hess.py @@ -1,5 +1,5 @@ # PDX-License-Identifier: LGPL-3.0-or-later -# anchor created + from typing import ( List, Optional, @@ -20,7 +20,7 @@ from deepmd.utils.data import ( DataRequirementItem, ) -import numpy as np # anchor added +import numpy as np class EnergyHessianStdLoss(TaskLoss): @@ -323,7 +323,7 @@ def forward(self, input_dict, model, label, natoms, learning_rate, mae=False): mae_v = torch.mean(torch.abs(diff_v)) * atom_norm more_loss["mae_v"] = self.display_if_exist(mae_v.detach(), find_virial) - if self.has_h and "hessian" in model_pred and "hessian" in label: # anchor added; sth to be corrected + if self.has_h and "hessian" in model_pred and "hessian" in label: find_hessian = label.get("find_hessian", 0.0) pref_h = pref_h * find_hessian diff_h = label["hessian"].reshape(-1,) - model_pred["hessian"].reshape(-1,) # tbd @@ -397,7 +397,7 @@ def label_requirement(self) -> List[DataRequirementItem]: high_prec=False, ) ) - if self.has_h: # anchor created + if self.has_h: label_requirement.append( DataRequirementItem( "hessian", From 876ccc6d8a4b35e00f6e185c1c856cb544c0ef67 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 23:00:11 +0800 Subject: [PATCH 063/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deepmd/pt/loss/__init__.py b/deepmd/pt/loss/__init__.py index dce279a33e..661fb46dd0 100644 --- a/deepmd/pt/loss/__init__.py +++ b/deepmd/pt/loss/__init__.py @@ -10,7 +10,7 @@ ) from .ener_hess import( EnergyHessianStdLoss, -) # anchor added +) from .ener_spin import ( EnergySpinLoss, ) @@ -24,7 +24,7 @@ __all__ = [ "DenoiseLoss", "EnergyStdLoss", - "EnergyHessianStdLoss", # anchor added + "EnergyHessianStdLoss", "EnergySpinLoss", "TensorLoss", "TaskLoss", From febc0fa9f2384134c647928285a408f620d1ac8f Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 23:02:01 +0800 Subject: [PATCH 064/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/__init__.py | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/deepmd/pt/model/model/__init__.py b/deepmd/pt/model/model/__init__.py index 443dcccc2a..3ac0f856b3 100644 --- a/deepmd/pt/model/model/__init__.py +++ b/deepmd/pt/model/model/__init__.py @@ -44,17 +44,16 @@ ) from .ener_model import ( EnergyModel, - # EnergyHessianModel, # anchor added ) from .ener_hess_model import ( - EnergyHessianModel, # anchor added + EnergyHessianModel, ) from .frozen import ( FrozenModel, ) from .make_hessian_model import ( make_hessian_model, -) # anchor added +) from .make_model import ( make_model, ) @@ -160,10 +159,6 @@ def get_standard_model(model_params): model_params["descriptor"]["ntypes"] = ntypes model_params["descriptor"]["type_map"] = copy.deepcopy(model_params["type_map"]) descriptor = BaseDescriptor(**model_params["descriptor"]) - # print(f"m_p[ds] in get_standard_model: {model_params['descriptor']}") # anchor added - # print(f"len of ds.sl()[ebd][ntw] in get_standard_model: {len(descriptor.serialize()['embeddings']['networks'])}") # anchor added - # print(f"type of descriptor in get_standard_model: {type(descriptor)}") # anchor added - # print(f"descriptor in get_standard_model: {descriptor}") # anchor added # fitting fitting_net = model_params.get("fitting_net", {}) fitting_net["type"] = fitting_net.get("type", "ener") @@ -190,14 +185,9 @@ def get_standard_model(model_params): modelcls = DOSModel elif fitting_net["type"] in ["ener", "direct_force_ener"]: modelcls = EnergyModel - # print("model type is EnergyModel") # anchor added - if "hessian_mode" in model_params.keys(): # anchor added - if model_params["hessian_mode"]: # anchor added + if "hessian_mode" in model_params.keys(): + if model_params["hessian_mode"]: modelcls = EnergyHessianModel - # print("model type is EnergyHessianModel in ener type") - # elif fitting_net["type"] == "ener_hess": # anchor created - # modelcls = EnergyHessianModel - # print("model type is EnergyHessianModel") else: raise RuntimeError(f"Unknown fitting type: {fitting_net['type']}") @@ -208,9 +198,6 @@ def get_standard_model(model_params): atom_exclude_types=atom_exclude_types, pair_exclude_types=pair_exclude_types, ) - # print(f"descriptor type in __init__: {type(descriptor)}") # anchor added - # print(f"fitting type in __init__: {type(fitting)}") # anchor added - # print(f"fitting content in __init__: {fitting}") # anchor added model.model_def_script = json.dumps(model_params_old) return model @@ -229,7 +216,7 @@ def get_model(model_params): "get_model", "DPModelCommon", "EnergyModel", - "EnergyHessianModel", # anchor added + "EnergyHessianModel", "DipoleModel", "PolarModel", "DOSModel", @@ -238,6 +225,6 @@ def get_model(model_params): "SpinEnergyModel", "DPZBLModel", "make_model", - "make_hessian_model", # anchor added + "make_hessian_model", ] From 307b175e540c3ce8c161b9fa458f70e810a0074f Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 23:03:04 +0800 Subject: [PATCH 065/189] Update ener_hess_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/ener_hess_model.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/deepmd/pt/model/model/ener_hess_model.py b/deepmd/pt/model/model/ener_hess_model.py index 9f1c992e5b..f1d4a4f92a 100644 --- a/deepmd/pt/model/model/ener_hess_model.py +++ b/deepmd/pt/model/model/ener_hess_model.py @@ -23,16 +23,16 @@ ) from .make_hessian_model import ( make_hessian_model, -) # anchor added -import numpy as np # anchor added +) +import numpy as np DPEnergyModel_ = make_model(DPEnergyAtomicModel) -DPEnergyModel_ = make_hessian_model(DPEnergyModel_) # anchor added +DPEnergyModel_ = make_hessian_model(DPEnergyModel_) @BaseModel.register("ener_hess") -class EnergyHessianModel(DPModelCommon, DPEnergyModel_): # anchor created +class EnergyHessianModel(DPModelCommon, DPEnergyModel_): model_type = "ener_hess" def __init__( @@ -57,7 +57,7 @@ def translated_output_def(self): output_def["virial"].squeeze(-2) output_def["atom_virial"] = deepcopy(out_def_data["energy_derv_c"]) output_def["atom_virial"].squeeze(-3) - output_def["hessian"] = deepcopy(out_def_data["energy_derv_r_derv_r"]) # anchor added + output_def["hessian"] = deepcopy(out_def_data["energy_derv_r_derv_r"]) if "mask" in out_def_data: output_def["mask"] = deepcopy(out_def_data["mask"]) return output_def @@ -71,7 +71,7 @@ def forward( aparam: Optional[torch.Tensor] = None, do_atomic_virial: bool = False, ) -> Dict[str, torch.Tensor]: - self.requires_hessian("energy") # anchor added + self.requires_hessian("energy") model_ret = self.forward_common( coord, atype, @@ -96,8 +96,8 @@ def forward( model_predict["force"] = model_ret["dforce"] if "mask" in model_ret: model_predict["mask"] = model_ret["mask"] - model_predict["hessian"] = model_ret["energy_derv_r_derv_r"] # anchor added squeeze? - model_predict["hessian"].squeeze(-2) # anchor added: no need to squeeze + model_predict["hessian"] = model_ret["energy_derv_r_derv_r"] + model_predict["hessian"].squeeze(-2) else: model_predict = model_ret model_predict["updated_coord"] += coord @@ -143,4 +143,3 @@ def forward_lower( else: model_predict = model_ret return model_predict - From 9ea1a6fdfb8e45a5cf46724c47fc81948c343827 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 29 Sep 2024 23:07:19 +0800 Subject: [PATCH 066/189] Update make_hessian_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/make_hessian_model.py | 27 ++++----------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/deepmd/pt/model/model/make_hessian_model.py b/deepmd/pt/model/model/make_hessian_model.py index ac3517e4f4..201119dc95 100644 --- a/deepmd/pt/model/model/make_hessian_model.py +++ b/deepmd/pt/model/model/make_hessian_model.py @@ -15,20 +15,6 @@ ) -def compute_hessian(func, inputs): # anchor created - device = torch.device('cuda:0') - inputs.to(device) - inputs = inputs.requires_grad_(True) - y = func(inputs) - grads = torch.autograd.grad(y, inputs, create_graph=True)[0] - n = len(inputs) - hessian = torch.zeros(n, n, device=device) - for i in range(n): - grad2 = torch.autograd.grad(grads[i], inputs, retain_graph=True, create_graph=True)[0] - hessian[i] = grad2 - return hessian - - def make_hessian_model(T_Model): """Make a model that can compute Hessian. @@ -188,17 +174,14 @@ def _cal_hessian_one_component( # fparam: Optional[torch.Tensor] = None, # nfp # aparam: Optional[torch.Tensor] = None, # (nloc x nap) wc = wrapper_class_forward_energy(self, ci, atype, box, fparam, aparam) - # hess = torch.autograd.functional.hessian( - # wc, - # coord, - # create_graph=False, - # # create_graph=True, # anchor changed to: FloatingPointError when nopbc - # ) - hess = compute_hessian(wc, coord) # anchor trying: identical to t.ag.f.hessian + hess = torch.autograd.functional.hessian( + wc, + coord, + create_graph=True, + ) return hess class wrapper_class_forward_energy: - # torch.autograd.set_detect_anomaly(True) # anchor added def __init__( self, obj: CM, From 0a9a0efbd1d02dbdc2f56066fb758ba02d3b1151 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Mon, 30 Sep 2024 00:19:13 +0800 Subject: [PATCH 067/189] Update train-energy-hessian.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/train-energy-hessian.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md index ade6c00242..b3d7b682df 100644 --- a/doc/model/train-energy-hessian.md +++ b/doc/model/train-energy-hessian.md @@ -1,6 +1,6 @@ # Fit energy Hessian -To train a model that takes Hessian matrices, i.e., the second order deriviates of energies w.r.t coordinates as input, you only need to prepare full hessian matries and modify the `loss` section to define the Hessian-specific settings, keeping other sections the same as the normal energy model's input script. +To train a model that takes Hessian matrices, i.e., the second order derivates of energies w.r.t coordinates as input, you only need to prepare full hessian matrices and modify the `loss` section to define the Hessian-specific settings, keeping other sections the same as the normal energy model's input script. Note that fitting energy Hessian is only supported in the **PyTorch** backend as for now. @@ -45,6 +45,6 @@ This system contains `Nframes` frames with the same atom number `Natoms`, the to | ID | Property | Raw file | Unit | Shape | Description | | -------------- | ---------------- | ------------- | ------- | --------------------------------------- | ----------------------------------------------------------------- | -| hessian | Hessian matrices | hessian.npy | eV/Å^2 | Nframes \* (Natoms \* 3 \* Natoms \* 3) | Second-order deriviates of energies w.r.t coordinates. | +| hessian | Hessian matrices | hessian.npy | eV/Å^2 | Nframes \* (Natoms \* 3 \* Natoms \* 3) | Second-order derivates of energies w.r.t coordinates. | Note that the `hessian.npy` should contain the **full** Hessian matrices with shape of `(3Natoms * 3Natoms)` for each frame, rather than the upper or lower triangular matrices with shape of `(3Natoms * (3Natoms + 1) / 2)` for each frame. From 1df1329330cb333110547817df3ed56edf02eb19 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Mon, 30 Sep 2024 01:24:11 +0800 Subject: [PATCH 068/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/pt/model/model/__init__.py b/deepmd/pt/model/model/__init__.py index 3ac0f856b3..03d7aeb191 100644 --- a/deepmd/pt/model/model/__init__.py +++ b/deepmd/pt/model/model/__init__.py @@ -1,4 +1,4 @@ -# PDX-License-Identifier: LGPL-3.0-or-later +# SPDX-License-Identifier: LGPL-3.0-or-later """The model that takes the coordinates, cell and atom types as input and predicts some property. The models are automatically generated from atomic models by the `deepmd.dpmodel.make_model` method. From 0880d2bc699aff43f51ba5efd874fcd8f6a4e0a5 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Mon, 30 Sep 2024 01:29:21 +0800 Subject: [PATCH 069/189] Update train-energy-hessian.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/train-energy-hessian.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md index b3d7b682df..ba6f473025 100644 --- a/doc/model/train-energy-hessian.md +++ b/doc/model/train-energy-hessian.md @@ -1,13 +1,13 @@ # Fit energy Hessian -To train a model that takes Hessian matrices, i.e., the second order derivates of energies w.r.t coordinates as input, you only need to prepare full hessian matrices and modify the `loss` section to define the Hessian-specific settings, keeping other sections the same as the normal energy model's input script. +To train a model that takes Hessian matrices, i.e., the second order derivatives of energies w.r.t coordinates as input, you only need to prepare full Hessian matrices and modify the `loss` section to define the Hessian-specific settings, keeping other sections the same as the normal energy model's input script. -Note that fitting energy Hessian is only supported in the **PyTorch** backend as for now. +Note that fitting energy Hessians is only supported in the **PyTorch** backend as of now. ## Energy Hessian Loss -If you want to train with Hessian, you are expected to add the start and limit prefactors of Hessian, i.e.,`start_pref_h` and `limit_pref_h` to the `loss` section in the `input.json`: +If you want to train with Hessians, you are expected to add the start and limit prefactors of Hessians, i.e.,`start_pref_h` and `limit_pref_h` to the `loss` section in the `input.json`: ```json "loss": { @@ -25,7 +25,7 @@ If you want to train with Hessian, you are expected to add the start and limit p The options `start_pref_e`, `limit_pref_e`, `start_pref_f`, `limit_pref_f`, `start_pref_v`, and `limit_pref_v`, determine the start and limit prefactors of energy, force, and virial, respectively. The calculation and definition of Hessian loss are the same as for the other terms. -If one does not want to train with virial, then he/she may set the virial prefactors `start_pref_v` and `limit_pref_v` to 0. +If one does not want to train with virial, they may set the virial prefactors `start_pref_v` and `limit_pref_v` to 0. ## Hessian format in PyTorch @@ -41,10 +41,10 @@ set.*/force.npy set.*/hessian.npy ``` -This system contains `Nframes` frames with the same atom number `Natoms`, the total number of element contained in all frames is `Ntypes`. Most files are the same as those in [standard formats](../data/system.md), here we only list the distinct ones: +This system contains `Nframes` frames with the same atom number `Natoms`, the total number of elements contained in all frames is `Ntypes`. Most files are the same as those in [standard formats](../data/system.md), here we only list the distinct ones: | ID | Property | Raw file | Unit | Shape | Description | | -------------- | ---------------- | ------------- | ------- | --------------------------------------- | ----------------------------------------------------------------- | -| hessian | Hessian matrices | hessian.npy | eV/Å^2 | Nframes \* (Natoms \* 3 \* Natoms \* 3) | Second-order derivates of energies w.r.t coordinates. | +| hessian | Hessian matrices | hessian.npy | eV/Å^2 | Nframes \* (Natoms \* 3 \* Natoms \* 3) | Second-order derivatives of energies w.r.t coordinates. | Note that the `hessian.npy` should contain the **full** Hessian matrices with shape of `(3Natoms * 3Natoms)` for each frame, rather than the upper or lower triangular matrices with shape of `(3Natoms * (3Natoms + 1) / 2)` for each frame. From 3a185ea924ccb1efabbf2c1cbce70fb0eb5db847 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Mon, 30 Sep 2024 01:42:56 +0800 Subject: [PATCH 070/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/pt/loss/__init__.py b/deepmd/pt/loss/__init__.py index 661fb46dd0..fa286122ff 100644 --- a/deepmd/pt/loss/__init__.py +++ b/deepmd/pt/loss/__init__.py @@ -1,4 +1,4 @@ -# PDX-License-Identifier: LGPL-3.0-or-later +# SPDX-License-Identifier: LGPL-3.0-or-later from .denoise import ( DenoiseLoss, ) From e4f6c0de6ca14f48bf819fec3252eab13deda8ee Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Mon, 30 Sep 2024 21:45:41 +0800 Subject: [PATCH 071/189] Update ener_hess_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/ener_hess_model.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deepmd/pt/model/model/ener_hess_model.py b/deepmd/pt/model/model/ener_hess_model.py index f1d4a4f92a..d511a47fc0 100644 --- a/deepmd/pt/model/model/ener_hess_model.py +++ b/deepmd/pt/model/model/ener_hess_model.py @@ -24,7 +24,6 @@ from .make_hessian_model import ( make_hessian_model, ) -import numpy as np DPEnergyModel_ = make_model(DPEnergyAtomicModel) From ecbb8da18c987f12906780f80275f7a57cdc5eb0 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Thu, 3 Oct 2024 08:58:26 +0800 Subject: [PATCH 072/189] Update deep_pot.py Remove unused import `get_model` Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/infer/deep_pot.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/deepmd/infer/deep_pot.py b/deepmd/infer/deep_pot.py index 3a2aedebb2..8830e7bc05 100644 --- a/deepmd/infer/deep_pot.py +++ b/deepmd/infer/deep_pot.py @@ -20,9 +20,6 @@ from .deep_eval import ( DeepEval, ) -from deepmd.pt.model.model import ( - get_model, -) class DeepPot(DeepEval): From 8654f4986153b365f8ffe38e3edf501310c27726 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Thu, 3 Oct 2024 09:01:34 +0800 Subject: [PATCH 073/189] Update deepmd/entrypoints/test.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index 5986942df6..31fe0d54eb 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -248,10 +248,9 @@ def save_txt_file( def whether_hessian(dp: "DeepPot"): # If model type is EnergyHessianModel, return True and print hessian loss info - if "Hessian" in str(type(get_model(dp.deep_eval.input_param))): - return True - else: - return False +def whether_hessian(dp: "DeepPot"): + # If model type is EnergyHessianModel, return True and print hessian loss info + return "Hessian" in str(type(get_model(dp.deep_eval.input_param))) def test_ener( From af869148f151ebabfa240c37dc886f41e5605d54 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Fri, 4 Oct 2024 22:02:47 +0800 Subject: [PATCH 074/189] Update training.py Remove unused import make_hessian_model Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/training.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index df85a01db6..f867c539df 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -68,9 +68,6 @@ from deepmd.utils.data import ( DataRequirementItem, ) -from deepmd.pt.model.model import ( - make_hessian_model -) if torch.__version__.startswith("2"): import torch._dynamo From deb13c3980256f1ac05e347fb46e874dcff291be Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sat, 5 Oct 2024 10:05:08 +0800 Subject: [PATCH 075/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index 31fe0d54eb..3955299393 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -545,8 +545,6 @@ def test_ener( _n_frames_, _n_hessian_ = test_data['hessian'][:numb_test].shape _n_atoms_ = np.int32(np.sqrt(_n_hessian_) / 3) # n_hessian = 3na*3na triu_indices = np.triu_indices(_n_atoms_ * 3) # upper triangle hessian indices - data_h = test_data["hessian"][:numb_test].reshape(-1, _n_atoms_ * _n_atoms_ * 9) - pred_h = hessian.reshape(-1, _n_atoms_ * _n_atoms_ * 9) data_h_triu = test_data["hessian"][:numb_test][:, triu_indices[0] * _n_atoms_ * 3 + triu_indices[1]].reshape(-1, 1) pred_h_triu = hessian[:, triu_indices[0] * _n_atoms_ * 3 + triu_indices[1]].reshape(-1, 1) h = np.concatenate( From 40135ec417525926f6363a0942b5a30a5785a3c2 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sat, 5 Oct 2024 10:14:18 +0800 Subject: [PATCH 076/189] Update deep_pot.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/infer/deep_pot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deepmd/infer/deep_pot.py b/deepmd/infer/deep_pot.py index 8830e7bc05..5f6593f7d8 100644 --- a/deepmd/infer/deep_pot.py +++ b/deepmd/infer/deep_pot.py @@ -184,6 +184,8 @@ def eval( atomic_virial The atomic virial of the system, in shape (nframes, natoms, 9). Only returned when atomic is True. + hessian + The Hessian matrix of the system, in shape (nframes, 3 * natoms, 3 * natoms). Returned when available. """ # This method has been used by: # documentation python.md @@ -234,14 +236,12 @@ def eval( virial, atomic_energy, atomic_virial, - # hessian, ) else: result = ( energy, force, virial, - # hessian, ) if self.deep_eval.get_has_spin(): force_mag = results["energy_derv_r_mag"].reshape(nframes, natoms, 3) From 3ef3891f11a1d56e56fe308bf43335f13ec3c0bf Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sat, 5 Oct 2024 10:15:45 +0800 Subject: [PATCH 077/189] Update ener_hess.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/ener_hess.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deepmd/pt/loss/ener_hess.py b/deepmd/pt/loss/ener_hess.py index a43f27888e..aa4852f7ab 100644 --- a/deepmd/pt/loss/ener_hess.py +++ b/deepmd/pt/loss/ener_hess.py @@ -20,7 +20,6 @@ from deepmd.utils.data import ( DataRequirementItem, ) -import numpy as np class EnergyHessianStdLoss(TaskLoss): From 4bcac3df2fbd6660721a803df66298d51ae3d413 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sat, 5 Oct 2024 10:21:34 +0800 Subject: [PATCH 078/189] Update wrapper.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/wrapper.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deepmd/pt/train/wrapper.py b/deepmd/pt/train/wrapper.py index 5b2576e63e..6bc7cdc87a 100644 --- a/deepmd/pt/train/wrapper.py +++ b/deepmd/pt/train/wrapper.py @@ -194,4 +194,3 @@ def get_extra_state(self) -> Dict: "train_infos": self.train_infos, } return state - From 86f6d4341d682280be2fe9c8c7565901dd51eab6 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sat, 5 Oct 2024 10:25:38 +0800 Subject: [PATCH 079/189] Update test_dp_hessian_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- source/tests/pt/model/test_dp_hessian_model.py | 1 - 1 file changed, 1 deletion(-) diff --git a/source/tests/pt/model/test_dp_hessian_model.py b/source/tests/pt/model/test_dp_hessian_model.py index fdadac59e5..cb14ed29ae 100644 --- a/source/tests/pt/model/test_dp_hessian_model.py +++ b/source/tests/pt/model/test_dp_hessian_model.py @@ -2,7 +2,6 @@ import unittest import numpy as np -import torch from deepmd.dpmodel.descriptor import DescrptSeA as DPDescrptSeA from deepmd.dpmodel.fitting import EnergyFittingNet as DPEnergyFittingNet From 9522e8b6e6745b3a0a2c56aa2e0cacbfa0a3d33a Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sat, 5 Oct 2024 10:27:15 +0800 Subject: [PATCH 080/189] Update test_dp_hessian_model.py Remove unused imports Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- source/tests/pt/model/test_dp_hessian_model.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/source/tests/pt/model/test_dp_hessian_model.py b/source/tests/pt/model/test_dp_hessian_model.py index cb14ed29ae..898c8fe9e9 100644 --- a/source/tests/pt/model/test_dp_hessian_model.py +++ b/source/tests/pt/model/test_dp_hessian_model.py @@ -3,9 +3,6 @@ import numpy as np -from deepmd.dpmodel.descriptor import DescrptSeA as DPDescrptSeA -from deepmd.dpmodel.fitting import EnergyFittingNet as DPEnergyFittingNet -from deepmd.dpmodel.model.ener_model import EnergyModel as DPEnergyModel from deepmd.pt.model.descriptor.se_a import ( DescrptSeA, ) @@ -24,9 +21,6 @@ to_torch_tensor, ) -from ...seed import ( - GLOBAL_SEED, -) from .test_env_mat import ( TestCaseSingleFrameWithNlist, TestCaseSingleFrameWithoutNlist, From 73ac324081610f8c2c70fdb39241fe9961c6fb95 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sat, 5 Oct 2024 10:28:07 +0800 Subject: [PATCH 081/189] Update test_dp_hessian_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- source/tests/pt/model/test_dp_hessian_model.py | 1 - 1 file changed, 1 deletion(-) diff --git a/source/tests/pt/model/test_dp_hessian_model.py b/source/tests/pt/model/test_dp_hessian_model.py index 898c8fe9e9..b493c18698 100644 --- a/source/tests/pt/model/test_dp_hessian_model.py +++ b/source/tests/pt/model/test_dp_hessian_model.py @@ -22,7 +22,6 @@ ) from .test_env_mat import ( - TestCaseSingleFrameWithNlist, TestCaseSingleFrameWithoutNlist, ) From 260d07c4f096e9b6449d27dd76aa07876484139f Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sat, 5 Oct 2024 10:32:47 +0800 Subject: [PATCH 082/189] Update training.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/training.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index f867c539df..4c8ff8dcaf 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -1226,11 +1226,7 @@ def get_additional_data_requirement(_model): def whether_hessian(loss_params): loss_type = loss_params.get("type", "ener") - if loss_type == "ener": - if loss_params["start_pref_h"] > 0.0: - return True - else: - return False + return loss_type == "ener" and loss_params.get("start_pref_h", 0.0) > 0.0 def get_loss(loss_params, start_lr, _ntypes, _model): From a41c0f45f494edf6cfad6e9dba1466a7af3599e4 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sat, 5 Oct 2024 10:39:00 +0800 Subject: [PATCH 083/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index 3955299393..0c4ba5706e 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -246,8 +246,6 @@ def save_txt_file( np.savetxt(fp, data, header=header) -def whether_hessian(dp: "DeepPot"): - # If model type is EnergyHessianModel, return True and print hessian loss info def whether_hessian(dp: "DeepPot"): # If model type is EnergyHessianModel, return True and print hessian loss info return "Hessian" in str(type(get_model(dp.deep_eval.input_param))) From 4879f4cfbe23c10a998232bc386795b314a1899a Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sat, 5 Oct 2024 10:55:44 +0800 Subject: [PATCH 084/189] Update input.json Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/single-task/input.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/hessian/single-task/input.json b/examples/hessian/single-task/input.json index ed6e6640b4..8d7d7c03a3 100644 --- a/examples/hessian/single-task/input.json +++ b/examples/hessian/single-task/input.json @@ -91,7 +91,7 @@ "batch_size": 1, "_comment": "that's all" }, - "numb_steps": 1, + "numb_steps": 1000000, "warmup_steps": 0, "gradient_max_norm": 5.0, "seed": 10, From 23fae86e0df1233648e2e6a2036de9c55ffa844c Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sat, 5 Oct 2024 10:57:42 +0800 Subject: [PATCH 085/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index 0c4ba5706e..7e3b8550cf 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -547,8 +547,6 @@ def test_ener( pred_h_triu = hessian[:, triu_indices[0] * _n_atoms_ * 3 + triu_indices[1]].reshape(-1, 1) h = np.concatenate( ( - # np.reshape(test_data["hessian"][:numb_test], [-1, 1]), - # np.reshape(hessian, [-1, 1]), data_h_triu, pred_h_triu, ), From 868f69f04afda9a06fcf8c6611b94748d6e7f68e Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sat, 5 Oct 2024 11:07:51 +0800 Subject: [PATCH 086/189] Update ener_hess.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/ener_hess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/pt/loss/ener_hess.py b/deepmd/pt/loss/ener_hess.py index aa4852f7ab..2468860278 100644 --- a/deepmd/pt/loss/ener_hess.py +++ b/deepmd/pt/loss/ener_hess.py @@ -427,7 +427,7 @@ def label_requirement(self) -> List[DataRequirementItem]: repeat=3, ) ) - if self.has_gf > 0: + if self.has_gf: label_requirement.append( DataRequirementItem( "drdq", From 54411bd0e7bd8a16b9e5262a37cda0fb1ab02e21 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sat, 5 Oct 2024 12:01:33 +0800 Subject: [PATCH 087/189] Update ener_hess.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/ener_hess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/pt/loss/ener_hess.py b/deepmd/pt/loss/ener_hess.py index 2468860278..6c76e9011d 100644 --- a/deepmd/pt/loss/ener_hess.py +++ b/deepmd/pt/loss/ener_hess.py @@ -1,4 +1,4 @@ -# PDX-License-Identifier: LGPL-3.0-or-later +# SPDX-License-Identifier: LGPL-3.0-or-later from typing import ( List, From 17e62cdf8d584c2cf6a03255207804b81dd2dbcb Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:02:48 +0800 Subject: [PATCH 088/189] Update training.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/training.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index 1e3a5d9ad1..7a67b3204d 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -1286,9 +1286,8 @@ def get_model_for_wrapper( _loss_params=None, ): if "model_dict" not in _model_params: - if _loss_params is not None: - if whether_hessian(_loss_params): - _model_params["hessian_mode"] = True + if _loss_params is not None and whether_hessian(_loss_params): + _model_params["hessian_mode"] = True _model = get_single_model( _model_params, ) @@ -1296,9 +1295,8 @@ def get_model_for_wrapper( _model = {} model_keys = list(_model_params["model_dict"]) for _model_key in model_keys: - if _loss_params is not None: - if whether_hessian(_loss_params): - _model_params["model_dict"][_model_key]["hessian_mode"] = True + if _loss_params is not None and whether_hessian(_loss_params): + _model_params["model_dict"][_model_key]["hessian_mode"] = True _model[_model_key] = get_single_model( _model_params["model_dict"][_model_key], ) From 776c2ef658aa34902efca672274c11a68c85e79f Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:04:08 +0800 Subject: [PATCH 089/189] Update training.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/training.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index 7a67b3204d..3ea54a0bc7 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -278,8 +278,7 @@ def get_lr(lr_params): if not self.multi_task: loss_param_tmp = config["loss"] else: - for model_key in self.model_keys: - loss_param_tmp = config["loss_dict"][model_key] + loss_param_tmp = {model_key: config["loss_dict"][model_key] for model_key in self.model_keys} # Model dp_random.seed(training_params["seed"]) From ee926a83fa7b25484aa7013eb40e25b6df342d74 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:28:21 +0800 Subject: [PATCH 090/189] Update argcheck.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/utils/argcheck.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deepmd/utils/argcheck.py b/deepmd/utils/argcheck.py index fdccb96b88..9a5ababc89 100644 --- a/deepmd/utils/argcheck.py +++ b/deepmd/utils/argcheck.py @@ -5,6 +5,7 @@ from typing import ( Callable, Optional, + Union, ) from dargs import ( From e9d1dce5cebe03a6971b02a54b4d13cf4180165b Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:34:01 +0800 Subject: [PATCH 091/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 228 ++++++++++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 1 deletion(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index 8be2901fae..b363cc6367 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -598,4 +598,230 @@ def eval_typeebd(self) -> np.ndarray: def get_model_def_script(self) -> str: """Get model defination script.""" - return self.model_def_script \ No newline at end of file + return self.model_def_script + +# For tests only +def eval_model( + model, + coords: Union[np.ndarray, torch.Tensor], + cells: Optional[Union[np.ndarray, torch.Tensor]], + atom_types: Union[np.ndarray, torch.Tensor, List[int]], + spins: Optional[Union[np.ndarray, torch.Tensor]] = None, + atomic: bool = False, + infer_batch_size: int = 2, + denoise: bool = False, +): + model = model.to(DEVICE) + energy_out = [] + atomic_energy_out = [] + force_out = [] + force_mag_out = [] + virial_out = [] + atomic_virial_out = [] + updated_coord_out = [] + logits_out = [] + err_msg = ( + f"All inputs should be the same format, " + f"but found {type(coords)}, {type(cells)}, {type(atom_types)} instead! " + ) + return_tensor = True + if isinstance(coords, torch.Tensor): + if cells is not None: + assert isinstance(cells, torch.Tensor), err_msg + if spins is not None: + assert isinstance(spins, torch.Tensor), err_msg + assert isinstance(atom_types, torch.Tensor) or isinstance(atom_types, list) + atom_types = torch.tensor(atom_types, dtype=torch.long, device=DEVICE) + elif isinstance(coords, np.ndarray): + if cells is not None: + assert isinstance(cells, np.ndarray), err_msg + if spins is not None: + assert isinstance(spins, np.ndarray), err_msg + assert isinstance(atom_types, np.ndarray) or isinstance(atom_types, list) + atom_types = np.array(atom_types, dtype=np.int32) + return_tensor = False + + nframes = coords.shape[0] + if len(atom_types.shape) == 1: + natoms = len(atom_types) + if isinstance(atom_types, torch.Tensor): + atom_types = torch.tile(atom_types.unsqueeze(0), [nframes, 1]).reshape( + nframes, -1 + ) + else: + atom_types = np.tile(atom_types, nframes).reshape(nframes, -1) + else: + natoms = len(atom_types[0]) + + coord_input = torch.tensor( + coords.reshape([-1, natoms, 3]), dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE + ) + spin_input = None + if spins is not None: + spin_input = torch.tensor( + spins.reshape([-1, natoms, 3]), + dtype=GLOBAL_PT_FLOAT_PRECISION, + device=DEVICE, + ) + has_spin = getattr(model, "has_spin", False) + if callable(has_spin): + has_spin = has_spin() + type_input = torch.tensor(atom_types, dtype=torch.long, device=DEVICE) + box_input = None + if cells is None: + pbc = False + else: + pbc = True + box_input = torch.tensor( + cells.reshape([-1, 3, 3]), dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE + ) + num_iter = int((nframes + infer_batch_size - 1) / infer_batch_size) + + for ii in range(num_iter): + batch_coord = coord_input[ii * infer_batch_size : (ii + 1) * infer_batch_size] + batch_atype = type_input[ii * infer_batch_size : (ii + 1) * infer_batch_size] + batch_box = None + batch_spin = None + if spin_input is not None: + batch_spin = spin_input[ii * infer_batch_size : (ii + 1) * infer_batch_size] + if pbc: + batch_box = box_input[ii * infer_batch_size : (ii + 1) * infer_batch_size] + input_dict = { + "coord": batch_coord, + "atype": batch_atype, + "box": batch_box, + "do_atomic_virial": atomic, + } + if has_spin: + input_dict["spin"] = batch_spin + batch_output = model(**input_dict) + if isinstance(batch_output, tuple): + batch_output = batch_output[0] + if not return_tensor: + if "energy" in batch_output: + energy_out.append(batch_output["energy"].detach().cpu().numpy()) + if "atom_energy" in batch_output: + atomic_energy_out.append( + batch_output["atom_energy"].detach().cpu().numpy() + ) + if "force" in batch_output: + force_out.append(batch_output["force"].detach().cpu().numpy()) + if "force_mag" in batch_output: + force_mag_out.append(batch_output["force_mag"].detach().cpu().numpy()) + if "virial" in batch_output: + virial_out.append(batch_output["virial"].detach().cpu().numpy()) + if "atom_virial" in batch_output: + atomic_virial_out.append( + batch_output["atom_virial"].detach().cpu().numpy() + ) + if "updated_coord" in batch_output: + updated_coord_out.append( + batch_output["updated_coord"].detach().cpu().numpy() + ) + if "logits" in batch_output: + logits_out.append(batch_output["logits"].detach().cpu().numpy()) + else: + if "energy" in batch_output: + energy_out.append(batch_output["energy"]) + if "atom_energy" in batch_output: + atomic_energy_out.append(batch_output["atom_energy"]) + if "force" in batch_output: + force_out.append(batch_output["force"]) + if "force_mag" in batch_output: + force_mag_out.append(batch_output["force_mag"]) + if "virial" in batch_output: + virial_out.append(batch_output["virial"]) + if "atom_virial" in batch_output: + atomic_virial_out.append(batch_output["atom_virial"]) + if "updated_coord" in batch_output: + updated_coord_out.append(batch_output["updated_coord"]) + if "logits" in batch_output: + logits_out.append(batch_output["logits"]) + if not return_tensor: + energy_out = ( + np.concatenate(energy_out) if energy_out else np.zeros([nframes, 1]) + ) + atomic_energy_out = ( + np.concatenate(atomic_energy_out) + if atomic_energy_out + else np.zeros([nframes, natoms, 1]) + ) + force_out = ( + np.concatenate(force_out) if force_out else np.zeros([nframes, natoms, 3]) + ) + force_mag_out = ( + np.concatenate(force_mag_out) + if force_mag_out + else np.zeros([nframes, natoms, 3]) + ) + virial_out = ( + np.concatenate(virial_out) if virial_out else np.zeros([nframes, 3, 3]) + ) + atomic_virial_out = ( + np.concatenate(atomic_virial_out) + if atomic_virial_out + else np.zeros([nframes, natoms, 3, 3]) + ) + updated_coord_out = ( + np.concatenate(updated_coord_out) if updated_coord_out else None + ) + logits_out = np.concatenate(logits_out) if logits_out else None + else: + energy_out = ( + torch.cat(energy_out) + if energy_out + else torch.zeros( + [nframes, 1], dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE + ) + ) + atomic_energy_out = ( + torch.cat(atomic_energy_out) + if atomic_energy_out + else torch.zeros( + [nframes, natoms, 1], dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE + ) + ) + force_out = ( + torch.cat(force_out) + if force_out + else torch.zeros( + [nframes, natoms, 3], dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE + ) + ) + force_mag_out = ( + torch.cat(force_mag_out) + if force_mag_out + else torch.zeros( + [nframes, natoms, 3], dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE + ) + ) + virial_out = ( + torch.cat(virial_out) + if virial_out + else torch.zeros( + [nframes, 3, 3], dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE + ) + ) + atomic_virial_out = ( + torch.cat(atomic_virial_out) + if atomic_virial_out + else torch.zeros( + [nframes, natoms, 3, 3], dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE + ) + ) + updated_coord_out = torch.cat(updated_coord_out) if updated_coord_out else None + logits_out = torch.cat(logits_out) if logits_out else None + if denoise: + return updated_coord_out, logits_out + else: + results_dict = { + "energy": energy_out, + "force": force_out, + "virial": virial_out, + } + if has_spin: + results_dict["force_mag"] = force_mag_out + if atomic: + results_dict["atom_energy"] = atomic_energy_out + results_dict["atom_virial"] = atomic_virial_out + return results_dict From 3c6f47a61a6f8da6dd27d81ca1215536cf5653b2 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Fri, 11 Oct 2024 18:28:42 +0800 Subject: [PATCH 092/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index b363cc6367..dbb4f510a9 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -6,6 +6,7 @@ Callable, Optional, Union, + List, ) import numpy as np From 8a8faea9b4bad8775541b01700585576dcedf770 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 13 Oct 2024 15:26:26 +0800 Subject: [PATCH 093/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/deepmd/pt/model/model/__init__.py b/deepmd/pt/model/model/__init__.py index e66eb454c7..72d6b99f5f 100644 --- a/deepmd/pt/model/model/__init__.py +++ b/deepmd/pt/model/model/__init__.py @@ -283,9 +283,8 @@ def get_standard_model(model_params): modelcls = DOSModel elif fitting_net["type"] in ["ener", "direct_force_ener"]: modelcls = EnergyModel - if "hessian_mode" in model_params.keys(): - if model_params["hessian_mode"]: - modelcls = EnergyHessianModel + if "hessian_mode" in model_params.keys() and model_params["hessian_mode"]: + modelcls = EnergyHessianModel elif fitting_net["type"] == "property": modelcls = PropertyModel else: From 206787b484487dcae54d5bf30eedff68f28b2a9d Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 13 Oct 2024 15:33:52 +0800 Subject: [PATCH 094/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/pt/model/model/__init__.py b/deepmd/pt/model/model/__init__.py index 72d6b99f5f..28712263e1 100644 --- a/deepmd/pt/model/model/__init__.py +++ b/deepmd/pt/model/model/__init__.py @@ -283,7 +283,7 @@ def get_standard_model(model_params): modelcls = DOSModel elif fitting_net["type"] in ["ener", "direct_force_ener"]: modelcls = EnergyModel - if "hessian_mode" in model_params.keys() and model_params["hessian_mode"]: + if "hessian_mode" in model_params and model_params["hessian_mode"]: modelcls = EnergyHessianModel elif fitting_net["type"] == "property": modelcls = PropertyModel From d2cb23d9d2dc80440e46426782673d981818daf6 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 13 Oct 2024 16:18:35 +0800 Subject: [PATCH 095/189] Update data.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/utils/data.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/deepmd/utils/data.py b/deepmd/utils/data.py index 27587ff309..a796c8d690 100644 --- a/deepmd/utils/data.py +++ b/deepmd/utils/data.py @@ -266,7 +266,7 @@ def get_batch(self, batch_size: int) -> dict: iterator_1 = self.iterator + batch_size if iterator_1 >= set_size: iterator_1 = set_size - idx = np.arange(self.iterator, iterator_1) + idx = np.arange(self.iterator, iterator_1) # pylint: disable=no-explicit-dtype self.iterator += batch_size ret = self._get_subdata(self.batch_set, idx) return ret @@ -290,7 +290,7 @@ def get_test(self, ntests: int = -1) -> dict: else self.test_set["type"].shape[0] ) # print('ntest', self.test_set['type'].shape[0], ntests, ntests_) - idx = np.arange(ntests_) + idx = np.arange(ntests_) # pylint: disable=no-explicit-dtype ret = self._get_subdata(self.test_set, idx=idx) if self.modifier is not None: self.modifier.modify_data(ret, self) @@ -378,14 +378,14 @@ def _idx_map_sel(self, atom_type, type_sel): new_types.append(ii) new_types = np.array(new_types, dtype=int) natoms = new_types.shape[0] - idx = np.arange(natoms) + idx = np.arange(natoms) # pylint: disable=no-explicit-dtype idx_map = np.lexsort((idx, new_types)) return idx_map def _get_natoms_2(self, ntypes): sample_type = self.atom_type natoms = len(sample_type) - natoms_vec = np.zeros(ntypes).astype(int) + natoms_vec = np.zeros(ntypes).astype(int) # pylint: disable=no-explicit-dtype for ii in range(ntypes): natoms_vec[ii] = np.count_nonzero(sample_type == ii) return natoms, natoms_vec @@ -435,7 +435,7 @@ def _load_test_set(self, shuffle_test: bool): def _shuffle_data(self, data): ret = {} nframes = data["coord"].shape[0] - idx = np.arange(nframes) + idx = np.arange(nframes) # pylint: disable=no-explicit-dtype # the training times of each frame idx = np.repeat(idx, np.reshape(data["numb_copy"], (nframes,))) dp_random.shuffle(idx) @@ -689,7 +689,7 @@ def _load_type_mix(self, set_name: DPPath): def _make_idx_map(self, atom_type): natoms = atom_type.shape[0] - idx = np.arange(natoms) + idx = np.arange(natoms) # pylint: disable=no-explicit-dtype if self.sort_atoms: idx_map = np.lexsort((idx, atom_type)) else: @@ -794,4 +794,3 @@ def __eq__(self, __value: object) -> bool: def __repr__(self) -> str: return f"DataRequirementItem({self.dict})" - From cfcd9f7ab69f7abc7f6cdf0cebcc5e2ccfca58ee Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 13 Oct 2024 16:21:45 +0800 Subject: [PATCH 096/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index dbb4f510a9..b5b7a91dfe 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -462,7 +462,7 @@ def _eval_model( results.append(out) else: shape = self._get_output_shape(odef, nframes, natoms) - results.append(np.full(np.abs(shape), np.nan)) # this is kinda hacky + results.append(np.full(np.abs(shape), np.nan)) # pylint: disable=no-explicit-dtype; this is kinda hack return tuple(results) def _eval_model_spin( @@ -540,7 +540,7 @@ def _eval_model_spin( results.append(out) else: shape = self._get_output_shape(odef, nframes, natoms) - results.append(np.full(np.abs(shape), np.nan)) # this is kinda hacky + results.append(np.full(np.abs(shape), np.nan)) # pylint: disable=no-explicit-dtype; this is kinda hacky return tuple(results) def _get_output_shape(self, odef, nframes, natoms): @@ -741,28 +741,28 @@ def eval_model( if not return_tensor: energy_out = ( np.concatenate(energy_out) if energy_out else np.zeros([nframes, 1]) - ) + ) # pylint: disable=no-explicit-dtype atomic_energy_out = ( np.concatenate(atomic_energy_out) if atomic_energy_out else np.zeros([nframes, natoms, 1]) - ) + ) # pylint: disable=no-explicit-dtype force_out = ( np.concatenate(force_out) if force_out else np.zeros([nframes, natoms, 3]) - ) + ) # pylint: disable=no-explicit-dtype force_mag_out = ( np.concatenate(force_mag_out) if force_mag_out else np.zeros([nframes, natoms, 3]) - ) + ) # pylint: disable=no-explicit-dtype virial_out = ( np.concatenate(virial_out) if virial_out else np.zeros([nframes, 3, 3]) - ) + ) # pylint: disable=no-explicit-dtype atomic_virial_out = ( np.concatenate(atomic_virial_out) if atomic_virial_out else np.zeros([nframes, natoms, 3, 3]) - ) + ) # pylint: disable=no-explicit-dtype updated_coord_out = ( np.concatenate(updated_coord_out) if updated_coord_out else None ) From afba7af5bd3b6014e6342c8b2c1b31557ab4412a Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 13 Oct 2024 16:22:28 +0800 Subject: [PATCH 097/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/pt/model/model/__init__.py b/deepmd/pt/model/model/__init__.py index 28712263e1..e646819fcd 100644 --- a/deepmd/pt/model/model/__init__.py +++ b/deepmd/pt/model/model/__init__.py @@ -80,7 +80,7 @@ def get_spin_model(model_params): if not model_params["spin"]["use_spin"] or isinstance( model_params["spin"]["use_spin"][0], int ): - use_spin = np.full(len(model_params["type_map"]), False) + use_spin = np.full(len(model_params["type_map"]), False) # pylint: disable=no-explicit-dtype use_spin[model_params["spin"]["use_spin"]] = True model_params["spin"]["use_spin"] = use_spin.tolist() # include virtual spin and placeholder types From fb6e49ac35b788fe04f83fd58dcc48d8df71f529 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 13 Oct 2024 16:23:23 +0800 Subject: [PATCH 098/189] Update training.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/training.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index 3ea54a0bc7..5a26baca26 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -949,7 +949,7 @@ def log_loss_valid(_task_key="Default"): p=np.array(self.model_prob), size=self.world_size, replace=True, - ) + ) # pylint: disable=no-explicit-dtype assert chosen_index_list.size == self.world_size model_index = chosen_index_list[self.rank] model_key = self.model_keys[model_index] From 95e55cf835e08cdc3be796782fcb9ea23a18c7d7 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 13 Oct 2024 16:26:28 +0800 Subject: [PATCH 099/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index b5b7a91dfe..f72784d07f 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -6,7 +6,6 @@ Callable, Optional, Union, - List, ) import numpy as np @@ -606,7 +605,7 @@ def eval_model( model, coords: Union[np.ndarray, torch.Tensor], cells: Optional[Union[np.ndarray, torch.Tensor]], - atom_types: Union[np.ndarray, torch.Tensor, List[int]], + atom_types: Union[np.ndarray, torch.Tensor, list[int]], spins: Optional[Union[np.ndarray, torch.Tensor]] = None, atomic: bool = False, infer_batch_size: int = 2, From 90572c90fe82834a39df5921414f71b0a9fa0f16 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 13 Oct 2024 16:27:44 +0800 Subject: [PATCH 100/189] Update ener_hess.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/ener_hess.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deepmd/pt/loss/ener_hess.py b/deepmd/pt/loss/ener_hess.py index 6c76e9011d..dd9399728e 100644 --- a/deepmd/pt/loss/ener_hess.py +++ b/deepmd/pt/loss/ener_hess.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: LGPL-3.0-or-later from typing import ( - List, Optional, ) @@ -363,7 +362,7 @@ def forward(self, input_dict, model, label, natoms, learning_rate, mae=False): return model_pred, loss, more_loss @property - def label_requirement(self) -> List[DataRequirementItem]: + def label_requirement(self) -> list[DataRequirementItem]: """Return data label requirements needed for this loss calculation.""" label_requirement = [] if self.has_e: From d42f041c6794d272bdf2e217e98bef445ed3cc5d Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 13 Oct 2024 16:28:56 +0800 Subject: [PATCH 101/189] Update ener_hess_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/ener_hess_model.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/deepmd/pt/model/model/ener_hess_model.py b/deepmd/pt/model/model/ener_hess_model.py index d511a47fc0..a16aa1bce6 100644 --- a/deepmd/pt/model/model/ener_hess_model.py +++ b/deepmd/pt/model/model/ener_hess_model.py @@ -3,7 +3,6 @@ deepcopy, ) from typing import ( - Dict, Optional, ) @@ -69,7 +68,7 @@ def forward( fparam: Optional[torch.Tensor] = None, aparam: Optional[torch.Tensor] = None, do_atomic_virial: bool = False, - ) -> Dict[str, torch.Tensor]: + ) -> dict[str, torch.Tensor]: self.requires_hessian("energy") model_ret = self.forward_common( coord, @@ -112,7 +111,7 @@ def forward_lower( fparam: Optional[torch.Tensor] = None, aparam: Optional[torch.Tensor] = None, do_atomic_virial: bool = False, - comm_dict: Optional[Dict[str, torch.Tensor]] = None, + comm_dict: Optional[dict[str, torch.Tensor]] = None, ): model_ret = self.forward_common_lower( extended_coord, From 672d975d92cc8d68ce8b9b5cf35291c5a2809ca0 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:58:33 +0800 Subject: [PATCH 102/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index f72784d07f..1973c160dd 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -739,29 +739,29 @@ def eval_model( logits_out.append(batch_output["logits"]) if not return_tensor: energy_out = ( - np.concatenate(energy_out) if energy_out else np.zeros([nframes, 1]) - ) # pylint: disable=no-explicit-dtype + np.concatenate(energy_out) if energy_out else np.zeros([nframes, 1]) # pylint: disable=no-explicit-dtype + ) atomic_energy_out = ( np.concatenate(atomic_energy_out) if atomic_energy_out - else np.zeros([nframes, natoms, 1]) - ) # pylint: disable=no-explicit-dtype + else np.zeros([nframes, natoms, 1]) # pylint: disable=no-explicit-dtype + ) force_out = ( - np.concatenate(force_out) if force_out else np.zeros([nframes, natoms, 3]) - ) # pylint: disable=no-explicit-dtype + np.concatenate(force_out) if force_out else np.zeros([nframes, natoms, 3]) # pylint: disable=no-explicit-dtype + ) force_mag_out = ( np.concatenate(force_mag_out) if force_mag_out - else np.zeros([nframes, natoms, 3]) - ) # pylint: disable=no-explicit-dtype + else np.zeros([nframes, natoms, 3]) # pylint: disable=no-explicit-dtype + ) virial_out = ( - np.concatenate(virial_out) if virial_out else np.zeros([nframes, 3, 3]) - ) # pylint: disable=no-explicit-dtype + np.concatenate(virial_out) if virial_out else np.zeros([nframes, 3, 3]) # pylint: disable=no-explicit-dtype + ) atomic_virial_out = ( np.concatenate(atomic_virial_out) if atomic_virial_out - else np.zeros([nframes, natoms, 3, 3]) - ) # pylint: disable=no-explicit-dtype + else np.zeros([nframes, natoms, 3, 3]) # pylint: disable=no-explicit-dtype + ) updated_coord_out = ( np.concatenate(updated_coord_out) if updated_coord_out else None ) From 1b79250507a22f6b206e3fbe4fad4b42075a7754 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:00:03 +0800 Subject: [PATCH 103/189] Update training.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/training.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index 5a26baca26..b689caf140 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -945,11 +945,11 @@ def log_loss_valid(_task_key="Default"): continue if self.multi_task: chosen_index_list = dp_random.choice( - np.arange(self.num_model), + np.arange(self.num_model), # pylint: disable=no-explicit-dtype p=np.array(self.model_prob), size=self.world_size, replace=True, - ) # pylint: disable=no-explicit-dtype + ) assert chosen_index_list.size == self.world_size model_index = chosen_index_list[self.rank] model_key = self.model_keys[model_index] From 11af67a0800689ce6eee9681d4552edbcab5845c Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:06:01 +0800 Subject: [PATCH 104/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index 1973c160dd..af01efcdb5 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -742,25 +742,25 @@ def eval_model( np.concatenate(energy_out) if energy_out else np.zeros([nframes, 1]) # pylint: disable=no-explicit-dtype ) atomic_energy_out = ( - np.concatenate(atomic_energy_out) + np.concatenate(atomic_energy_out) # pylint: disable=no-explicit-dtype if atomic_energy_out - else np.zeros([nframes, natoms, 1]) # pylint: disable=no-explicit-dtype + else np.zeros([nframes, natoms, 1]) ) force_out = ( np.concatenate(force_out) if force_out else np.zeros([nframes, natoms, 3]) # pylint: disable=no-explicit-dtype ) force_mag_out = ( - np.concatenate(force_mag_out) + np.concatenate(force_mag_out) # pylint: disable=no-explicit-dtype if force_mag_out - else np.zeros([nframes, natoms, 3]) # pylint: disable=no-explicit-dtype + else np.zeros([nframes, natoms, 3]) ) virial_out = ( np.concatenate(virial_out) if virial_out else np.zeros([nframes, 3, 3]) # pylint: disable=no-explicit-dtype ) atomic_virial_out = ( - np.concatenate(atomic_virial_out) + np.concatenate(atomic_virial_out) # pylint: disable=no-explicit-dtype if atomic_virial_out - else np.zeros([nframes, natoms, 3, 3]) # pylint: disable=no-explicit-dtype + else np.zeros([nframes, natoms, 3, 3]) ) updated_coord_out = ( np.concatenate(updated_coord_out) if updated_coord_out else None From 79d5d62eaada5704a8e629a7e2298c6f8e517b8f Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:07:05 +0800 Subject: [PATCH 105/189] Update data.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/utils/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/utils/data.py b/deepmd/utils/data.py index a796c8d690..0a10bdca1f 100644 --- a/deepmd/utils/data.py +++ b/deepmd/utils/data.py @@ -647,7 +647,7 @@ def _load_data( data = data.reshape(nframes, 3 * natoms, 3 * natoms) # get idx_map for hessian num_chunks, chunk_size = len(idx_map), 3 - idx_map_hess = np.arange(num_chunks * chunk_size) + idx_map_hess = np.arange(num_chunks * chunk_size) # pylint: disable=no-explicit-dtype idx_map_hess = idx_map_hess.reshape(num_chunks, chunk_size) idx_map_hess = idx_map_hess[idx_map] idx_map_hess = idx_map_hess.flatten() From 3d09cdea0a6bbf7cd9b51f63880cacbd7f240834 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:07:56 +0800 Subject: [PATCH 106/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index af01efcdb5..1973c160dd 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -742,25 +742,25 @@ def eval_model( np.concatenate(energy_out) if energy_out else np.zeros([nframes, 1]) # pylint: disable=no-explicit-dtype ) atomic_energy_out = ( - np.concatenate(atomic_energy_out) # pylint: disable=no-explicit-dtype + np.concatenate(atomic_energy_out) if atomic_energy_out - else np.zeros([nframes, natoms, 1]) + else np.zeros([nframes, natoms, 1]) # pylint: disable=no-explicit-dtype ) force_out = ( np.concatenate(force_out) if force_out else np.zeros([nframes, natoms, 3]) # pylint: disable=no-explicit-dtype ) force_mag_out = ( - np.concatenate(force_mag_out) # pylint: disable=no-explicit-dtype + np.concatenate(force_mag_out) if force_mag_out - else np.zeros([nframes, natoms, 3]) + else np.zeros([nframes, natoms, 3]) # pylint: disable=no-explicit-dtype ) virial_out = ( np.concatenate(virial_out) if virial_out else np.zeros([nframes, 3, 3]) # pylint: disable=no-explicit-dtype ) atomic_virial_out = ( - np.concatenate(atomic_virial_out) # pylint: disable=no-explicit-dtype + np.concatenate(atomic_virial_out) if atomic_virial_out - else np.zeros([nframes, natoms, 3, 3]) + else np.zeros([nframes, natoms, 3, 3]) # pylint: disable=no-explicit-dtype ) updated_coord_out = ( np.concatenate(updated_coord_out) if updated_coord_out else None From a4c94f9439ee314f61b7a8c9797fe26f5a8f9feb Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:09:49 +0800 Subject: [PATCH 107/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index c2e6d20c89..36563c1a7d 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -37,6 +37,9 @@ from deepmd.infer.deep_wfc import ( DeepWFC, ) +from deepmd.pt.model.model import ( + get_model, +) from deepmd.utils import random as dp_random from deepmd.utils.data import ( DeepmdData, @@ -44,9 +47,6 @@ from deepmd.utils.weight_avg import ( weighted_average, ) -from deepmd.pt.model.model import ( - get_model, -) if TYPE_CHECKING: from deepmd.infer.deep_tensor import ( From 81d4cc72aee0281f654c1aac944f6c306efd8b8d Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:10:53 +0800 Subject: [PATCH 108/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/pt/loss/__init__.py b/deepmd/pt/loss/__init__.py index 24a598a8c2..94dc69aeb5 100644 --- a/deepmd/pt/loss/__init__.py +++ b/deepmd/pt/loss/__init__.py @@ -8,7 +8,7 @@ from .ener import ( EnergyStdLoss, ) -from .ener_hess import( +from .ener_hess import ( EnergyHessianStdLoss, ) from .ener_spin import ( From 51f31bdc15b1828f751e5be9107facd081670ed3 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:11:59 +0800 Subject: [PATCH 109/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deepmd/pt/model/model/__init__.py b/deepmd/pt/model/model/__init__.py index e646819fcd..3c15158cbd 100644 --- a/deepmd/pt/model/model/__init__.py +++ b/deepmd/pt/model/model/__init__.py @@ -45,12 +45,12 @@ from .dp_zbl_model import ( DPZBLModel, ) -from .ener_model import ( - EnergyModel, -) from .ener_hess_model import ( EnergyHessianModel, ) +from .ener_model import ( + EnergyModel, +) from .frozen import ( FrozenModel, ) From a1adf35657e11fe204feb351f42d27e52a6613aa Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:12:43 +0800 Subject: [PATCH 110/189] Update ener_hess_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/ener_hess_model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deepmd/pt/model/model/ener_hess_model.py b/deepmd/pt/model/model/ener_hess_model.py index a16aa1bce6..6cc184ee84 100644 --- a/deepmd/pt/model/model/ener_hess_model.py +++ b/deepmd/pt/model/model/ener_hess_model.py @@ -14,16 +14,16 @@ from deepmd.pt.model.model.model import ( BaseModel, ) + from .dp_model import ( DPModelCommon, ) -from .make_model import ( - make_model, -) from .make_hessian_model import ( make_hessian_model, ) - +from .make_model import ( + make_model, +) DPEnergyModel_ = make_model(DPEnergyAtomicModel) DPEnergyModel_ = make_hessian_model(DPEnergyModel_) From 75bda4b2567b8b84e206cc6586317d98809d3940 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:13:19 +0800 Subject: [PATCH 111/189] Update training.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/training.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index b689caf140..5d3d3de614 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -25,9 +25,9 @@ from deepmd.pt.loss import ( DenoiseLoss, DOSLoss, + EnergyHessianStdLoss, EnergySpinLoss, EnergyStdLoss, - EnergyHessianStdLoss, PropertyLoss, TensorLoss, ) From baa5581d8a43b75aafdb7e9617551fc7eaf7cdb2 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:13:56 +0800 Subject: [PATCH 112/189] Update test_dp_hessian_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- source/tests/pt/model/test_dp_hessian_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tests/pt/model/test_dp_hessian_model.py b/source/tests/pt/model/test_dp_hessian_model.py index b493c18698..dfaca00ebb 100644 --- a/source/tests/pt/model/test_dp_hessian_model.py +++ b/source/tests/pt/model/test_dp_hessian_model.py @@ -7,8 +7,8 @@ DescrptSeA, ) from deepmd.pt.model.model import ( - EnergyModel, EnergyHessianModel, + EnergyModel, ) from deepmd.pt.model.task.ener import ( EnergyFittingNet, From 1ac94844e703e3ccdb53a085cbf3b74fd6bed05e Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:20:35 +0800 Subject: [PATCH 113/189] Update train-energy-hessian.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> From 4bd2906918dc3cc18019cdecc056f7fcc0dc2a8e Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:32:17 +0800 Subject: [PATCH 114/189] Update train-energy-hessian.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/train-energy-hessian.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md index ba6f473025..07badec3d2 100644 --- a/doc/model/train-energy-hessian.md +++ b/doc/model/train-energy-hessian.md @@ -1,7 +1,7 @@ # Fit energy Hessian To train a model that takes Hessian matrices, i.e., the second order derivatives of energies w.r.t coordinates as input, you only need to prepare full Hessian matrices and modify the `loss` section to define the Hessian-specific settings, keeping other sections the same as the normal energy model's input script. - + Note that fitting energy Hessians is only supported in the **PyTorch** backend as of now. From 19cbc348e4343fd7bd8fd2c39d922be0dc0a394e Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:34:26 +0800 Subject: [PATCH 115/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deepmd/pt/model/model/__init__.py b/deepmd/pt/model/model/__init__.py index cae9690847..86c2990f57 100644 --- a/deepmd/pt/model/model/__init__.py +++ b/deepmd/pt/model/model/__init__.py @@ -308,4 +308,3 @@ def get_model(model_params): "make_hessian_model", "LinearEnergyModel", ] - From cff11b0a425f48b9da0cfb5104603cd1b44097d9 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:35:08 +0800 Subject: [PATCH 116/189] Update argcheck.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/utils/argcheck.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deepmd/utils/argcheck.py b/deepmd/utils/argcheck.py index 9a5ababc89..b556f7fe3d 100644 --- a/deepmd/utils/argcheck.py +++ b/deepmd/utils/argcheck.py @@ -3070,4 +3070,3 @@ def normalize(data, multi_task=False): if __name__ == "__main__": gen_doc() - From 6b0ad9db1e24ed6972b88017280598aee7493dd6 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:35:37 +0800 Subject: [PATCH 117/189] Update ener_hess.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/ener_hess.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deepmd/pt/loss/ener_hess.py b/deepmd/pt/loss/ener_hess.py index dd9399728e..d80b28c0be 100644 --- a/deepmd/pt/loss/ener_hess.py +++ b/deepmd/pt/loss/ener_hess.py @@ -448,4 +448,3 @@ def label_requirement(self) -> list[DataRequirementItem]: ) ) return label_requirement - From 6d5a46396236672c1f032a6c0c70eb89febda119 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:36:00 +0800 Subject: [PATCH 118/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deepmd/pt/loss/__init__.py b/deepmd/pt/loss/__init__.py index 94dc69aeb5..2b9c755084 100644 --- a/deepmd/pt/loss/__init__.py +++ b/deepmd/pt/loss/__init__.py @@ -34,4 +34,3 @@ "DOSLoss", "PropertyLoss", ] - From 69b5cb41bf68f5535b2ca8fe946e9b4a6919bac6 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:36:21 +0800 Subject: [PATCH 119/189] Update training.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/training.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index 5d3d3de614..7e47fd19a9 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -1321,4 +1321,3 @@ def model_change_out_bias( f"to {to_numpy_array(new_bias).reshape(-1)!s}." ) return _model - From e891b47fb28ffa4ba4988ea0d3d1caca56e6c8e4 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:48:24 +0800 Subject: [PATCH 120/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index 36563c1a7d..a75a9d70fd 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -37,9 +37,6 @@ from deepmd.infer.deep_wfc import ( DeepWFC, ) -from deepmd.pt.model.model import ( - get_model, -) from deepmd.utils import random as dp_random from deepmd.utils.data import ( DeepmdData, @@ -48,6 +45,10 @@ weighted_average, ) +from .pt.model.model import ( + get_model, +) + if TYPE_CHECKING: from deepmd.infer.deep_tensor import ( DeepTensor, From bb106345900f775f3884e1c7e9b63c8143faa827 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:51:11 +0800 Subject: [PATCH 121/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index a75a9d70fd..82b9d23e13 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -45,7 +45,7 @@ weighted_average, ) -from .pt.model.model import ( +from ..pt.model.model import ( get_model, ) From baeb71b0216fdbee2f2a41a8f792fa8aeae959e7 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 20:15:39 +0800 Subject: [PATCH 122/189] Update train-energy-hessian.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/train-energy-hessian.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md index 07badec3d2..c472850b59 100644 --- a/doc/model/train-energy-hessian.md +++ b/doc/model/train-energy-hessian.md @@ -4,30 +4,28 @@ To train a model that takes Hessian matrices, i.e., the second order derivatives Note that fitting energy Hessians is only supported in the **PyTorch** backend as of now. - ## Energy Hessian Loss If you want to train with Hessians, you are expected to add the start and limit prefactors of Hessians, i.e.,`start_pref_h` and `limit_pref_h` to the `loss` section in the `input.json`: ```json - "loss": { - "type": "ener", - "start_pref_e": 0.02, - "limit_pref_e": 1, - "start_pref_f": 1000, - "limit_pref_f": 1, - "start_pref_v": 0, - "limit_pref_v": 0, - "start_pref_h": 10, - "limit_pref_h": 1 - }, + "loss": { + "type": "ener", + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + "start_pref_h": 10, + "limit_pref_h": 1 + }, ``` The options `start_pref_e`, `limit_pref_e`, `start_pref_f`, `limit_pref_f`, `start_pref_v`, and `limit_pref_v`, determine the start and limit prefactors of energy, force, and virial, respectively. The calculation and definition of Hessian loss are the same as for the other terms. If one does not want to train with virial, they may set the virial prefactors `start_pref_v` and `limit_pref_v` to 0. - ## Hessian format in PyTorch In the PyTorch backend, Hessian matrices are listed in `hessian.npy` files, and the data format may contain the following files: From 68416d64cd92a5b1202a1b76beb0853a3d82b15a Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 20:39:00 +0800 Subject: [PATCH 123/189] Update train-energy-hessian.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/train-energy-hessian.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md index c472850b59..f5dd2c38ed 100644 --- a/doc/model/train-energy-hessian.md +++ b/doc/model/train-energy-hessian.md @@ -30,7 +30,7 @@ If one does not want to train with virial, they may set the virial prefactors `s In the PyTorch backend, Hessian matrices are listed in `hessian.npy` files, and the data format may contain the following files: -``` +```plaintext type.raw set.*/box.npy set.*/coord.npy From f66069469e626137c27bf9c00b5bd718d1c75b9e Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 20:40:11 +0800 Subject: [PATCH 124/189] Update train-energy-hessian.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/train-energy-hessian.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md index f5dd2c38ed..1cea3f5262 100644 --- a/doc/model/train-energy-hessian.md +++ b/doc/model/train-energy-hessian.md @@ -9,20 +9,20 @@ Note that fitting energy Hessians is only supported in the **PyTorch** backend a If you want to train with Hessians, you are expected to add the start and limit prefactors of Hessians, i.e.,`start_pref_h` and `limit_pref_h` to the `loss` section in the `input.json`: ```json - "loss": { - "type": "ener", - "start_pref_e": 0.02, - "limit_pref_e": 1, - "start_pref_f": 1000, - "limit_pref_f": 1, - "start_pref_v": 0, - "limit_pref_v": 0, - "start_pref_h": 10, - "limit_pref_h": 1 - }, + "loss": { + "type": "ener", + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + "start_pref_h": 10, + "limit_pref_h": 1 + }, ``` -The options `start_pref_e`, `limit_pref_e`, `start_pref_f`, `limit_pref_f`, `start_pref_v`, and `limit_pref_v`, determine the start and limit prefactors of energy, force, and virial, respectively. The calculation and definition of Hessian loss are the same as for the other terms. +The options `start_pref_e`, `limit_pref_e`, `start_pref_f`, `limit_pref_f`, `start_pref_v`, and `limit_pref_v`, determine the start and limit prefactors of energy, force, and virial, respectively. The calculation and definition of Hessian loss are the same as for the other terms. If one does not want to train with virial, they may set the virial prefactors `start_pref_v` and `limit_pref_v` to 0. @@ -41,8 +41,8 @@ set.*/hessian.npy This system contains `Nframes` frames with the same atom number `Natoms`, the total number of elements contained in all frames is `Ntypes`. Most files are the same as those in [standard formats](../data/system.md), here we only list the distinct ones: -| ID | Property | Raw file | Unit | Shape | Description | -| -------------- | ---------------- | ------------- | ------- | --------------------------------------- | ----------------------------------------------------------------- | -| hessian | Hessian matrices | hessian.npy | eV/Å^2 | Nframes \* (Natoms \* 3 \* Natoms \* 3) | Second-order derivatives of energies w.r.t coordinates. | +| ID | Property | Raw file | Unit | Shape | Description | +| ------- | ---------------- | ----------- | ------ | --------------------------------------- | ------------------------------------------------------- | +| hessian | Hessian matrices | hessian.npy | eV/Å^2 | Nframes \* (Natoms \* 3 \* Natoms \* 3) | Second-order derivatives of energies w.r.t coordinates. | Note that the `hessian.npy` should contain the **full** Hessian matrices with shape of `(3Natoms * 3Natoms)` for each frame, rather than the upper or lower triangular matrices with shape of `(3Natoms * (3Natoms + 1) / 2)` for each frame. From f4fc6ee39dd24f0023efc3eba5ec94d220f0643e Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 22:07:32 +0800 Subject: [PATCH 125/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index 82b9d23e13..865f456319 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -259,11 +259,6 @@ def save_txt_file( np.savetxt(fp, data, header=header) -def whether_hessian(dp: "DeepPot"): - # If model type is EnergyHessianModel, return True and print hessian loss info - return "Hessian" in str(type(get_model(dp.deep_eval.input_param))) - - def test_ener( dp: "DeepPot", data: DeepmdData, @@ -313,7 +308,7 @@ def test_ener( if dp.has_spin: data.add("spin", 3, atomic=True, must=True, high_prec=False) data.add("force_mag", 3, atomic=True, must=False, high_prec=False) - if whether_hessian(dp): + if dp.deep_eval.input_param.get("hessian_mode"): data.add("hessian", 1, atomic=True, must=True, high_prec=False) test_data = data.get_test() @@ -364,7 +359,7 @@ def test_ener( energy = energy.reshape([numb_test, 1]) force = force.reshape([numb_test, -1]) virial = virial.reshape([numb_test, 9]) - if whether_hessian(dp): + if dp.deep_eval.input_param.get("hessian_mode"): hessian = ret[-1] hessian = hessian.reshape([numb_test, -1]) if has_atom_ener: @@ -430,7 +425,7 @@ def test_ener( rmse_ea = rmse_e / natoms mae_va = mae_v / natoms rmse_va = rmse_v / natoms - if whether_hessian(dp): + if dp.deep_eval.input_param.get("hessian_mode"): diff_h = hessian - test_data["hessian"][:numb_test] mae_h = mae(diff_h) rmse_h = rmse(diff_h) @@ -466,7 +461,7 @@ def test_ener( if has_atom_ener: log.info(f"Atomic ener MAE : {mae_ae:e} eV") log.info(f"Atomic ener RMSE : {rmse_ae:e} eV") - if whether_hessian(dp): + if dp.deep_eval.input_param.get("hessian_mode"): log.info(f"Hessian MAE : {mae_h:e} eV/A^2") log.info(f"Hessian RMSE : {rmse_h:e} eV/A^2") @@ -552,7 +547,7 @@ def test_ener( "pred_vyy pred_vyz pred_vzx pred_vzy pred_vzz", append=append_detail, ) - if whether_hessian(dp): + if dp.deep_eval.input_param.get("hessian_mode"): _n_frames_, _n_hessian_ = test_data['hessian'][:numb_test].shape _n_atoms_ = np.int32(np.sqrt(_n_hessian_) / 3) # n_hessian = 3na*3na triu_indices = np.triu_indices(_n_atoms_ * 3) # upper triangle hessian indices @@ -599,7 +594,7 @@ def test_ener( "rmse_v": (rmse_v, virial.size), "rmse_va": (rmse_va, virial.size), } - if whether_hessian(dp): + if dp.deep_eval.input_param.get("hessian_mode"): dict_to_return["mae_h"] = (mae_h, hessian.size) dict_to_return["rmse_h"] = (rmse_h, hessian.size) return dict_to_return From 86b5451ddec4b08194d8eb4502cff534ffa70ac4 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 15 Oct 2024 22:08:05 +0800 Subject: [PATCH 126/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index 865f456319..2b1fb56643 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -45,10 +45,6 @@ weighted_average, ) -from ..pt.model.model import ( - get_model, -) - if TYPE_CHECKING: from deepmd.infer.deep_tensor import ( DeepTensor, From 1f766c306da8b57e5d16f84a631ac3180803e848 Mon Sep 17 00:00:00 2001 From: anyangml Date: Thu, 17 Oct 2024 19:17:25 +0800 Subject: [PATCH 127/189] Fixed bugs and formatted codes for fitting with hessian --- deepmd/calculator.py | 7 +- deepmd/driver.py | 7 +- deepmd/entrypoints/test.py | 48 ++- deepmd/infer/deep_pot.py | 8 +- deepmd/pt/infer/deep_eval.py | 1 + deepmd/pt/loss/ener_hess.py | 8 +- deepmd/pt/model/model/__init__.py | 2 +- deepmd/pt/model/model/ener_hess_model.py | 38 +- deepmd/pt/train/training.py | 9 +- deepmd/utils/argcheck.py | 12 + deepmd/utils/data.py | 4 +- examples/hessian/multi-task/input.json | 350 +++++++++--------- examples/hessian/single-task/input.json | 194 +++++----- source/tests/infer/test_models.py | 28 +- .../tests/pt/model/test_dp_hessian_model.py | 1 + 15 files changed, 395 insertions(+), 322 deletions(-) diff --git a/deepmd/calculator.py b/deepmd/calculator.py index 032fa2bcfa..cc8975642d 100644 --- a/deepmd/calculator.py +++ b/deepmd/calculator.py @@ -123,7 +123,12 @@ def calculate( cell = None symbols = self.atoms.get_chemical_symbols() atype = [self.type_dict[k] for k in symbols] - e, f, v = self.dp.eval(coords=coord, cells=cell, atom_types=atype) + results_tmp = self.dp.eval(coords=coord, cells=cell, atom_types=atype) + if len(results_tmp) == 3: + e, f, v = results_tmp + else: + e, f, v = results_tmp[:-1] # results_tmp[-1] is hessian + del results_tmp self.results["energy"] = e[0][0] # see https://gitlab.com/ase/ase/-/merge_requests/2485 self.results["free_energy"] = e[0][0] diff --git a/deepmd/driver.py b/deepmd/driver.py index 998edcbc18..bbe4f6134c 100644 --- a/deepmd/driver.py +++ b/deepmd/driver.py @@ -67,7 +67,12 @@ def label(self, data: dict) -> dict: cell = data["cells"].reshape((nframes, 9)) else: cell = None - e, f, v = self.dp.eval(coord, cell, atype) + results_tmp = self.dp.eval(coords=coord, cells=cell, atom_types=atype) + if len(results_tmp) == 3: + e, f, v = results_tmp + else: + e, f, v = results_tmp[:-1] # results_tmp[-1] is hessian + del results_tmp data = data.copy() data["energies"] = e.reshape((nframes,)) data["forces"] = f.reshape((nframes, natoms, 3)) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index 2b1fb56643..8c51887c7f 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -304,7 +304,9 @@ def test_ener( if dp.has_spin: data.add("spin", 3, atomic=True, must=True, high_prec=False) data.add("force_mag", 3, atomic=True, must=False, high_prec=False) - if dp.deep_eval.input_param.get("hessian_mode"): + if hasattr(dp.deep_eval, "input_param") and dp.deep_eval.input_param.get( + "hessian_mode" + ): data.add("hessian", 1, atomic=True, must=True, high_prec=False) test_data = data.get_test() @@ -355,7 +357,9 @@ def test_ener( energy = energy.reshape([numb_test, 1]) force = force.reshape([numb_test, -1]) virial = virial.reshape([numb_test, 9]) - if dp.deep_eval.input_param.get("hessian_mode"): + if hasattr(dp.deep_eval, "input_param") and dp.deep_eval.input_param.get( + "hessian_mode" + ): hessian = ret[-1] hessian = hessian.reshape([numb_test, -1]) if has_atom_ener: @@ -421,7 +425,9 @@ def test_ener( rmse_ea = rmse_e / natoms mae_va = mae_v / natoms rmse_va = rmse_v / natoms - if dp.deep_eval.input_param.get("hessian_mode"): + if hasattr(dp.deep_eval, "input_param") and dp.deep_eval.input_param.get( + "hessian_mode" + ): diff_h = hessian - test_data["hessian"][:numb_test] mae_h = mae(diff_h) rmse_h = rmse(diff_h) @@ -457,7 +463,9 @@ def test_ener( if has_atom_ener: log.info(f"Atomic ener MAE : {mae_ae:e} eV") log.info(f"Atomic ener RMSE : {rmse_ae:e} eV") - if dp.deep_eval.input_param.get("hessian_mode"): + if hasattr(dp.deep_eval, "input_param") and dp.deep_eval.input_param.get( + "hessian_mode" + ): log.info(f"Hessian MAE : {mae_h:e} eV/A^2") log.info(f"Hessian RMSE : {rmse_h:e} eV/A^2") @@ -543,19 +551,27 @@ def test_ener( "pred_vyy pred_vyz pred_vzx pred_vzy pred_vzz", append=append_detail, ) - if dp.deep_eval.input_param.get("hessian_mode"): - _n_frames_, _n_hessian_ = test_data['hessian'][:numb_test].shape + if hasattr(dp.deep_eval, "input_param") and dp.deep_eval.input_param.get( + "hessian_mode" + ): + _n_frames_, _n_hessian_ = test_data["hessian"][:numb_test].shape _n_atoms_ = np.int32(np.sqrt(_n_hessian_) / 3) # n_hessian = 3na*3na - triu_indices = np.triu_indices(_n_atoms_ * 3) # upper triangle hessian indices - data_h_triu = test_data["hessian"][:numb_test][:, triu_indices[0] * _n_atoms_ * 3 + triu_indices[1]].reshape(-1, 1) - pred_h_triu = hessian[:, triu_indices[0] * _n_atoms_ * 3 + triu_indices[1]].reshape(-1, 1) + triu_indices = np.triu_indices( + _n_atoms_ * 3 + ) # upper triangle hessian indices + data_h_triu = test_data["hessian"][:numb_test][ + :, triu_indices[0] * _n_atoms_ * 3 + triu_indices[1] + ].reshape(-1, 1) + pred_h_triu = hessian[ + :, triu_indices[0] * _n_atoms_ * 3 + triu_indices[1] + ].reshape(-1, 1) h = np.concatenate( ( - data_h_triu, - pred_h_triu, - ), - axis=1, - ) + data_h_triu, + pred_h_triu, + ), + axis=1, + ) save_txt_file( detail_path.with_suffix(".h.out"), h, @@ -590,7 +606,9 @@ def test_ener( "rmse_v": (rmse_v, virial.size), "rmse_va": (rmse_va, virial.size), } - if dp.deep_eval.input_param.get("hessian_mode"): + if hasattr(dp.deep_eval, "input_param") and dp.deep_eval.input_param.get( + "hessian_mode" + ): dict_to_return["mae_h"] = (mae_h, hessian.size) dict_to_return["rmse_h"] = (rmse_h, hessian.size) return dict_to_return diff --git a/deepmd/infer/deep_pot.py b/deepmd/infer/deep_pot.py index 9c81f9d8d2..6aeb11dfc2 100644 --- a/deepmd/infer/deep_pot.py +++ b/deepmd/infer/deep_pot.py @@ -23,6 +23,7 @@ class DeepPot(DeepEval): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + """Potential energy model. Parameters @@ -214,7 +215,6 @@ def eval( force = results["energy_derv_r"].reshape(nframes, natoms, 3) virial = results["energy_derv_c_redu"].reshape(nframes, 9) - if atomic: if self.get_ntypes_spin() > 0: ntypes_real = self.get_ntypes() - self.get_ntypes_spin() @@ -246,8 +246,10 @@ def eval( mask_mag = results["mask_mag"].reshape(nframes, natoms, 1) result = (*list(result), force_mag, mask_mag) if "energy_derv_r_derv_r" in list(results.keys()): - hessian = results["energy_derv_r_derv_r"].reshape(nframes, 3 * natoms, 3 * natoms) - result += (hessian, ) + hessian = results["energy_derv_r_derv_r"].reshape( + nframes, 3 * natoms, 3 * natoms + ) + result += (hessian,) return result diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index 1973c160dd..cb646873bc 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -600,6 +600,7 @@ def get_model_def_script(self) -> str: """Get model defination script.""" return self.model_def_script + # For tests only def eval_model( model, diff --git a/deepmd/pt/loss/ener_hess.py b/deepmd/pt/loss/ener_hess.py index d80b28c0be..498caab578 100644 --- a/deepmd/pt/loss/ener_hess.py +++ b/deepmd/pt/loss/ener_hess.py @@ -324,13 +324,17 @@ def forward(self, input_dict, model, label, natoms, learning_rate, mae=False): if self.has_h and "hessian" in model_pred and "hessian" in label: find_hessian = label.get("find_hessian", 0.0) pref_h = pref_h * find_hessian - diff_h = label["hessian"].reshape(-1,) - model_pred["hessian"].reshape(-1,) # tbd + diff_h = label["hessian"].reshape( + -1, + ) - model_pred["hessian"].reshape( + -1, + ) # tbd l2_hessian_loss = torch.mean(torch.square(diff_h)) if not self.inference: more_loss["l2_hessian_loss"] = self.display_if_exist( l2_hessian_loss.detach(), find_hessian ) - loss += (pref_h * l2_hessian_loss) # tbd + loss += pref_h * l2_hessian_loss # tbd rmse_h = l2_hessian_loss.sqrt() # tbd more_loss["rmse_h"] = self.display_if_exist(rmse_h.detach(), find_hessian) if mae: diff --git a/deepmd/pt/model/model/__init__.py b/deepmd/pt/model/model/__init__.py index 86c2990f57..a9ffac87e0 100644 --- a/deepmd/pt/model/model/__init__.py +++ b/deepmd/pt/model/model/__init__.py @@ -257,7 +257,7 @@ def get_standard_model(model_params): modelcls = DOSModel elif fitting_net_type in ["ener", "direct_force_ener"]: modelcls = EnergyModel - if "hessian_mode" in model_params and model_params["hessian_mode"]: + if model_params.get("hessian_mode"): modelcls = EnergyHessianModel elif fitting_net_type == "property": modelcls = PropertyModel diff --git a/deepmd/pt/model/model/ener_hess_model.py b/deepmd/pt/model/model/ener_hess_model.py index 6cc184ee84..07d0de89da 100644 --- a/deepmd/pt/model/model/ener_hess_model.py +++ b/deepmd/pt/model/model/ener_hess_model.py @@ -34,9 +34,9 @@ class EnergyHessianModel(DPModelCommon, DPEnergyModel_): model_type = "ener_hess" def __init__( - self, - *args, - **kwargs, + self, + *args, + **kwargs, ): DPModelCommon.__init__(self) DPEnergyModel_.__init__(self, *args, **kwargs) @@ -61,13 +61,13 @@ def translated_output_def(self): return output_def def forward( - self, - coord, - atype, - box: Optional[torch.Tensor] = None, - fparam: Optional[torch.Tensor] = None, - aparam: Optional[torch.Tensor] = None, - do_atomic_virial: bool = False, + self, + coord, + atype, + box: Optional[torch.Tensor] = None, + fparam: Optional[torch.Tensor] = None, + aparam: Optional[torch.Tensor] = None, + do_atomic_virial: bool = False, ) -> dict[str, torch.Tensor]: self.requires_hessian("energy") model_ret = self.forward_common( @@ -103,15 +103,15 @@ def forward( @torch.jit.export def forward_lower( - self, - extended_coord, - extended_atype, - nlist, - mapping: Optional[torch.Tensor] = None, - fparam: Optional[torch.Tensor] = None, - aparam: Optional[torch.Tensor] = None, - do_atomic_virial: bool = False, - comm_dict: Optional[dict[str, torch.Tensor]] = None, + self, + extended_coord, + extended_atype, + nlist, + mapping: Optional[torch.Tensor] = None, + fparam: Optional[torch.Tensor] = None, + aparam: Optional[torch.Tensor] = None, + do_atomic_virial: bool = False, + comm_dict: Optional[dict[str, torch.Tensor]] = None, ): model_ret = self.forward_common_lower( extended_coord, diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index 7e47fd19a9..f5a8d2e38d 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -278,7 +278,10 @@ def get_lr(lr_params): if not self.multi_task: loss_param_tmp = config["loss"] else: - loss_param_tmp = {model_key: config["loss_dict"][model_key] for model_key in self.model_keys} + loss_param_tmp = { + model_key: config["loss_dict"][model_key] + for model_key in self.model_keys + } # Model dp_random.seed(training_params["seed"]) @@ -1281,8 +1284,8 @@ def get_single_model( def get_model_for_wrapper( - _model_params, - _loss_params=None, + _model_params, + _loss_params=None, ): if "model_dict" not in _model_params: if _loss_params is not None and whether_hessian(_loss_params): diff --git a/deepmd/utils/argcheck.py b/deepmd/utils/argcheck.py index b556f7fe3d..4519264ad9 100644 --- a/deepmd/utils/argcheck.py +++ b/deepmd/utils/argcheck.py @@ -2211,6 +2211,7 @@ def loss_ener(): ), ] + @loss_args_plugin.register("ener_hess") def loss_ener_hess(): # loss of fitting EnergyHessianModel doc_start_pref_e = start_pref("energy", abbr="e") @@ -2348,6 +2349,7 @@ def loss_ener_hess(): # loss of fitting EnergyHessianModel ), ] + @loss_args_plugin.register("ener_spin") def loss_ener_spin(): doc_start_pref_e = start_pref("energy") @@ -3048,6 +3050,16 @@ def gen_args(multi_task: bool = False) -> list[Argument]: ] +def gen_args_multi_task() -> Argument: + """Generate multi-task arguments.""" + return Argument( + "multi-task", + dict, + sub_fields=gen_args(multi_task=True), + doc="Multi-task arguments.", + ) + + def gen_json_schema() -> str: """Generate JSON schema. diff --git a/deepmd/utils/data.py b/deepmd/utils/data.py index 0a10bdca1f..5e7153b405 100644 --- a/deepmd/utils/data.py +++ b/deepmd/utils/data.py @@ -654,7 +654,9 @@ def _load_data( data = data[:, idx_map_hess, :] data = data[:, :, idx_map_hess] data = data.reshape([nframes, -1]) - ndof = 3 * ndof * 3 * ndof # size of hessian is 3Natoms * 3Natoms + ndof = ( + 3 * ndof * 3 * ndof + ) # size of hessian is 3Natoms * 3Natoms else: data = data.reshape([nframes, natoms, -1]) data = data[:, idx_map, :] diff --git a/examples/hessian/multi-task/input.json b/examples/hessian/multi-task/input.json index d5a68e7b24..28ab91ee58 100644 --- a/examples/hessian/multi-task/input.json +++ b/examples/hessian/multi-task/input.json @@ -1,183 +1,183 @@ { - "_comment": "that's all", - "model": { - "shared_dict": { - "type_map_all": [ - "C", - "H", - "N", - "O" - ], - "dpa1_descriptor": { - "type": "dpa1", - "sel": 120, - "rcut_smth": 0.5, - "rcut": 6.0, - "neuron": [ - 25, - 50, - 100 - ], - "tebd_dim": 256, - "axis_neuron": 16, - "type_one_side": true, - "attn": 128, - "attn_layer": 0, - "attn_dotr": true, - "attn_mask": false, - "activation_function": "tanh", - "scaling_factor": 1.0, - "normalize": true, - "temperature": 1.0 - }, - "_comment": "that's all" + "_comment": "that's all", + "model": { + "shared_dict": { + "type_map_all": [ + "C", + "H", + "N", + "O" + ], + "dpa1_descriptor": { + "type": "dpa1", + "sel": 120, + "rcut_smth": 0.5, + "rcut": 6.0, + "neuron": [ + 25, + 50, + 100 + ], + "tebd_dim": 256, + "axis_neuron": 16, + "type_one_side": true, + "attn": 128, + "attn_layer": 0, + "attn_dotr": true, + "attn_mask": false, + "activation_function": "tanh", + "scaling_factor": 1.0, + "normalize": true, + "temperature": 1.0 + }, + "_comment": "that's all" + }, + "model_dict": { + "H10C5N2O": { + "type_map": [ + "C", + "H", + "N", + "O" + ], + "descriptor": { + "type": "dpa1", + "sel": 120, + "rcut_smth": 0.5, + "rcut": 6.0, + "neuron": [ + 25, + 50, + 100 + ], + "tebd_dim": 256, + "axis_neuron": 16, + "type_one_side": true, + "attn": 128, + "attn_layer": 0, + "attn_dotr": true, + "attn_mask": false, + "activation_function": "tanh", + "scaling_factor": 1.0, + "normalize": true, + "temperature": 1.0 }, - "model_dict": { - "H10C5N2O": { - "type_map": [ - "C", - "H", - "N", - "O" - ], - "descriptor": { - "type": "dpa1", - "sel": 120, - "rcut_smth": 0.5, - "rcut": 6.0, - "neuron": [ - 25, - 50, - 100 - ], - "tebd_dim": 256, - "axis_neuron": 16, - "type_one_side": true, - "attn": 128, - "attn_layer": 0, - "attn_dotr": true, - "attn_mask": false, - "activation_function": "tanh", - "scaling_factor": 1.0, - "normalize": true, - "temperature": 1.0 - }, - "fitting_net": { - "neuron": [ - 240, - 240, - 240 - ], - "activation_function": "tanh", - "resnet_dt": true, - "seed": 1, - "_comment": " that's all" - } - }, - "H8C4N2O": { - "type_map": [ - "C", - "H", - "N", - "O" - ], - "descriptor": { - "type": "dpa1", - "sel": 120, - "rcut_smth": 0.5, - "rcut": 6.0, - "neuron": [ - 25, - 50, - 100 - ], - "tebd_dim": 256, - "axis_neuron": 16, - "type_one_side": true, - "attn": 128, - "attn_layer": 0, - "attn_dotr": true, - "attn_mask": false, - "activation_function": "tanh", - "scaling_factor": 1.0, - "normalize": true, - "temperature": 1.0 - }, - "fitting_net": { - "neuron": [ - 240, - 240, - 240 - ], - "activation_function": "tanh", - "resnet_dt": true, - "seed": 1, - "_comment": " that's all" - } - } + "fitting_net": { + "neuron": [ + 240, + 240, + 240 + ], + "activation_function": "tanh", + "resnet_dt": true, + "seed": 1, + "_comment": " that's all" } - }, - "learning_rate": { - "type": "exp", - "decay_steps": 20000, - "start_lr": 0.0002, - "stop_lr": 3.51e-08, - "_comment": "that's all" - }, - "loss_dict": { - "H10C5N2O": { - "type": "ener", - "start_pref_e": 0.02, - "limit_pref_e": 1, - "start_pref_f": 1000, - "limit_pref_f": 1, - "start_pref_v": 0, - "limit_pref_v": 0 + }, + "H8C4N2O": { + "type_map": [ + "C", + "H", + "N", + "O" + ], + "descriptor": { + "type": "dpa1", + "sel": 120, + "rcut_smth": 0.5, + "rcut": 6.0, + "neuron": [ + 25, + 50, + 100 + ], + "tebd_dim": 256, + "axis_neuron": 16, + "type_one_side": true, + "attn": 128, + "attn_layer": 0, + "attn_dotr": true, + "attn_mask": false, + "activation_function": "tanh", + "scaling_factor": 1.0, + "normalize": true, + "temperature": 1.0 }, - "H8C4N2O": { - "type": "ener", - "start_pref_e": 0.02, - "limit_pref_e": 1, - "start_pref_f": 1000, - "limit_pref_f": 1, - "start_pref_v": 0, - "limit_pref_v": 0, - "start_pref_h": 10, - "limit_pref_h": 1 + "fitting_net": { + "neuron": [ + 240, + 240, + 240 + ], + "activation_function": "tanh", + "resnet_dt": true, + "seed": 1, + "_comment": " that's all" } + } + } + }, + "learning_rate": { + "type": "exp", + "decay_steps": 20000, + "start_lr": 0.0002, + "stop_lr": 3.51e-08, + "_comment": "that's all" + }, + "loss_dict": { + "H10C5N2O": { + "type": "ener", + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0 }, - "training": { - "model_prob": { - "H10C5N2O": 2.0, - "H8C4N2O": 3.0 - }, - "data_dict": { - "H10C5N2O": { - "training_data": { - "systems": [ - "../data/H10C5N2O/" - ], - "batch_size": 1, - "_comment": "that's all" - } - }, - "H8C4N2O": { - "training_data": { - "systems": [ - "../data/H8C4N2O/" - ], - "batch_size": 1, - "_comment": "that's all" - } - } - }, - "numb_steps": 1, - "warmup_steps": 0, - "gradient_max_norm": 5.0, - "seed": 10, - "disp_file": "lcurve.out", - "disp_freq": 100, - "save_freq": 2000, - "_comment": "that's all" + "H8C4N2O": { + "type": "ener", + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + "start_pref_h": 10, + "limit_pref_h": 1 } + }, + "training": { + "model_prob": { + "H10C5N2O": 2.0, + "H8C4N2O": 3.0 + }, + "data_dict": { + "H10C5N2O": { + "training_data": { + "systems": [ + "../data/H10C5N2O/" + ], + "batch_size": 1, + "_comment": "that's all" + } + }, + "H8C4N2O": { + "training_data": { + "systems": [ + "../data/H8C4N2O/" + ], + "batch_size": 1, + "_comment": "that's all" + } + } + }, + "numb_steps": 1, + "warmup_steps": 0, + "gradient_max_norm": 5.0, + "seed": 10, + "disp_file": "lcurve.out", + "disp_freq": 100, + "save_freq": 2000, + "_comment": "that's all" + } } diff --git a/examples/hessian/single-task/input.json b/examples/hessian/single-task/input.json index 8d7d7c03a3..e227cf342c 100644 --- a/examples/hessian/single-task/input.json +++ b/examples/hessian/single-task/input.json @@ -1,103 +1,103 @@ { - "_comment": "that's all", - "model": { - "type_map": [ - "C", - "H", - "N", - "O" + "_comment": "that's all", + "model": { + "type_map": [ + "C", + "H", + "N", + "O" + ], + "descriptor": { + "type": "dpa2", + "repinit": { + "tebd_dim": 8, + "rcut": 9.0, + "rcut_smth": 8.0, + "nsel": 120, + "neuron": [ + 25, + 50, + 100 ], - "descriptor": { - "type": "dpa2", - "repinit": { - "tebd_dim": 8, - "rcut": 9.0, - "rcut_smth": 8.0, - "nsel": 120, - "neuron": [ - 25, - 50, - 100 - ], - "axis_neuron": 12, - "activation_function": "tanh" - }, - "repformer": { - "rcut": 4.0, - "rcut_smth": 3.5, - "nsel": 40, - "nlayers": 12, - "g1_dim": 128, - "g2_dim": 32, - "attn2_hidden": 32, - "attn2_nhead": 4, - "attn1_hidden": 128, - "attn1_nhead": 4, - "axis_neuron": 4, - "update_h2": false, - "update_g1_has_conv": true, - "update_g1_has_grrg": true, - "update_g1_has_drrd": true, - "update_g1_has_attn": true, - "update_g2_has_g1g1": true, - "update_g2_has_attn": true, - "attn2_has_gate": true - }, - "add_tebd_to_repinit_out": false - }, - "fitting_net": { - "neuron": [ - 240, - 240, - 240 - ], - "resnet_dt": true, - "seed": 1, - "_comment": " that's all" - }, - "_comment": " that's all" + "axis_neuron": 12, + "activation_function": "tanh" + }, + "repformer": { + "rcut": 4.0, + "rcut_smth": 3.5, + "nsel": 40, + "nlayers": 12, + "g1_dim": 128, + "g2_dim": 32, + "attn2_hidden": 32, + "attn2_nhead": 4, + "attn1_hidden": 128, + "attn1_nhead": 4, + "axis_neuron": 4, + "update_h2": false, + "update_g1_has_conv": true, + "update_g1_has_grrg": true, + "update_g1_has_drrd": true, + "update_g1_has_attn": true, + "update_g2_has_g1g1": true, + "update_g2_has_attn": true, + "attn2_has_gate": true + }, + "add_tebd_to_repinit_out": false }, - "learning_rate": { - "type": "exp", - "decay_steps": 5000, - "start_lr": 0.0002, - "stop_lr": 3.51e-08, - "_comment": "that's all" + "fitting_net": { + "neuron": [ + 240, + 240, + 240 + ], + "resnet_dt": true, + "seed": 1, + "_comment": " that's all" }, - "loss": { - "type": "ener", - "start_pref_e": 0.02, - "limit_pref_e": 1, - "start_pref_f": 1000, - "limit_pref_f": 1, - "start_pref_v": 0, - "limit_pref_v": 0, - "start_pref_h": 10, - "limit_pref_h": 1, - "_comment": " that's all" + "_comment": " that's all" + }, + "learning_rate": { + "type": "exp", + "decay_steps": 5000, + "start_lr": 0.0002, + "stop_lr": 3.51e-08, + "_comment": "that's all" + }, + "loss": { + "type": "ener", + "start_pref_e": 0.02, + "limit_pref_e": 1, + "start_pref_f": 1000, + "limit_pref_f": 1, + "start_pref_v": 0, + "limit_pref_v": 0, + "start_pref_h": 10, + "limit_pref_h": 1, + "_comment": " that's all" + }, + "training": { + "training_data": { + "systems": [ + "../data/H8C4N2O" + ], + "batch_size": 1, + "_comment": "that's all" }, - "training": { - "training_data": { - "systems": [ - "../data/H8C4N2O" - ], - "batch_size": 1, - "_comment": "that's all" - }, - "validation_data": { - "systems": [ - "../data/H10C5N2O" - ], - "batch_size": 1, - "_comment": "that's all" - }, - "numb_steps": 1000000, - "warmup_steps": 0, - "gradient_max_norm": 5.0, - "seed": 10, - "disp_file": "lcurve.out", - "disp_freq": 100, - "save_freq": 2000, - "_comment": "that's all" - } + "validation_data": { + "systems": [ + "../data/H10C5N2O" + ], + "batch_size": 1, + "_comment": "that's all" + }, + "numb_steps": 1000000, + "warmup_steps": 0, + "gradient_max_norm": 5.0, + "seed": 10, + "disp_file": "lcurve.out", + "disp_freq": 100, + "save_freq": 2000, + "_comment": "that's all" + } } diff --git a/source/tests/infer/test_models.py b/source/tests/infer/test_models.py index 6b62e994aa..ace72718ab 100644 --- a/source/tests/infer/test_models.py +++ b/source/tests/infer/test_models.py @@ -63,7 +63,7 @@ def test_attrs(self): def test_1frame(self): for ii, result in enumerate(self.case.results): - ee, ff, vv = self.dp.eval( + results_tmp = self.dp.eval( result.coord, result.box, result.atype, @@ -71,6 +71,11 @@ def test_1frame(self): fparam=result.fparam, aparam=result.aparam, ) + if len(results_tmp) == 3: + ee, ff, vv = results_tmp + else: + ee, ff, vv = results_tmp[:-1] # results_tmp[-1] is hessian + del results_tmp # check shape of the returns nframes = 1 natoms = len(result.atype) @@ -101,7 +106,7 @@ def test_1frame(self): def test_1frame_atm(self): for ii, result in enumerate(self.case.results): - ee, ff, vv, ae, av = self.dp.eval( + results_tmp = self.dp.eval( result.coord, result.box, result.atype, @@ -109,6 +114,11 @@ def test_1frame_atm(self): fparam=result.fparam, aparam=result.aparam, ) + if len(results_tmp) == 5: + ee, ff, vv, ae, av = results_tmp + else: + ee, ff, vv, ae, av = results_tmp[:-1] # results_tmp[-1] is hessian + del results_tmp # check shape of the returns nframes = 1 natoms = len(result.atype) @@ -169,7 +179,7 @@ def test_2frame_atm(self): box2 = np.concatenate((result.box, result.box)) else: box2 = None - ee, ff, vv, ae, av = self.dp.eval( + results_tmp = self.dp.eval( coords2, box2, result.atype, @@ -177,6 +187,11 @@ def test_2frame_atm(self): fparam=result.fparam, aparam=result.aparam, ) + if len(results_tmp) == 5: + ee, ff, vv, ae, av = results_tmp + else: + ee, ff, vv, ae, av = results_tmp[:-1] # results_tmp[-1] is hessian + del results_tmp # check shape of the returns nframes = 2 natoms = len(result.atype) @@ -223,7 +238,7 @@ def test_zero_input(self): self.skipTest("Segfault in GPUs") nframes = 1 for box in [np.eye(3, dtype=np.float64).reshape(1, 3, 3), None]: - ee, ff, vv = self.dp.eval( + results_tmp = self.dp.eval( np.zeros([nframes, 0, 3], dtype=np.float64), box, np.zeros([0], dtype=int), @@ -235,6 +250,11 @@ def test_zero_input(self): if self.case.dim_aparam else None, ) + if len(results_tmp) == 3: + ee, ff, vv = results_tmp + else: + ee, ff, vv = results_tmp[:-1] # results_tmp[-1] is hessian + del results_tmp # check shape of the returns natoms = 0 self.assertEqual(ee.shape, (nframes, 1)) diff --git a/source/tests/pt/model/test_dp_hessian_model.py b/source/tests/pt/model/test_dp_hessian_model.py index dfaca00ebb..b4b1ff2c33 100644 --- a/source/tests/pt/model/test_dp_hessian_model.py +++ b/source/tests/pt/model/test_dp_hessian_model.py @@ -27,6 +27,7 @@ dtype = env.GLOBAL_PT_FLOAT_PRECISION + class TestEnergyHessianModel(unittest.TestCase, TestCaseSingleFrameWithoutNlist): def setUp(self): TestCaseSingleFrameWithoutNlist.setUp(self) From 7d3bfda87609780e97047ceaa2d985cf7f8c6987 Mon Sep 17 00:00:00 2001 From: anyangml Date: Fri, 18 Oct 2024 00:50:34 +0800 Subject: [PATCH 128/189] Fixed bugs and formatted codes for fitting with hessian --- deepmd/pt/infer/deep_eval.py | 5 ++--- deepmd/pt/infer/inference.py | 3 +++ deepmd/pt/train/training.py | 2 +- deepmd/utils/argcheck.py | 41 ++++++++++++++++++++---------------- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index ea5800eb92..a6b92b759a 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -599,8 +599,7 @@ def eval_typeebd(self) -> np.ndarray: def get_model_def_script(self) -> str: """Get model defination script.""" return self.model_def_script - - + def eval_descriptor( self, coords: np.ndarray, @@ -656,7 +655,7 @@ def eval_descriptor( model.set_eval_descriptor_hook(False) return to_numpy_array(descriptor) - + # For tests only def eval_model( model, diff --git a/deepmd/pt/infer/inference.py b/deepmd/pt/infer/inference.py index dfb7abdb21..4204020a0d 100644 --- a/deepmd/pt/infer/inference.py +++ b/deepmd/pt/infer/inference.py @@ -55,6 +55,9 @@ def __init__( ] = state_dict[item].clone() state_dict = state_dict_head + model_params.pop( + "hessian_mode", None + ) # wrapper Hessian to Energy model due to JIT limit self.model_params = deepcopy(model_params) self.model = get_model(model_params).to(DEVICE) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index 52428cf160..b1e4907da9 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -1306,7 +1306,7 @@ def get_model_for_wrapper( _model = {} model_keys = list(_model_params["model_dict"]) for _model_key in model_keys: - if _loss_params is not None and whether_hessian(_loss_params): + if _loss_params is not None and whether_hessian(_loss_params[_model_key]): _model_params["model_dict"][_model_key]["hessian_mode"] = True _model[_model_key] = get_single_model( _model_params["model_dict"][_model_key], diff --git a/deepmd/utils/argcheck.py b/deepmd/utils/argcheck.py index 4519264ad9..352190de84 100644 --- a/deepmd/utils/argcheck.py +++ b/deepmd/utils/argcheck.py @@ -2360,8 +2360,8 @@ def loss_ener_spin(): doc_limit_pref_fm = limit_pref("force_magnetic") doc_start_pref_v = start_pref("virial") doc_limit_pref_v = limit_pref("virial") - doc_start_pref_h = start_pref("hessian") - doc_limit_pref_h = limit_pref("hessian") + # doc_start_pref_h = start_pref("hessian") + # doc_limit_pref_h = limit_pref("hessian") doc_start_pref_ae = start_pref("atom_ener") doc_limit_pref_ae = limit_pref("atom_ener") doc_start_pref_pf = start_pref("atom_pref") @@ -2425,20 +2425,20 @@ def loss_ener_spin(): default=0.00, doc=doc_limit_pref_v, ), - Argument( - "start_pref_h", - [float, int], - optional=True, - default=0.00, - doc=doc_start_pref_h, - ), - Argument( - "limit_pref_h", - [float, int], - optional=True, - default=0.00, - doc=doc_limit_pref_h, - ), + # Argument( + # "start_pref_h", + # [float, int], + # optional=True, + # default=0.00, + # doc=doc_start_pref_h, + # ), + # Argument( + # "limit_pref_h", + # [float, int], + # optional=True, + # default=0.00, + # doc=doc_limit_pref_h, + # ), Argument( "start_pref_ae", [float, int], @@ -3060,7 +3060,7 @@ def gen_args_multi_task() -> Argument: ) -def gen_json_schema() -> str: +def gen_json_schema(multi_task: bool = False) -> str: """Generate JSON schema. Returns @@ -3068,7 +3068,12 @@ def gen_json_schema() -> str: str JSON schema. """ - arg = Argument("DeePMD-kit", dict, gen_args(), doc=f"DeePMD-kit {__version__}") + arg = Argument( + "DeePMD-kit", + dict, + gen_args(multi_task=multi_task), + doc=f"DeePMD-kit {__version__}", + ) return json.dumps(generate_json_schema(arg)) From 551d5f93b7455bacf8735aa150308fa243dbaba8 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Mon, 21 Oct 2024 13:58:17 +0800 Subject: [PATCH 129/189] Remove eval_model func. Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 227 ----------------------------------- 1 file changed, 227 deletions(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index a6b92b759a..b43b181670 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -654,230 +654,3 @@ def eval_descriptor( descriptor = model.eval_descriptor() model.set_eval_descriptor_hook(False) return to_numpy_array(descriptor) - - -# For tests only -def eval_model( - model, - coords: Union[np.ndarray, torch.Tensor], - cells: Optional[Union[np.ndarray, torch.Tensor]], - atom_types: Union[np.ndarray, torch.Tensor, list[int]], - spins: Optional[Union[np.ndarray, torch.Tensor]] = None, - atomic: bool = False, - infer_batch_size: int = 2, - denoise: bool = False, -): - model = model.to(DEVICE) - energy_out = [] - atomic_energy_out = [] - force_out = [] - force_mag_out = [] - virial_out = [] - atomic_virial_out = [] - updated_coord_out = [] - logits_out = [] - err_msg = ( - f"All inputs should be the same format, " - f"but found {type(coords)}, {type(cells)}, {type(atom_types)} instead! " - ) - return_tensor = True - if isinstance(coords, torch.Tensor): - if cells is not None: - assert isinstance(cells, torch.Tensor), err_msg - if spins is not None: - assert isinstance(spins, torch.Tensor), err_msg - assert isinstance(atom_types, torch.Tensor) or isinstance(atom_types, list) - atom_types = torch.tensor(atom_types, dtype=torch.long, device=DEVICE) - elif isinstance(coords, np.ndarray): - if cells is not None: - assert isinstance(cells, np.ndarray), err_msg - if spins is not None: - assert isinstance(spins, np.ndarray), err_msg - assert isinstance(atom_types, np.ndarray) or isinstance(atom_types, list) - atom_types = np.array(atom_types, dtype=np.int32) - return_tensor = False - - nframes = coords.shape[0] - if len(atom_types.shape) == 1: - natoms = len(atom_types) - if isinstance(atom_types, torch.Tensor): - atom_types = torch.tile(atom_types.unsqueeze(0), [nframes, 1]).reshape( - nframes, -1 - ) - else: - atom_types = np.tile(atom_types, nframes).reshape(nframes, -1) - else: - natoms = len(atom_types[0]) - - coord_input = torch.tensor( - coords.reshape([-1, natoms, 3]), dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE - ) - spin_input = None - if spins is not None: - spin_input = torch.tensor( - spins.reshape([-1, natoms, 3]), - dtype=GLOBAL_PT_FLOAT_PRECISION, - device=DEVICE, - ) - has_spin = getattr(model, "has_spin", False) - if callable(has_spin): - has_spin = has_spin() - type_input = torch.tensor(atom_types, dtype=torch.long, device=DEVICE) - box_input = None - if cells is None: - pbc = False - else: - pbc = True - box_input = torch.tensor( - cells.reshape([-1, 3, 3]), dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE - ) - num_iter = int((nframes + infer_batch_size - 1) / infer_batch_size) - - for ii in range(num_iter): - batch_coord = coord_input[ii * infer_batch_size : (ii + 1) * infer_batch_size] - batch_atype = type_input[ii * infer_batch_size : (ii + 1) * infer_batch_size] - batch_box = None - batch_spin = None - if spin_input is not None: - batch_spin = spin_input[ii * infer_batch_size : (ii + 1) * infer_batch_size] - if pbc: - batch_box = box_input[ii * infer_batch_size : (ii + 1) * infer_batch_size] - input_dict = { - "coord": batch_coord, - "atype": batch_atype, - "box": batch_box, - "do_atomic_virial": atomic, - } - if has_spin: - input_dict["spin"] = batch_spin - batch_output = model(**input_dict) - if isinstance(batch_output, tuple): - batch_output = batch_output[0] - if not return_tensor: - if "energy" in batch_output: - energy_out.append(batch_output["energy"].detach().cpu().numpy()) - if "atom_energy" in batch_output: - atomic_energy_out.append( - batch_output["atom_energy"].detach().cpu().numpy() - ) - if "force" in batch_output: - force_out.append(batch_output["force"].detach().cpu().numpy()) - if "force_mag" in batch_output: - force_mag_out.append(batch_output["force_mag"].detach().cpu().numpy()) - if "virial" in batch_output: - virial_out.append(batch_output["virial"].detach().cpu().numpy()) - if "atom_virial" in batch_output: - atomic_virial_out.append( - batch_output["atom_virial"].detach().cpu().numpy() - ) - if "updated_coord" in batch_output: - updated_coord_out.append( - batch_output["updated_coord"].detach().cpu().numpy() - ) - if "logits" in batch_output: - logits_out.append(batch_output["logits"].detach().cpu().numpy()) - else: - if "energy" in batch_output: - energy_out.append(batch_output["energy"]) - if "atom_energy" in batch_output: - atomic_energy_out.append(batch_output["atom_energy"]) - if "force" in batch_output: - force_out.append(batch_output["force"]) - if "force_mag" in batch_output: - force_mag_out.append(batch_output["force_mag"]) - if "virial" in batch_output: - virial_out.append(batch_output["virial"]) - if "atom_virial" in batch_output: - atomic_virial_out.append(batch_output["atom_virial"]) - if "updated_coord" in batch_output: - updated_coord_out.append(batch_output["updated_coord"]) - if "logits" in batch_output: - logits_out.append(batch_output["logits"]) - if not return_tensor: - energy_out = ( - np.concatenate(energy_out) if energy_out else np.zeros([nframes, 1]) # pylint: disable=no-explicit-dtype - ) - atomic_energy_out = ( - np.concatenate(atomic_energy_out) - if atomic_energy_out - else np.zeros([nframes, natoms, 1]) # pylint: disable=no-explicit-dtype - ) - force_out = ( - np.concatenate(force_out) if force_out else np.zeros([nframes, natoms, 3]) # pylint: disable=no-explicit-dtype - ) - force_mag_out = ( - np.concatenate(force_mag_out) - if force_mag_out - else np.zeros([nframes, natoms, 3]) # pylint: disable=no-explicit-dtype - ) - virial_out = ( - np.concatenate(virial_out) if virial_out else np.zeros([nframes, 3, 3]) # pylint: disable=no-explicit-dtype - ) - atomic_virial_out = ( - np.concatenate(atomic_virial_out) - if atomic_virial_out - else np.zeros([nframes, natoms, 3, 3]) # pylint: disable=no-explicit-dtype - ) - updated_coord_out = ( - np.concatenate(updated_coord_out) if updated_coord_out else None - ) - logits_out = np.concatenate(logits_out) if logits_out else None - else: - energy_out = ( - torch.cat(energy_out) - if energy_out - else torch.zeros( - [nframes, 1], dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE - ) - ) - atomic_energy_out = ( - torch.cat(atomic_energy_out) - if atomic_energy_out - else torch.zeros( - [nframes, natoms, 1], dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE - ) - ) - force_out = ( - torch.cat(force_out) - if force_out - else torch.zeros( - [nframes, natoms, 3], dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE - ) - ) - force_mag_out = ( - torch.cat(force_mag_out) - if force_mag_out - else torch.zeros( - [nframes, natoms, 3], dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE - ) - ) - virial_out = ( - torch.cat(virial_out) - if virial_out - else torch.zeros( - [nframes, 3, 3], dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE - ) - ) - atomic_virial_out = ( - torch.cat(atomic_virial_out) - if atomic_virial_out - else torch.zeros( - [nframes, natoms, 3, 3], dtype=GLOBAL_PT_FLOAT_PRECISION, device=DEVICE - ) - ) - updated_coord_out = torch.cat(updated_coord_out) if updated_coord_out else None - logits_out = torch.cat(logits_out) if logits_out else None - if denoise: - return updated_coord_out, logits_out - else: - results_dict = { - "energy": energy_out, - "force": force_out, - "virial": virial_out, - } - if has_spin: - results_dict["force_mag"] = force_mag_out - if atomic: - results_dict["atom_energy"] = atomic_energy_out - results_dict["atom_virial"] = atomic_virial_out - return results_dict From efc8ab793102db4394a98faddf43c669eac86f0e Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:44:57 +0800 Subject: [PATCH 130/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index b43b181670..166fcc5096 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -461,7 +461,9 @@ def _eval_model( results.append(out) else: shape = self._get_output_shape(odef, nframes, natoms) - results.append(np.full(np.abs(shape), np.nan)) # pylint: disable=no-explicit-dtype; this is kinda hack + results.append( + np.full(np.abs(shape), np.nan) # pylint: disable=no-explicit-dtype + ) # this is kinda hack return tuple(results) def _eval_model_spin( @@ -539,7 +541,9 @@ def _eval_model_spin( results.append(out) else: shape = self._get_output_shape(odef, nframes, natoms) - results.append(np.full(np.abs(shape), np.nan)) # pylint: disable=no-explicit-dtype; this is kinda hacky + results.append( + np.full(np.abs(shape), np.nan) # pylint: disable=no-explicit-dtype + ) # this is kinda hack return tuple(results) def _get_output_shape(self, odef, nframes, natoms): From c9405ed456cedf1a224ac8f54c6b286ab2d44dca Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:50:56 +0800 Subject: [PATCH 131/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index 166fcc5096..0c118a2f55 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -463,7 +463,7 @@ def _eval_model( shape = self._get_output_shape(odef, nframes, natoms) results.append( np.full(np.abs(shape), np.nan) # pylint: disable=no-explicit-dtype - ) # this is kinda hack + ) # this is kinda hacky return tuple(results) def _eval_model_spin( @@ -543,7 +543,7 @@ def _eval_model_spin( shape = self._get_output_shape(odef, nframes, natoms) results.append( np.full(np.abs(shape), np.nan) # pylint: disable=no-explicit-dtype - ) # this is kinda hack + ) # this is kinda hacky return tuple(results) def _get_output_shape(self, odef, nframes, natoms): From fefe0666f11044e3861768afdefac98530450f10 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:20:53 +0800 Subject: [PATCH 132/189] Update argcheck.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/utils/argcheck.py | 54 +++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/deepmd/utils/argcheck.py b/deepmd/utils/argcheck.py index 352190de84..dd69dc40bc 100644 --- a/deepmd/utils/argcheck.py +++ b/deepmd/utils/argcheck.py @@ -212,9 +212,17 @@ def get_all_argument(self, exclude_hybrid: bool = False) -> list[Argument]: for (name, alias, doc), metd in self.__plugin.plugins.items(): if exclude_hybrid and name == "hybrid": continue - arguments.append( - Argument(name=name, dtype=dict, sub_fields=metd(), alias=alias, doc=doc) - ) + args = metd() + if isinstance(args, Argument): + arguments.append(args) + elif isinstance(args, list): + arguments.append( + Argument( + name=name, dtype=dict, sub_fields=metd(), alias=alias, doc=doc + ) + ) + else: + raise ValueError(f"Invalid return type {type(args)}") return arguments @@ -1741,6 +1749,11 @@ def model_compression_type_args(): ) +model_args_plugin = ArgsPlugin() +# for models that require another model as input +hybrid_model_args_plugin = ArgsPlugin() + + def model_args(exclude_hybrid=False): doc_type_map = "A list of strings. Give the name to each type of atoms. It is noted that the number of atom type of training system must be less than 128 in a GPU environment. If not given, type.raw in each system should use the same type indexes, and type_map.raw will take no effect." doc_data_stat_nbatch = "The model determines the normalization from the statistics of the data. This key specifies the number of `frames` in each `system` used for statistics." @@ -1765,12 +1778,7 @@ def model_args(exclude_hybrid=False): hybrid_models = [] if not exclude_hybrid: - hybrid_models.extend( - [ - pairwise_dprc(), - linear_ener_model_args(), - ] - ) + hybrid_models.extend(hybrid_model_args_plugin.get_all_argument()) return Argument( "model", dict, @@ -1876,9 +1884,7 @@ def model_args(exclude_hybrid=False): Variant( "type", [ - standard_model_args(), - frozen_model_args(), - pairtab_model_args(), + *model_args_plugin.get_all_argument(), *hybrid_models, ], optional=True, @@ -1888,6 +1894,7 @@ def model_args(exclude_hybrid=False): ) +@model_args_plugin.register("standard") def standard_model_args() -> Argument: doc_descrpt = "The descriptor of atomic environment." doc_fitting = "The fitting of physical properties." @@ -1912,6 +1919,7 @@ def standard_model_args() -> Argument: return ca +@hybrid_model_args_plugin.register("pairwise_dprc") def pairwise_dprc() -> Argument: qm_model_args = model_args(exclude_hybrid=True) qm_model_args.name = "qm_model" @@ -1931,6 +1939,7 @@ def pairwise_dprc() -> Argument: return ca +@model_args_plugin.register("frozen") def frozen_model_args() -> Argument: doc_model_file = "Path to the frozen model file." ca = Argument( @@ -1943,6 +1952,7 @@ def frozen_model_args() -> Argument: return ca +@model_args_plugin.register("pairtab") def pairtab_model_args() -> Argument: doc_tab_file = "Path to the tabulation file." doc_rcut = "The cut-off radius." @@ -1963,6 +1973,7 @@ def pairtab_model_args() -> Argument: return ca +@hybrid_model_args_plugin.register("linear_ener") def linear_ener_model_args() -> Argument: doc_weights = ( "If the type is list of float, a list of weights for each model. " @@ -2037,7 +2048,7 @@ def learning_rate_variant_type_args(): ) -def learning_rate_args(): +def learning_rate_args(fold_subdoc: bool = False) -> Argument: doc_scale_by_worker = "When parallel training or batch size scaled, how to alter learning rate. Valid values are `linear`(default), `sqrt` or `none`." doc_lr = "The definitio of learning rate" return Argument( @@ -2055,6 +2066,7 @@ def learning_rate_args(): [learning_rate_variant_type_args()], optional=True, doc=doc_lr, + fold_subdoc=fold_subdoc, ) @@ -2977,6 +2989,7 @@ def multi_model_args(): model_dict = model_args() model_dict.name = "model_dict" model_dict.repeat = True + model_dict.fold_subdoc = True model_dict.doc = ( "The multiple definition of the model, used in the multi-task mode." ) @@ -2997,6 +3010,7 @@ def multi_loss_args(): loss_dict = loss_args() loss_dict.name = "loss_dict" loss_dict.repeat = True + loss_dict.fold_subdoc = True loss_dict.doc = "The multiple definition of the loss, used in the multi-task mode." return loss_dict @@ -3008,11 +3022,11 @@ def make_index(keys): return ", ".join(ret) -def gen_doc(*, make_anchor=True, make_link=True, **kwargs): +def gen_doc(*, make_anchor=True, make_link=True, multi_task=False, **kwargs) -> str: if make_link: make_anchor = True ptr = [] - for ii in gen_args(): + for ii in gen_args(multi_task=multi_task): ptr.append(ii.gen_doc(make_anchor=make_anchor, make_link=make_link, **kwargs)) key_words = [] @@ -3024,9 +3038,9 @@ def gen_doc(*, make_anchor=True, make_link=True, **kwargs): return "\n\n".join(ptr) -def gen_json(**kwargs): +def gen_json(multi_task: bool = False, **kwargs) -> str: return json.dumps( - tuple(gen_args()), + tuple(gen_args(multi_task=multi_task)), cls=ArgumentEncoder, ) @@ -3043,10 +3057,10 @@ def gen_args(multi_task: bool = False) -> list[Argument]: else: return [ multi_model_args(), - learning_rate_args(), + learning_rate_args(fold_subdoc=True), multi_loss_args(), training_args(multi_task=multi_task), - nvnmd_args(), + nvnmd_args(fold_subdoc=True), ] @@ -3077,7 +3091,7 @@ def gen_json_schema(multi_task: bool = False) -> str: return json.dumps(generate_json_schema(arg)) -def normalize(data, multi_task=False): +def normalize(data, multi_task: bool = False): base = Argument("base", dict, gen_args(multi_task=multi_task)) data = base.normalize_value(data, trim_pattern="_*") base.check_value(data, strict=True) From f4566997b16d137c1914dd600efeea8fb7040e05 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:36:34 +0800 Subject: [PATCH 133/189] Update training.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/training.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index b1e4907da9..481b612557 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -185,6 +185,7 @@ def get_dataloader_and_buffer(_data, _params): if dist.is_available() else 0, # setting to 0 diverges the behavior of its iterator; should be >=1 drop_last=False, + collate_fn=lambda batch: batch, # prevent extra conversion pin_memory=True, ) with torch.device("cpu"): @@ -284,10 +285,6 @@ def get_lr(lr_params): } # Model - dp_random.seed(training_params["seed"]) - if training_params["seed"] is not None: - torch.manual_seed(training_params["seed"]) - self.model = get_model_for_wrapper(model_params, loss_param_tmp) # Loss @@ -312,7 +309,6 @@ def get_lr(lr_params): ) # Data - dp_random.seed(training_params["seed"]) if not self.multi_task: self.get_sample_func = single_model_stat( self.model, @@ -1116,7 +1112,7 @@ def get_data(self, is_train=True, task_key="Default"): batch_data = next(iter(self.validation_data[task_key])) for key in batch_data.keys(): - if key == "sid" or key == "fid" or key == "box": + if key == "sid" or key == "fid" or key == "box" or "find_" in key: continue elif not isinstance(batch_data[key], list): if batch_data[key] is not None: From 3ee0bcbed557f7c20532599ae9f124bf81006e57 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:10:43 +0800 Subject: [PATCH 134/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/infer/deep_eval.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deepmd/infer/deep_eval.py b/deepmd/infer/deep_eval.py index 6d98d7c9e3..2361d73c4f 100644 --- a/deepmd/infer/deep_eval.py +++ b/deepmd/infer/deep_eval.py @@ -544,6 +544,11 @@ def has_spin(self) -> bool: """Check if the model has spin.""" return self.deep_eval.get_has_spin() + @property + def has_hessian(self) -> bool: + """Check if the model has hessian.""" + return self.input_param.get("hessian_mode", False) + def get_ntypes_spin(self) -> int: """Get the number of spin atom types of this model. Only used in old implement.""" return self.deep_eval.get_ntypes_spin() From 10ed979b055014e312ac4a653a19576b4d65a863 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:18:35 +0800 Subject: [PATCH 135/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index 0c118a2f55..a0b089c60b 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -232,6 +232,10 @@ def get_has_spin(self): """Check if the model has spin atom types.""" return self._has_spin + def get_has_hessian(self): + """Check if the model has hessian enabled.""" + return self.input_param.get("hessian_mode", False) + def eval( self, coords: np.ndarray, From ca8f8c08a83aace8952c3941df3f6deecfec667c Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:19:10 +0800 Subject: [PATCH 136/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/infer/deep_eval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/infer/deep_eval.py b/deepmd/infer/deep_eval.py index 2361d73c4f..8ee4d12fb1 100644 --- a/deepmd/infer/deep_eval.py +++ b/deepmd/infer/deep_eval.py @@ -547,7 +547,7 @@ def has_spin(self) -> bool: @property def has_hessian(self) -> bool: """Check if the model has hessian.""" - return self.input_param.get("hessian_mode", False) + return self.deep_eval.get_has_hessian() def get_ntypes_spin(self) -> int: """Get the number of spin atom types of this model. Only used in old implement.""" From 7a5c68b3e46f14c0b3dbd1ac7cc9c67387393148 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:20:34 +0800 Subject: [PATCH 137/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/infer/deep_eval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index a0b089c60b..830d1ba7ed 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -233,7 +233,7 @@ def get_has_spin(self): return self._has_spin def get_has_hessian(self): - """Check if the model has hessian enabled.""" + """Check if the model has hessian.""" return self.input_param.get("hessian_mode", False) def eval( From ec8b2ef721a8a6adac9b51ba580cede17f144968 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:21:45 +0800 Subject: [PATCH 138/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index 8c51887c7f..7b9530b5a3 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -304,9 +304,7 @@ def test_ener( if dp.has_spin: data.add("spin", 3, atomic=True, must=True, high_prec=False) data.add("force_mag", 3, atomic=True, must=False, high_prec=False) - if hasattr(dp.deep_eval, "input_param") and dp.deep_eval.input_param.get( - "hessian_mode" - ): + if dp.has_hessian: data.add("hessian", 1, atomic=True, must=True, high_prec=False) test_data = data.get_test() @@ -357,9 +355,7 @@ def test_ener( energy = energy.reshape([numb_test, 1]) force = force.reshape([numb_test, -1]) virial = virial.reshape([numb_test, 9]) - if hasattr(dp.deep_eval, "input_param") and dp.deep_eval.input_param.get( - "hessian_mode" - ): + if dp.has_hessian: hessian = ret[-1] hessian = hessian.reshape([numb_test, -1]) if has_atom_ener: @@ -425,9 +421,7 @@ def test_ener( rmse_ea = rmse_e / natoms mae_va = mae_v / natoms rmse_va = rmse_v / natoms - if hasattr(dp.deep_eval, "input_param") and dp.deep_eval.input_param.get( - "hessian_mode" - ): + if dp.has_hessian: diff_h = hessian - test_data["hessian"][:numb_test] mae_h = mae(diff_h) rmse_h = rmse(diff_h) @@ -463,9 +457,7 @@ def test_ener( if has_atom_ener: log.info(f"Atomic ener MAE : {mae_ae:e} eV") log.info(f"Atomic ener RMSE : {rmse_ae:e} eV") - if hasattr(dp.deep_eval, "input_param") and dp.deep_eval.input_param.get( - "hessian_mode" - ): + if dp.has_hessian: log.info(f"Hessian MAE : {mae_h:e} eV/A^2") log.info(f"Hessian RMSE : {rmse_h:e} eV/A^2") @@ -551,9 +543,7 @@ def test_ener( "pred_vyy pred_vyz pred_vzx pred_vzy pred_vzz", append=append_detail, ) - if hasattr(dp.deep_eval, "input_param") and dp.deep_eval.input_param.get( - "hessian_mode" - ): + if dp.has_hessian: _n_frames_, _n_hessian_ = test_data["hessian"][:numb_test].shape _n_atoms_ = np.int32(np.sqrt(_n_hessian_) / 3) # n_hessian = 3na*3na triu_indices = np.triu_indices( @@ -606,9 +596,7 @@ def test_ener( "rmse_v": (rmse_v, virial.size), "rmse_va": (rmse_va, virial.size), } - if hasattr(dp.deep_eval, "input_param") and dp.deep_eval.input_param.get( - "hessian_mode" - ): + if dp.has_hessian: dict_to_return["mae_h"] = (mae_h, hessian.size) dict_to_return["rmse_h"] = (rmse_h, hessian.size) return dict_to_return From f9706f63d244b5fed762e864807c3a32bfa889c2 Mon Sep 17 00:00:00 2001 From: 1azyking <137011979@qq.com> Date: Wed, 23 Oct 2024 17:50:03 +0800 Subject: [PATCH 139/189] Resolving conversations --- deepmd/infer/deep_eval.py | 4 ++++ deepmd/infer/deep_pot.py | 4 ++-- deepmd/pt/infer/deep_eval.py | 6 +++++- examples/hessian/{multi-task => multi_task}/input.json | 0 examples/hessian/{single-task => single_task}/input.json | 0 source/tests/common/test_examples.py | 2 ++ 6 files changed, 13 insertions(+), 3 deletions(-) rename examples/hessian/{multi-task => multi_task}/input.json (100%) rename examples/hessian/{single-task => single_task}/input.json (100%) diff --git a/deepmd/infer/deep_eval.py b/deepmd/infer/deep_eval.py index 8ee4d12fb1..c4440b54f4 100644 --- a/deepmd/infer/deep_eval.py +++ b/deepmd/infer/deep_eval.py @@ -277,6 +277,10 @@ def get_has_spin(self): """Check if the model has spin atom types.""" return False + def get_has_hessian(self): + """Check if the model has hessian.""" + return False + @abstractmethod def get_ntypes_spin(self) -> int: """Get the number of spin atom types of this model. Only used in old implement.""" diff --git a/deepmd/infer/deep_pot.py b/deepmd/infer/deep_pot.py index 6aeb11dfc2..1865be786f 100644 --- a/deepmd/infer/deep_pot.py +++ b/deepmd/infer/deep_pot.py @@ -245,11 +245,11 @@ def eval( force_mag = results["energy_derv_r_mag"].reshape(nframes, natoms, 3) mask_mag = results["mask_mag"].reshape(nframes, natoms, 1) result = (*list(result), force_mag, mask_mag) - if "energy_derv_r_derv_r" in list(results.keys()): + if self.deep_eval.get_has_hessian(): hessian = results["energy_derv_r_derv_r"].reshape( nframes, 3 * natoms, 3 * natoms ) - result += (hessian,) + result = (*list(result), hessian) return result diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index 830d1ba7ed..b14e4e7131 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -158,6 +158,9 @@ def __init__( self._has_spin = getattr(self.dp.model["Default"], "has_spin", False) if callable(self._has_spin): self._has_spin = self._has_spin() + self._has_hessian = hasattr(self, "input_param") and getattr(self.input_param, "hessian_mode", False) + if callable(self._has_hessian): + self._has_hessian = self._has_hessian() def get_rcut(self) -> float: """Get the cutoff radius of this model.""" @@ -234,7 +237,7 @@ def get_has_spin(self): def get_has_hessian(self): """Check if the model has hessian.""" - return self.input_param.get("hessian_mode", False) + return self._has_hessian def eval( self, @@ -662,3 +665,4 @@ def eval_descriptor( descriptor = model.eval_descriptor() model.set_eval_descriptor_hook(False) return to_numpy_array(descriptor) + diff --git a/examples/hessian/multi-task/input.json b/examples/hessian/multi_task/input.json similarity index 100% rename from examples/hessian/multi-task/input.json rename to examples/hessian/multi_task/input.json diff --git a/examples/hessian/single-task/input.json b/examples/hessian/single_task/input.json similarity index 100% rename from examples/hessian/single-task/input.json rename to examples/hessian/single_task/input.json diff --git a/source/tests/common/test_examples.py b/source/tests/common/test_examples.py index 246e767f01..70abb2b126 100644 --- a/source/tests/common/test_examples.py +++ b/source/tests/common/test_examples.py @@ -59,6 +59,8 @@ p_examples / "water" / "dpa2" / "input_torch_large.json", p_examples / "property" / "train" / "input_torch.json", p_examples / "water" / "se_e3_tebd" / "input_torch.json", + p_examples / "hessian"/ "single_task" / "input.json", + p_examples / "hessian"/ "multi_task" / "input.json", ) input_files_multi = ( From 934f5a0a8b7044a2c3ecccee9b09692f63787261 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:23:53 +0800 Subject: [PATCH 140/189] Update system.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/data/system.md | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/doc/data/system.md b/doc/data/system.md index b5db516726..f8933cbd4d 100644 --- a/doc/data/system.md +++ b/doc/data/system.md @@ -22,29 +22,31 @@ The input frame properties contain the following property, the first axis of whi The labeled frame properties are listed as follows, all of which will be used for training if and only if the loss function contains such property: -| ID | Property | Raw file | Unit | Shape | Description | -| --------------------- | -------------------------------------------------------------------------------- | ------------------------- | ---- | ------------------------------------- | ----------------------------------------- | -| energy | Frame energies | energy.raw | eV | Nframes | -| force | Atomic forces | force.raw | eV/Å | Nframes \* Natoms \* 3 | -| virial | Frame virial | virial.raw | eV | Nframes \* 9 | in the order `XX XY XZ YX YY YZ ZX ZY ZZ` | -| atom_ener | Atomic energies | atom_ener.raw | eV | Nframes \* Natoms | -| atom_pref | Weights of atomic forces | atom_pref.raw | 1 | Nframes \* Natoms | -| dipole | Frame dipole | dipole.raw | Any | Nframes \* 3 | -| atomic_dipole | Atomic dipole | atomic_dipole.raw | Any | Nframes \* Natoms \* 3 | -| polarizability | Frame polarizability | polarizability.raw | Any | Nframes \* 9 | in the order `XX XY XZ YX YY YZ ZX ZY ZZ` | -| atomic_polarizability | Atomic polarizability | atomic_polarizability.raw | Any | Nframes \* Natoms \* 9 | in the order `XX XY XZ YX YY YZ ZX ZY ZZ` | -| drdq | Partial derivative of atomic coordinates with respect to generalized coordinates | drdq.raw | 1 | Nframes \* Natoms \* 3 \* Ngen_coords | +| ID | Property | Raw file | Unit | Shape | Description | +| --------------------- | -------------------------------------------------------------------------------- | ------------------------- | ------ | ------------------------------------- | ----------------------------------------- | +| energy | Frame energies | energy.raw | eV | Nframes | +| force | Atomic forces | force.raw | eV/Å | Nframes \* Natoms \* 3 | +| virial | Frame virial | virial.raw | eV | Nframes \* 9 | in the order `XX XY XZ YX YY YZ ZX ZY ZZ` | +| hessian | Frame energy Hessian matrices | hessian.raw | eV/Å^2 | Nframes \* Natoms \* 3 \* Natoms \* 3 | full Hessian matrices | +| atom_ener | Atomic energies | atom_ener.raw | eV | Nframes \* Natoms | +| atom_pref | Weights of atomic forces | atom_pref.raw | 1 | Nframes \* Natoms | +| dipole | Frame dipole | dipole.raw | Any | Nframes \* 3 | +| atomic_dipole | Atomic dipole | atomic_dipole.raw | Any | Nframes \* Natoms \* 3 | +| polarizability | Frame polarizability | polarizability.raw | Any | Nframes \* 9 | in the order `XX XY XZ YX YY YZ ZX ZY ZZ` | +| atomic_polarizability | Atomic polarizability | atomic_polarizability.raw | Any | Nframes \* Natoms \* 9 | in the order `XX XY XZ YX YY YZ ZX ZY ZZ` | +| drdq | Partial derivative of atomic coordinates with respect to generalized coordinates | drdq.raw | 1 | Nframes \* Natoms \* 3 \* Ngen_coords | In general, we always use the following convention of units: -| Property | Unit | -| -------- | ---- | -| Time | ps | -| Length | Å | -| Energy | eV | -| Force | eV/Å | -| Virial | eV | -| Pressure | Bar | +| Property | Unit | +| -------- | ------ | +| Time | ps | +| Length | Å | +| Energy | eV | +| Force | eV/Å | +| Virial | eV | +| Hessian | eV/Å^2 | +| Pressure | Bar | ## Mixed type From 87b0bb4995106b00d57704a01b7bf47c4d16669f Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:26:55 +0800 Subject: [PATCH 141/189] Update train-energy-hessian.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/train-energy-hessian.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md index 1cea3f5262..8a35b7d7f7 100644 --- a/doc/model/train-energy-hessian.md +++ b/doc/model/train-energy-hessian.md @@ -39,10 +39,4 @@ set.*/force.npy set.*/hessian.npy ``` -This system contains `Nframes` frames with the same atom number `Natoms`, the total number of elements contained in all frames is `Ntypes`. Most files are the same as those in [standard formats](../data/system.md), here we only list the distinct ones: - -| ID | Property | Raw file | Unit | Shape | Description | -| ------- | ---------------- | ----------- | ------ | --------------------------------------- | ------------------------------------------------------- | -| hessian | Hessian matrices | hessian.npy | eV/Å^2 | Nframes \* (Natoms \* 3 \* Natoms \* 3) | Second-order derivatives of energies w.r.t coordinates. | - -Note that the `hessian.npy` should contain the **full** Hessian matrices with shape of `(3Natoms * 3Natoms)` for each frame, rather than the upper or lower triangular matrices with shape of `(3Natoms * (3Natoms + 1) / 2)` for each frame. +This system contains `Nframes` frames with the same atom number `Natoms`, the total number of elements contained in all frames is `Ntypes`. Note that the `hessian` file should contain the **full** Hessian matrices with shape of `(3Natoms * 3Natoms)` for each frame, rather than the upper or lower triangular matrices with shape of `(3Natoms * (3Natoms + 1) / 2)` for each frame. From 24231ed9aa2955e2a330b69705859b2d342e858d Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:52:40 +0800 Subject: [PATCH 142/189] Update train-energy-hessian.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/train-energy-hessian.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md index 8a35b7d7f7..1cea3f5262 100644 --- a/doc/model/train-energy-hessian.md +++ b/doc/model/train-energy-hessian.md @@ -39,4 +39,10 @@ set.*/force.npy set.*/hessian.npy ``` -This system contains `Nframes` frames with the same atom number `Natoms`, the total number of elements contained in all frames is `Ntypes`. Note that the `hessian` file should contain the **full** Hessian matrices with shape of `(3Natoms * 3Natoms)` for each frame, rather than the upper or lower triangular matrices with shape of `(3Natoms * (3Natoms + 1) / 2)` for each frame. +This system contains `Nframes` frames with the same atom number `Natoms`, the total number of elements contained in all frames is `Ntypes`. Most files are the same as those in [standard formats](../data/system.md), here we only list the distinct ones: + +| ID | Property | Raw file | Unit | Shape | Description | +| ------- | ---------------- | ----------- | ------ | --------------------------------------- | ------------------------------------------------------- | +| hessian | Hessian matrices | hessian.npy | eV/Å^2 | Nframes \* (Natoms \* 3 \* Natoms \* 3) | Second-order derivatives of energies w.r.t coordinates. | + +Note that the `hessian.npy` should contain the **full** Hessian matrices with shape of `(3Natoms * 3Natoms)` for each frame, rather than the upper or lower triangular matrices with shape of `(3Natoms * (3Natoms + 1) / 2)` for each frame. From 8669fa5eacd8e713362488d89dee1027b2efbb19 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:55:05 +0800 Subject: [PATCH 143/189] Update index.rst Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/model/index.rst b/doc/model/index.rst index 8409d4ce97..ede7729874 100644 --- a/doc/model/index.rst +++ b/doc/model/index.rst @@ -14,6 +14,7 @@ Model sel train-energy train-energy-spin + train-energy-hessian train-fitting-tensor train-fitting-dos train-se-e2-a-tebd From 2fbcc64e85a18a1ea0e3532637c125813ad46b94 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:57:32 +0800 Subject: [PATCH 144/189] Update overall.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/overall.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/model/overall.md b/doc/model/overall.md index 102a8fc671..b0ea19ac5d 100644 --- a/doc/model/overall.md +++ b/doc/model/overall.md @@ -57,6 +57,6 @@ DeePMD-kit implements the following descriptors: The fitting of the following physical properties is supported -1. [`ener`](train-energy.md): Fit the energy of the system. The force (derivative with atom positions) and the virial (derivative with the box tensor) can also be trained. +1. [`ener`](train-energy.md): Fit the energy of the system. The force (derivative with atom positions), the virial (derivative with the box tensor) and the hessian (second-order derivative with atom positions) can also be trained. 2. [`dipole`](train-fitting-tensor.md): The dipole moment. 3. [`polar`](train-fitting-tensor.md): The polarizability. From 91f2435450ec8eb11ad5c3b9e760ba2e168391ca Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 20:20:17 +0800 Subject: [PATCH 145/189] Update train-energy-hessian.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/train-energy-hessian.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md index 1cea3f5262..a8b350b1b3 100644 --- a/doc/model/train-energy-hessian.md +++ b/doc/model/train-energy-hessian.md @@ -1,12 +1,14 @@ -# Fit energy Hessian +# Fit energy Hessian {{ pytorch_icon }} -To train a model that takes Hessian matrices, i.e., the second order derivatives of energies w.r.t coordinates as input, you only need to prepare full Hessian matrices and modify the `loss` section to define the Hessian-specific settings, keeping other sections the same as the normal energy model's input script. +:::{note} +**Supported backends**: PyTorch {{ pytorch_icon }} +::: -Note that fitting energy Hessians is only supported in the **PyTorch** backend as of now. +To train a model that takes Hessian matrices, i.e., the second order derivatives of energies w.r.t coordinates as input, you only need to prepare full Hessian matrices and modify the `loss` section to define the Hessian-specific settings, keeping other sections the same as the normal energy model's input script. ## Energy Hessian Loss -If you want to train with Hessians, you are expected to add the start and limit prefactors of Hessians, i.e.,`start_pref_h` and `limit_pref_h` to the `loss` section in the `input.json`: +If you want to train with Hessians, you are expected to add the start and limit prefactors of Hessians, i.e., {ref}`start_pref_h ` and {ref}`limit_pref_h ` to the {ref}`loss ` section in the `input.json`: ```json "loss": { @@ -22,15 +24,15 @@ If you want to train with Hessians, you are expected to add the start and limit }, ``` -The options `start_pref_e`, `limit_pref_e`, `start_pref_f`, `limit_pref_f`, `start_pref_v`, and `limit_pref_v`, determine the start and limit prefactors of energy, force, and virial, respectively. The calculation and definition of Hessian loss are the same as for the other terms. +The options {ref}`start_pref_e `, {ref}`limit_pref_e `, {ref}`start_pref_f `, {ref}`limit_pref_f `, {ref}`start_pref_v ` and {ref}`limit_pref_v ` determine the start and limit prefactors of energy, force, and virial, respectively. The calculation and definition of Hessian loss are the same as for the other terms. -If one does not want to train with virial, they may set the virial prefactors `start_pref_v` and `limit_pref_v` to 0. +If one does not want to train with virial, then he/she may set the virial prefactors {ref}`start_pref_v ` and {ref}`limit_pref_v ` to 0. ## Hessian format in PyTorch In the PyTorch backend, Hessian matrices are listed in `hessian.npy` files, and the data format may contain the following files: -```plaintext +``` type.raw set.*/box.npy set.*/coord.npy From 0774f792113329e46f33a2c14b6efa3affdbfc88 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 21:14:04 +0800 Subject: [PATCH 146/189] Update input.json Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/multi_task/input.json | 62 ++------------------------ 1 file changed, 4 insertions(+), 58 deletions(-) diff --git a/examples/hessian/multi_task/input.json b/examples/hessian/multi_task/input.json index 28ab91ee58..b9a347581b 100644 --- a/examples/hessian/multi_task/input.json +++ b/examples/hessian/multi_task/input.json @@ -34,82 +34,28 @@ }, "model_dict": { "H10C5N2O": { - "type_map": [ - "C", - "H", - "N", - "O" - ], - "descriptor": { - "type": "dpa1", - "sel": 120, - "rcut_smth": 0.5, - "rcut": 6.0, - "neuron": [ - 25, - 50, - 100 - ], - "tebd_dim": 256, - "axis_neuron": 16, - "type_one_side": true, - "attn": 128, - "attn_layer": 0, - "attn_dotr": true, - "attn_mask": false, - "activation_function": "tanh", - "scaling_factor": 1.0, - "normalize": true, - "temperature": 1.0 - }, + "type_map": "type_map_all", + "descriptor": "dpa1_descriptor", "fitting_net": { "neuron": [ 240, 240, 240 ], - "activation_function": "tanh", "resnet_dt": true, "seed": 1, "_comment": " that's all" } }, "H8C4N2O": { - "type_map": [ - "C", - "H", - "N", - "O" - ], - "descriptor": { - "type": "dpa1", - "sel": 120, - "rcut_smth": 0.5, - "rcut": 6.0, - "neuron": [ - 25, - 50, - 100 - ], - "tebd_dim": 256, - "axis_neuron": 16, - "type_one_side": true, - "attn": 128, - "attn_layer": 0, - "attn_dotr": true, - "attn_mask": false, - "activation_function": "tanh", - "scaling_factor": 1.0, - "normalize": true, - "temperature": 1.0 - }, + "type_map": "type_map_all", + "descriptor": "dpa1_descriptor", "fitting_net": { "neuron": [ 240, 240, 240 ], - "activation_function": "tanh", "resnet_dt": true, "seed": 1, "_comment": " that's all" From ca7a96d8103169904499501875afe7334ae6e32b Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 21:15:13 +0800 Subject: [PATCH 147/189] Update test_examples.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- source/tests/common/test_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tests/common/test_examples.py b/source/tests/common/test_examples.py index 70abb2b126..e23da116b9 100644 --- a/source/tests/common/test_examples.py +++ b/source/tests/common/test_examples.py @@ -60,11 +60,11 @@ p_examples / "property" / "train" / "input_torch.json", p_examples / "water" / "se_e3_tebd" / "input_torch.json", p_examples / "hessian"/ "single_task" / "input.json", - p_examples / "hessian"/ "multi_task" / "input.json", ) input_files_multi = ( p_examples / "water_multi_task" / "pytorch_example" / "input_torch.json", + p_examples / "hessian"/ "multi_task" / "input.json", ) From 793534d63c762625641fa43c21f889224b746ab0 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 21:15:52 +0800 Subject: [PATCH 148/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deepmd/pt/loss/__init__.py b/deepmd/pt/loss/__init__.py index 2b9c755084..bc1bd7c3b6 100644 --- a/deepmd/pt/loss/__init__.py +++ b/deepmd/pt/loss/__init__.py @@ -6,10 +6,8 @@ DOSLoss, ) from .ener import ( - EnergyStdLoss, -) -from .ener_hess import ( EnergyHessianStdLoss, + EnergyStdLoss, ) from .ener_spin import ( EnergySpinLoss, From d2553cfe483d805b75a548a0b08da11516395ef0 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 21:16:48 +0800 Subject: [PATCH 149/189] Update ener.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/ener.py | 433 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 433 insertions(+) diff --git a/deepmd/pt/loss/ener.py b/deepmd/pt/loss/ener.py index f40110a749..ba611c1843 100644 --- a/deepmd/pt/loss/ener.py +++ b/deepmd/pt/loss/ener.py @@ -411,3 +411,436 @@ def label_requirement(self) -> list[DataRequirementItem]: ) ) return label_requirement + + +class EnergyHessianStdLoss(TaskLoss): + def __init__( + self, + starter_learning_rate=1.0, + start_pref_e=0.0, + limit_pref_e=0.0, + start_pref_f=0.0, + limit_pref_f=0.0, + start_pref_v=0.0, + limit_pref_v=0.0, + start_pref_h=0.0, + limit_pref_h=0.0, + start_pref_ae: float = 0.0, + limit_pref_ae: float = 0.0, + start_pref_pf: float = 0.0, + limit_pref_pf: float = 0.0, + relative_f: Optional[float] = None, + enable_atom_ener_coeff: bool = False, + start_pref_gf: float = 0.0, + limit_pref_gf: float = 0.0, + numb_generalized_coord: int = 0, + use_l1_all: bool = False, + inference=False, + **kwargs, + ): + r"""Construct a layer to compute loss on energy, force and virial. + + Parameters + ---------- + starter_learning_rate : float + The learning rate at the start of the training. + start_pref_e : float + The prefactor of energy loss at the start of the training. + limit_pref_e : float + The prefactor of energy loss at the end of the training. + start_pref_f : float + The prefactor of force loss at the start of the training. + limit_pref_f : float + The prefactor of force loss at the end of the training. + start_pref_v : float + The prefactor of virial loss at the start of the training. + limit_pref_v : float + The prefactor of virial loss at the end of the training. + start_pref_h : float + The prefactor of hessian loss at the start of the training. + limit_pref_h : float + The prefactor of hessian loss at the end of the training. + start_pref_ae : float + The prefactor of atomic energy loss at the start of the training. + limit_pref_ae : float + The prefactor of atomic energy loss at the end of the training. + start_pref_pf : float + The prefactor of atomic prefactor force loss at the start of the training. + limit_pref_pf : float + The prefactor of atomic prefactor force loss at the end of the training. + relative_f : float + If provided, relative force error will be used in the loss. The difference + of force will be normalized by the magnitude of the force in the label with + a shift given by relative_f + enable_atom_ener_coeff : bool + if true, the energy will be computed as \sum_i c_i E_i + start_pref_gf : float + The prefactor of generalized force loss at the start of the training. + limit_pref_gf : float + The prefactor of generalized force loss at the end of the training. + numb_generalized_coord : int + The dimension of generalized coordinates. + use_l1_all : bool + Whether to use L1 loss, if False (default), it will use L2 loss. + inference : bool + If true, it will output all losses found in output, ignoring the pre-factors. + **kwargs + Other keyword arguments. + """ + super().__init__() + self.starter_learning_rate = starter_learning_rate + self.has_e = (start_pref_e != 0.0 and limit_pref_e != 0.0) or inference + self.has_f = (start_pref_f != 0.0 and limit_pref_f != 0.0) or inference + self.has_v = (start_pref_v != 0.0 and limit_pref_v != 0.0) or inference + self.has_h = (start_pref_h != 0.0 and limit_pref_h != 0.0) or inference + self.has_ae = (start_pref_ae != 0.0 and limit_pref_ae != 0.0) or inference + self.has_pf = (start_pref_pf != 0.0 and limit_pref_pf != 0.0) or inference + self.has_gf = start_pref_gf != 0.0 and limit_pref_gf != 0.0 + + self.start_pref_e = start_pref_e + self.limit_pref_e = limit_pref_e + self.start_pref_f = start_pref_f + self.limit_pref_f = limit_pref_f + self.start_pref_v = start_pref_v + self.limit_pref_v = limit_pref_v + self.start_pref_h = start_pref_h + self.limit_pref_h = limit_pref_h + self.start_pref_ae = start_pref_ae + self.limit_pref_ae = limit_pref_ae + self.start_pref_pf = start_pref_pf + self.limit_pref_pf = limit_pref_pf + self.start_pref_gf = start_pref_gf + self.limit_pref_gf = limit_pref_gf + self.relative_f = relative_f + self.enable_atom_ener_coeff = enable_atom_ener_coeff + self.numb_generalized_coord = numb_generalized_coord + if self.has_gf and self.numb_generalized_coord < 1: + raise RuntimeError( + "When generalized force loss is used, the dimension of generalized coordinates should be larger than 0" + ) + self.use_l1_all = use_l1_all + self.inference = inference + + def forward(self, input_dict, model, label, natoms, learning_rate, mae=False): + """Return loss on energy and force. + + Parameters + ---------- + input_dict : dict[str, torch.Tensor] + Model inputs. + model : torch.nn.Module + Model to be used to output the predictions. + label : dict[str, torch.Tensor] + Labels. + natoms : int + The local atom number. + + Returns + ------- + model_pred: dict[str, torch.Tensor] + Model predictions. + loss: torch.Tensor + Loss for model to minimize. + more_loss: dict[str, torch.Tensor] + Other losses for display. + """ + model_pred = model(**input_dict) + coef = learning_rate / self.starter_learning_rate + pref_e = self.limit_pref_e + (self.start_pref_e - self.limit_pref_e) * coef + pref_f = self.limit_pref_f + (self.start_pref_f - self.limit_pref_f) * coef + pref_v = self.limit_pref_v + (self.start_pref_v - self.limit_pref_v) * coef + pref_h = self.limit_pref_h + (self.start_pref_h - self.limit_pref_h) * coef + pref_ae = self.limit_pref_ae + (self.start_pref_ae - self.limit_pref_ae) * coef + pref_pf = self.limit_pref_pf + (self.start_pref_pf - self.limit_pref_pf) * coef + pref_gf = self.limit_pref_gf + (self.start_pref_gf - self.limit_pref_gf) * coef + + loss = torch.zeros(1, dtype=env.GLOBAL_PT_FLOAT_PRECISION, device=env.DEVICE)[0] + more_loss = {} + # more_loss['log_keys'] = [] # showed when validation on the fly + # more_loss['test_keys'] = [] # showed when doing dp test + atom_norm = 1.0 / natoms + if self.has_e and "energy" in model_pred and "energy" in label: + energy_pred = model_pred["energy"] + energy_label = label["energy"] + if self.enable_atom_ener_coeff and "atom_energy" in model_pred: + atom_ener_pred = model_pred["atom_energy"] + # when ener_coeff (\nu) is defined, the energy is defined as + # E = \sum_i \nu_i E_i + # instead of the sum of atomic energies. + # + # A case is that we want to train reaction energy + # A + B -> C + D + # E = - E(A) - E(B) + E(C) + E(D) + # A, B, C, D could be put far away from each other + atom_ener_coeff = label["atom_ener_coeff"] + atom_ener_coeff = atom_ener_coeff.reshape(atom_ener_pred.shape) + energy_pred = torch.sum(atom_ener_coeff * atom_ener_pred, dim=1) + find_energy = label.get("find_energy", 0.0) + pref_e = pref_e * find_energy + if not self.use_l1_all: + l2_ener_loss = torch.mean(torch.square(energy_pred - energy_label)) + if not self.inference: + more_loss["l2_ener_loss"] = self.display_if_exist( + l2_ener_loss.detach(), find_energy + ) + loss += atom_norm * (pref_e * l2_ener_loss) + rmse_e = l2_ener_loss.sqrt() * atom_norm + more_loss["rmse_e"] = self.display_if_exist( + rmse_e.detach(), find_energy + ) + # more_loss['log_keys'].append('rmse_e') + else: # use l1 and for all atoms + l1_ener_loss = F.l1_loss( + energy_pred.reshape(-1), + energy_label.reshape(-1), + reduction="sum", + ) + loss += pref_e * l1_ener_loss + more_loss["mae_e"] = self.display_if_exist( + F.l1_loss( + energy_pred.reshape(-1), + energy_label.reshape(-1), + reduction="mean", + ).detach(), + find_energy, + ) + # more_loss['log_keys'].append('rmse_e') + if mae: + mae_e = torch.mean(torch.abs(energy_pred - energy_label)) * atom_norm + more_loss["mae_e"] = self.display_if_exist(mae_e.detach(), find_energy) + mae_e_all = torch.mean(torch.abs(energy_pred - energy_label)) + more_loss["mae_e_all"] = self.display_if_exist( + mae_e_all.detach(), find_energy + ) + + if ( + (self.has_f or self.has_pf or self.relative_f or self.has_gf) + and "force" in model_pred + and "force" in label + ): + find_force = label.get("find_force", 0.0) + pref_f = pref_f * find_force + force_pred = model_pred["force"] + force_label = label["force"] + diff_f = (force_label - force_pred).reshape(-1) + + if self.relative_f is not None: + force_label_3 = force_label.reshape(-1, 3) + norm_f = force_label_3.norm(dim=1, keepdim=True) + self.relative_f + diff_f_3 = diff_f.reshape(-1, 3) + diff_f_3 = diff_f_3 / norm_f + diff_f = diff_f_3.reshape(-1) + + if self.has_f: + if not self.use_l1_all: + l2_force_loss = torch.mean(torch.square(diff_f)) + if not self.inference: + more_loss["l2_force_loss"] = self.display_if_exist( + l2_force_loss.detach(), find_force + ) + loss += (pref_f * l2_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) + rmse_f = l2_force_loss.sqrt() + more_loss["rmse_f"] = self.display_if_exist( + rmse_f.detach(), find_force + ) + else: + l1_force_loss = F.l1_loss(force_label, force_pred, reduction="none") + more_loss["mae_f"] = self.display_if_exist( + l1_force_loss.mean().detach(), find_force + ) + l1_force_loss = l1_force_loss.sum(-1).mean(-1).sum() + loss += (pref_f * l1_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) + if mae: + mae_f = torch.mean(torch.abs(diff_f)) + more_loss["mae_f"] = self.display_if_exist( + mae_f.detach(), find_force + ) + + if self.has_pf and "atom_pref" in label: + atom_pref = label["atom_pref"] + find_atom_pref = label.get("find_atom_pref", 0.0) + pref_pf = pref_pf * find_atom_pref + atom_pref_reshape = atom_pref.reshape(-1) + l2_pref_force_loss = (torch.square(diff_f) * atom_pref_reshape).mean() + if not self.inference: + more_loss["l2_pref_force_loss"] = self.display_if_exist( + l2_pref_force_loss.detach(), find_atom_pref + ) + loss += (pref_pf * l2_pref_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) + rmse_pf = l2_pref_force_loss.sqrt() + more_loss["rmse_pf"] = self.display_if_exist( + rmse_pf.detach(), find_atom_pref + ) + + if self.has_gf and "drdq" in label: + drdq = label["drdq"] + find_drdq = label.get("find_drdq", 0.0) + pref_gf = pref_gf * find_drdq + force_reshape_nframes = force_pred.reshape(-1, natoms * 3) + force_label_reshape_nframes = force_label.reshape(-1, natoms * 3) + drdq_reshape = drdq.reshape(-1, natoms * 3, self.numb_generalized_coord) + gen_force_label = torch.einsum( + "bij,bi->bj", drdq_reshape, force_label_reshape_nframes + ) + gen_force = torch.einsum( + "bij,bi->bj", drdq_reshape, force_reshape_nframes + ) + diff_gen_force = gen_force_label - gen_force + l2_gen_force_loss = torch.square(diff_gen_force).mean() + if not self.inference: + more_loss["l2_gen_force_loss"] = self.display_if_exist( + l2_gen_force_loss.detach(), find_drdq + ) + loss += (pref_gf * l2_gen_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) + rmse_gf = l2_gen_force_loss.sqrt() + more_loss["rmse_gf"] = self.display_if_exist( + rmse_gf.detach(), find_drdq + ) + + if self.has_v and "virial" in model_pred and "virial" in label: + find_virial = label.get("find_virial", 0.0) + pref_v = pref_v * find_virial + diff_v = label["virial"] - model_pred["virial"].reshape(-1, 9) + l2_virial_loss = torch.mean(torch.square(diff_v)) + if not self.inference: + more_loss["l2_virial_loss"] = self.display_if_exist( + l2_virial_loss.detach(), find_virial + ) + loss += atom_norm * (pref_v * l2_virial_loss) + rmse_v = l2_virial_loss.sqrt() * atom_norm + more_loss["rmse_v"] = self.display_if_exist(rmse_v.detach(), find_virial) + if mae: + mae_v = torch.mean(torch.abs(diff_v)) * atom_norm + more_loss["mae_v"] = self.display_if_exist(mae_v.detach(), find_virial) + + if self.has_h and "hessian" in model_pred and "hessian" in label: + find_hessian = label.get("find_hessian", 0.0) + pref_h = pref_h * find_hessian + diff_h = label["hessian"].reshape( + -1, + ) - model_pred["hessian"].reshape( + -1, + ) # tbd + l2_hessian_loss = torch.mean(torch.square(diff_h)) + if not self.inference: + more_loss["l2_hessian_loss"] = self.display_if_exist( + l2_hessian_loss.detach(), find_hessian + ) + loss += pref_h * l2_hessian_loss # tbd + rmse_h = l2_hessian_loss.sqrt() # tbd + more_loss["rmse_h"] = self.display_if_exist(rmse_h.detach(), find_hessian) + if mae: + mae_h = torch.mean(torch.abs(diff_h)) # tbd + more_loss["mae_h"] = self.display_if_exist(mae_h.detach(), find_hessian) + + if self.has_ae and "atom_energy" in model_pred and "atom_ener" in label: + atom_ener = model_pred["atom_energy"] + atom_ener_label = label["atom_ener"] + find_atom_ener = label.get("find_atom_ener", 0.0) + pref_ae = pref_ae * find_atom_ener + atom_ener_reshape = atom_ener.reshape(-1) + atom_ener_label_reshape = atom_ener_label.reshape(-1) + l2_atom_ener_loss = torch.square( + atom_ener_label_reshape - atom_ener_reshape + ).mean() + if not self.inference: + more_loss["l2_atom_ener_loss"] = self.display_if_exist( + l2_atom_ener_loss.detach(), find_atom_ener + ) + loss += (pref_ae * l2_atom_ener_loss).to(GLOBAL_PT_FLOAT_PRECISION) + rmse_ae = l2_atom_ener_loss.sqrt() + more_loss["rmse_ae"] = self.display_if_exist( + rmse_ae.detach(), find_atom_ener + ) + + if not self.inference: + more_loss["rmse"] = torch.sqrt(loss.detach()) + return model_pred, loss, more_loss + + @property + def label_requirement(self) -> list[DataRequirementItem]: + """Return data label requirements needed for this loss calculation.""" + label_requirement = [] + if self.has_e: + label_requirement.append( + DataRequirementItem( + "energy", + ndof=1, + atomic=False, + must=False, + high_prec=True, + ) + ) + if self.has_f: + label_requirement.append( + DataRequirementItem( + "force", + ndof=3, + atomic=True, + must=False, + high_prec=False, + ) + ) + if self.has_v: + label_requirement.append( + DataRequirementItem( + "virial", + ndof=9, + atomic=False, + must=False, + high_prec=False, + ) + ) + if self.has_h: + label_requirement.append( + DataRequirementItem( + "hessian", + ndof=1, # 9=3*3 --> 3N*3N=ndof*natoms*natoms + atomic=True, + must=False, + high_prec=False, + ) + ) + if self.has_ae: + label_requirement.append( + DataRequirementItem( + "atom_ener", + ndof=1, + atomic=True, + must=False, + high_prec=False, + ) + ) + if self.has_pf: + label_requirement.append( + DataRequirementItem( + "atom_pref", + ndof=1, + atomic=True, + must=False, + high_prec=False, + repeat=3, + ) + ) + if self.has_gf: + label_requirement.append( + DataRequirementItem( + "drdq", + ndof=self.numb_generalized_coord * 3, + atomic=True, + must=False, + high_prec=False, + ) + ) + if self.enable_atom_ener_coeff: + label_requirement.append( + DataRequirementItem( + "atom_ener_coeff", + ndof=1, + atomic=True, + must=False, + high_prec=False, + default=1.0, + ) + ) + return label_requirement From 5eb7e4604852dd1aeb6c0225981344f50ec5fe29 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 21:17:43 +0800 Subject: [PATCH 150/189] Delete deepmd/pt/loss/ener_hess.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/loss/ener_hess.py | 454 ------------------------------------ 1 file changed, 454 deletions(-) delete mode 100644 deepmd/pt/loss/ener_hess.py diff --git a/deepmd/pt/loss/ener_hess.py b/deepmd/pt/loss/ener_hess.py deleted file mode 100644 index 498caab578..0000000000 --- a/deepmd/pt/loss/ener_hess.py +++ /dev/null @@ -1,454 +0,0 @@ -# SPDX-License-Identifier: LGPL-3.0-or-later - -from typing import ( - Optional, -) - -import torch -import torch.nn.functional as F - -from deepmd.pt.loss.loss import ( - TaskLoss, -) -from deepmd.pt.utils import ( - env, -) -from deepmd.pt.utils.env import ( - GLOBAL_PT_FLOAT_PRECISION, -) -from deepmd.utils.data import ( - DataRequirementItem, -) - - -class EnergyHessianStdLoss(TaskLoss): - def __init__( - self, - starter_learning_rate=1.0, - start_pref_e=0.0, - limit_pref_e=0.0, - start_pref_f=0.0, - limit_pref_f=0.0, - start_pref_v=0.0, - limit_pref_v=0.0, - start_pref_h=0.0, - limit_pref_h=0.0, - start_pref_ae: float = 0.0, - limit_pref_ae: float = 0.0, - start_pref_pf: float = 0.0, - limit_pref_pf: float = 0.0, - relative_f: Optional[float] = None, - enable_atom_ener_coeff: bool = False, - start_pref_gf: float = 0.0, - limit_pref_gf: float = 0.0, - numb_generalized_coord: int = 0, - use_l1_all: bool = False, - inference=False, - **kwargs, - ): - r"""Construct a layer to compute loss on energy, force and virial. - - Parameters - ---------- - starter_learning_rate : float - The learning rate at the start of the training. - start_pref_e : float - The prefactor of energy loss at the start of the training. - limit_pref_e : float - The prefactor of energy loss at the end of the training. - start_pref_f : float - The prefactor of force loss at the start of the training. - limit_pref_f : float - The prefactor of force loss at the end of the training. - start_pref_v : float - The prefactor of virial loss at the start of the training. - limit_pref_v : float - The prefactor of virial loss at the end of the training. - start_pref_h : float - The prefactor of hessian loss at the start of the training. - limit_pref_h : float - The prefactor of hessian loss at the end of the training. - start_pref_ae : float - The prefactor of atomic energy loss at the start of the training. - limit_pref_ae : float - The prefactor of atomic energy loss at the end of the training. - start_pref_pf : float - The prefactor of atomic prefactor force loss at the start of the training. - limit_pref_pf : float - The prefactor of atomic prefactor force loss at the end of the training. - relative_f : float - If provided, relative force error will be used in the loss. The difference - of force will be normalized by the magnitude of the force in the label with - a shift given by relative_f - enable_atom_ener_coeff : bool - if true, the energy will be computed as \sum_i c_i E_i - start_pref_gf : float - The prefactor of generalized force loss at the start of the training. - limit_pref_gf : float - The prefactor of generalized force loss at the end of the training. - numb_generalized_coord : int - The dimension of generalized coordinates. - use_l1_all : bool - Whether to use L1 loss, if False (default), it will use L2 loss. - inference : bool - If true, it will output all losses found in output, ignoring the pre-factors. - **kwargs - Other keyword arguments. - """ - super().__init__() - self.starter_learning_rate = starter_learning_rate - self.has_e = (start_pref_e != 0.0 and limit_pref_e != 0.0) or inference - self.has_f = (start_pref_f != 0.0 and limit_pref_f != 0.0) or inference - self.has_v = (start_pref_v != 0.0 and limit_pref_v != 0.0) or inference - self.has_h = (start_pref_h != 0.0 and limit_pref_h != 0.0) or inference - self.has_ae = (start_pref_ae != 0.0 and limit_pref_ae != 0.0) or inference - self.has_pf = (start_pref_pf != 0.0 and limit_pref_pf != 0.0) or inference - self.has_gf = start_pref_gf != 0.0 and limit_pref_gf != 0.0 - - self.start_pref_e = start_pref_e - self.limit_pref_e = limit_pref_e - self.start_pref_f = start_pref_f - self.limit_pref_f = limit_pref_f - self.start_pref_v = start_pref_v - self.limit_pref_v = limit_pref_v - self.start_pref_h = start_pref_h - self.limit_pref_h = limit_pref_h - self.start_pref_ae = start_pref_ae - self.limit_pref_ae = limit_pref_ae - self.start_pref_pf = start_pref_pf - self.limit_pref_pf = limit_pref_pf - self.start_pref_gf = start_pref_gf - self.limit_pref_gf = limit_pref_gf - self.relative_f = relative_f - self.enable_atom_ener_coeff = enable_atom_ener_coeff - self.numb_generalized_coord = numb_generalized_coord - if self.has_gf and self.numb_generalized_coord < 1: - raise RuntimeError( - "When generalized force loss is used, the dimension of generalized coordinates should be larger than 0" - ) - self.use_l1_all = use_l1_all - self.inference = inference - - def forward(self, input_dict, model, label, natoms, learning_rate, mae=False): - """Return loss on energy and force. - - Parameters - ---------- - input_dict : dict[str, torch.Tensor] - Model inputs. - model : torch.nn.Module - Model to be used to output the predictions. - label : dict[str, torch.Tensor] - Labels. - natoms : int - The local atom number. - - Returns - ------- - model_pred: dict[str, torch.Tensor] - Model predictions. - loss: torch.Tensor - Loss for model to minimize. - more_loss: dict[str, torch.Tensor] - Other losses for display. - """ - model_pred = model(**input_dict) - coef = learning_rate / self.starter_learning_rate - pref_e = self.limit_pref_e + (self.start_pref_e - self.limit_pref_e) * coef - pref_f = self.limit_pref_f + (self.start_pref_f - self.limit_pref_f) * coef - pref_v = self.limit_pref_v + (self.start_pref_v - self.limit_pref_v) * coef - pref_h = self.limit_pref_h + (self.start_pref_h - self.limit_pref_h) * coef - pref_ae = self.limit_pref_ae + (self.start_pref_ae - self.limit_pref_ae) * coef - pref_pf = self.limit_pref_pf + (self.start_pref_pf - self.limit_pref_pf) * coef - pref_gf = self.limit_pref_gf + (self.start_pref_gf - self.limit_pref_gf) * coef - - loss = torch.zeros(1, dtype=env.GLOBAL_PT_FLOAT_PRECISION, device=env.DEVICE)[0] - more_loss = {} - # more_loss['log_keys'] = [] # showed when validation on the fly - # more_loss['test_keys'] = [] # showed when doing dp test - atom_norm = 1.0 / natoms - if self.has_e and "energy" in model_pred and "energy" in label: - energy_pred = model_pred["energy"] - energy_label = label["energy"] - if self.enable_atom_ener_coeff and "atom_energy" in model_pred: - atom_ener_pred = model_pred["atom_energy"] - # when ener_coeff (\nu) is defined, the energy is defined as - # E = \sum_i \nu_i E_i - # instead of the sum of atomic energies. - # - # A case is that we want to train reaction energy - # A + B -> C + D - # E = - E(A) - E(B) + E(C) + E(D) - # A, B, C, D could be put far away from each other - atom_ener_coeff = label["atom_ener_coeff"] - atom_ener_coeff = atom_ener_coeff.reshape(atom_ener_pred.shape) - energy_pred = torch.sum(atom_ener_coeff * atom_ener_pred, dim=1) - find_energy = label.get("find_energy", 0.0) - pref_e = pref_e * find_energy - if not self.use_l1_all: - l2_ener_loss = torch.mean(torch.square(energy_pred - energy_label)) - if not self.inference: - more_loss["l2_ener_loss"] = self.display_if_exist( - l2_ener_loss.detach(), find_energy - ) - loss += atom_norm * (pref_e * l2_ener_loss) - rmse_e = l2_ener_loss.sqrt() * atom_norm - more_loss["rmse_e"] = self.display_if_exist( - rmse_e.detach(), find_energy - ) - # more_loss['log_keys'].append('rmse_e') - else: # use l1 and for all atoms - l1_ener_loss = F.l1_loss( - energy_pred.reshape(-1), - energy_label.reshape(-1), - reduction="sum", - ) - loss += pref_e * l1_ener_loss - more_loss["mae_e"] = self.display_if_exist( - F.l1_loss( - energy_pred.reshape(-1), - energy_label.reshape(-1), - reduction="mean", - ).detach(), - find_energy, - ) - # more_loss['log_keys'].append('rmse_e') - if mae: - mae_e = torch.mean(torch.abs(energy_pred - energy_label)) * atom_norm - more_loss["mae_e"] = self.display_if_exist(mae_e.detach(), find_energy) - mae_e_all = torch.mean(torch.abs(energy_pred - energy_label)) - more_loss["mae_e_all"] = self.display_if_exist( - mae_e_all.detach(), find_energy - ) - - if ( - (self.has_f or self.has_pf or self.relative_f or self.has_gf) - and "force" in model_pred - and "force" in label - ): - find_force = label.get("find_force", 0.0) - pref_f = pref_f * find_force - force_pred = model_pred["force"] - force_label = label["force"] - diff_f = (force_label - force_pred).reshape(-1) - - if self.relative_f is not None: - force_label_3 = force_label.reshape(-1, 3) - norm_f = force_label_3.norm(dim=1, keepdim=True) + self.relative_f - diff_f_3 = diff_f.reshape(-1, 3) - diff_f_3 = diff_f_3 / norm_f - diff_f = diff_f_3.reshape(-1) - - if self.has_f: - if not self.use_l1_all: - l2_force_loss = torch.mean(torch.square(diff_f)) - if not self.inference: - more_loss["l2_force_loss"] = self.display_if_exist( - l2_force_loss.detach(), find_force - ) - loss += (pref_f * l2_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) - rmse_f = l2_force_loss.sqrt() - more_loss["rmse_f"] = self.display_if_exist( - rmse_f.detach(), find_force - ) - else: - l1_force_loss = F.l1_loss(force_label, force_pred, reduction="none") - more_loss["mae_f"] = self.display_if_exist( - l1_force_loss.mean().detach(), find_force - ) - l1_force_loss = l1_force_loss.sum(-1).mean(-1).sum() - loss += (pref_f * l1_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) - if mae: - mae_f = torch.mean(torch.abs(diff_f)) - more_loss["mae_f"] = self.display_if_exist( - mae_f.detach(), find_force - ) - - if self.has_pf and "atom_pref" in label: - atom_pref = label["atom_pref"] - find_atom_pref = label.get("find_atom_pref", 0.0) - pref_pf = pref_pf * find_atom_pref - atom_pref_reshape = atom_pref.reshape(-1) - l2_pref_force_loss = (torch.square(diff_f) * atom_pref_reshape).mean() - if not self.inference: - more_loss["l2_pref_force_loss"] = self.display_if_exist( - l2_pref_force_loss.detach(), find_atom_pref - ) - loss += (pref_pf * l2_pref_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) - rmse_pf = l2_pref_force_loss.sqrt() - more_loss["rmse_pf"] = self.display_if_exist( - rmse_pf.detach(), find_atom_pref - ) - - if self.has_gf and "drdq" in label: - drdq = label["drdq"] - find_drdq = label.get("find_drdq", 0.0) - pref_gf = pref_gf * find_drdq - force_reshape_nframes = force_pred.reshape(-1, natoms * 3) - force_label_reshape_nframes = force_label.reshape(-1, natoms * 3) - drdq_reshape = drdq.reshape(-1, natoms * 3, self.numb_generalized_coord) - gen_force_label = torch.einsum( - "bij,bi->bj", drdq_reshape, force_label_reshape_nframes - ) - gen_force = torch.einsum( - "bij,bi->bj", drdq_reshape, force_reshape_nframes - ) - diff_gen_force = gen_force_label - gen_force - l2_gen_force_loss = torch.square(diff_gen_force).mean() - if not self.inference: - more_loss["l2_gen_force_loss"] = self.display_if_exist( - l2_gen_force_loss.detach(), find_drdq - ) - loss += (pref_gf * l2_gen_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) - rmse_gf = l2_gen_force_loss.sqrt() - more_loss["rmse_gf"] = self.display_if_exist( - rmse_gf.detach(), find_drdq - ) - - if self.has_v and "virial" in model_pred and "virial" in label: - find_virial = label.get("find_virial", 0.0) - pref_v = pref_v * find_virial - diff_v = label["virial"] - model_pred["virial"].reshape(-1, 9) - l2_virial_loss = torch.mean(torch.square(diff_v)) - if not self.inference: - more_loss["l2_virial_loss"] = self.display_if_exist( - l2_virial_loss.detach(), find_virial - ) - loss += atom_norm * (pref_v * l2_virial_loss) - rmse_v = l2_virial_loss.sqrt() * atom_norm - more_loss["rmse_v"] = self.display_if_exist(rmse_v.detach(), find_virial) - if mae: - mae_v = torch.mean(torch.abs(diff_v)) * atom_norm - more_loss["mae_v"] = self.display_if_exist(mae_v.detach(), find_virial) - - if self.has_h and "hessian" in model_pred and "hessian" in label: - find_hessian = label.get("find_hessian", 0.0) - pref_h = pref_h * find_hessian - diff_h = label["hessian"].reshape( - -1, - ) - model_pred["hessian"].reshape( - -1, - ) # tbd - l2_hessian_loss = torch.mean(torch.square(diff_h)) - if not self.inference: - more_loss["l2_hessian_loss"] = self.display_if_exist( - l2_hessian_loss.detach(), find_hessian - ) - loss += pref_h * l2_hessian_loss # tbd - rmse_h = l2_hessian_loss.sqrt() # tbd - more_loss["rmse_h"] = self.display_if_exist(rmse_h.detach(), find_hessian) - if mae: - mae_h = torch.mean(torch.abs(diff_h)) # tbd - more_loss["mae_h"] = self.display_if_exist(mae_h.detach(), find_hessian) - - if self.has_ae and "atom_energy" in model_pred and "atom_ener" in label: - atom_ener = model_pred["atom_energy"] - atom_ener_label = label["atom_ener"] - find_atom_ener = label.get("find_atom_ener", 0.0) - pref_ae = pref_ae * find_atom_ener - atom_ener_reshape = atom_ener.reshape(-1) - atom_ener_label_reshape = atom_ener_label.reshape(-1) - l2_atom_ener_loss = torch.square( - atom_ener_label_reshape - atom_ener_reshape - ).mean() - if not self.inference: - more_loss["l2_atom_ener_loss"] = self.display_if_exist( - l2_atom_ener_loss.detach(), find_atom_ener - ) - loss += (pref_ae * l2_atom_ener_loss).to(GLOBAL_PT_FLOAT_PRECISION) - rmse_ae = l2_atom_ener_loss.sqrt() - more_loss["rmse_ae"] = self.display_if_exist( - rmse_ae.detach(), find_atom_ener - ) - - if not self.inference: - more_loss["rmse"] = torch.sqrt(loss.detach()) - return model_pred, loss, more_loss - - @property - def label_requirement(self) -> list[DataRequirementItem]: - """Return data label requirements needed for this loss calculation.""" - label_requirement = [] - if self.has_e: - label_requirement.append( - DataRequirementItem( - "energy", - ndof=1, - atomic=False, - must=False, - high_prec=True, - ) - ) - if self.has_f: - label_requirement.append( - DataRequirementItem( - "force", - ndof=3, - atomic=True, - must=False, - high_prec=False, - ) - ) - if self.has_v: - label_requirement.append( - DataRequirementItem( - "virial", - ndof=9, - atomic=False, - must=False, - high_prec=False, - ) - ) - if self.has_h: - label_requirement.append( - DataRequirementItem( - "hessian", - ndof=1, # 9=3*3 --> 3N*3N=ndof*natoms*natoms - atomic=True, - must=False, - high_prec=False, - ) - ) - if self.has_ae: - label_requirement.append( - DataRequirementItem( - "atom_ener", - ndof=1, - atomic=True, - must=False, - high_prec=False, - ) - ) - if self.has_pf: - label_requirement.append( - DataRequirementItem( - "atom_pref", - ndof=1, - atomic=True, - must=False, - high_prec=False, - repeat=3, - ) - ) - if self.has_gf: - label_requirement.append( - DataRequirementItem( - "drdq", - ndof=self.numb_generalized_coord * 3, - atomic=True, - must=False, - high_prec=False, - ) - ) - if self.enable_atom_ener_coeff: - label_requirement.append( - DataRequirementItem( - "atom_ener_coeff", - ndof=1, - atomic=True, - must=False, - high_prec=False, - default=1.0, - ) - ) - return label_requirement From 4868f55412ec073d723d87a59d2a997d9e2a998f Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 23 Oct 2024 21:21:22 +0800 Subject: [PATCH 151/189] Update train-energy-hessian.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/train-energy-hessian.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md index a8b350b1b3..6cb80bd943 100644 --- a/doc/model/train-energy-hessian.md +++ b/doc/model/train-energy-hessian.md @@ -8,7 +8,7 @@ To train a model that takes Hessian matrices, i.e., the second order derivatives ## Energy Hessian Loss -If you want to train with Hessians, you are expected to add the start and limit prefactors of Hessians, i.e., {ref}`start_pref_h ` and {ref}`limit_pref_h ` to the {ref}`loss ` section in the `input.json`: +If you want to train with Hessians, you are expected to add the start and limit prefactors of Hessians, i.e., {ref}`start_pref_h ` and {ref}`limit_pref_h ` to the {ref}`loss ` section in the `input.json`: ```json "loss": { @@ -24,9 +24,9 @@ If you want to train with Hessians, you are expected to add the start and limit }, ``` -The options {ref}`start_pref_e `, {ref}`limit_pref_e `, {ref}`start_pref_f `, {ref}`limit_pref_f `, {ref}`start_pref_v ` and {ref}`limit_pref_v ` determine the start and limit prefactors of energy, force, and virial, respectively. The calculation and definition of Hessian loss are the same as for the other terms. +The options {ref}`start_pref_e `, {ref}`limit_pref_e `, {ref}`start_pref_f `, {ref}`limit_pref_f `, {ref}`start_pref_v ` and {ref}`limit_pref_v ` determine the start and limit prefactors of energy, force, and virial, respectively. The calculation and definition of Hessian loss are the same as for the other terms. -If one does not want to train with virial, then he/she may set the virial prefactors {ref}`start_pref_v ` and {ref}`limit_pref_v ` to 0. +If one does not want to train with virial, then he/she may set the virial prefactors {ref}`start_pref_v ` and {ref}`limit_pref_v ` to 0. ## Hessian format in PyTorch From 3fe1c8d5c15d410b8c96d5356a4975020712f76e Mon Sep 17 00:00:00 2001 From: 1azyking <137011979@qq.com> Date: Wed, 23 Oct 2024 22:23:26 +0800 Subject: [PATCH 152/189] Merge ener_hess_loss to ener_loss --- deepmd/pt/infer/deep_eval.py | 5 +- deepmd/pt/loss/ener.py | 387 +-------------------------- source/tests/common/test_examples.py | 4 +- 3 files changed, 18 insertions(+), 378 deletions(-) diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index b14e4e7131..084c9b282d 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -158,7 +158,9 @@ def __init__( self._has_spin = getattr(self.dp.model["Default"], "has_spin", False) if callable(self._has_spin): self._has_spin = self._has_spin() - self._has_hessian = hasattr(self, "input_param") and getattr(self.input_param, "hessian_mode", False) + self._has_hessian = hasattr(self, "input_param") and getattr( + self.input_param, "hessian_mode", False + ) if callable(self._has_hessian): self._has_hessian = self._has_hessian() @@ -665,4 +667,3 @@ def eval_descriptor( descriptor = model.eval_descriptor() model.set_eval_descriptor_hook(False) return to_numpy_array(descriptor) - diff --git a/deepmd/pt/loss/ener.py b/deepmd/pt/loss/ener.py index ba611c1843..7c495d09d2 100644 --- a/deepmd/pt/loss/ener.py +++ b/deepmd/pt/loss/ener.py @@ -413,305 +413,36 @@ def label_requirement(self) -> list[DataRequirementItem]: return label_requirement -class EnergyHessianStdLoss(TaskLoss): +class EnergyHessianStdLoss(EnergyStdLoss): def __init__( self, - starter_learning_rate=1.0, - start_pref_e=0.0, - limit_pref_e=0.0, - start_pref_f=0.0, - limit_pref_f=0.0, - start_pref_v=0.0, - limit_pref_v=0.0, start_pref_h=0.0, limit_pref_h=0.0, - start_pref_ae: float = 0.0, - limit_pref_ae: float = 0.0, - start_pref_pf: float = 0.0, - limit_pref_pf: float = 0.0, - relative_f: Optional[float] = None, - enable_atom_ener_coeff: bool = False, - start_pref_gf: float = 0.0, - limit_pref_gf: float = 0.0, - numb_generalized_coord: int = 0, - use_l1_all: bool = False, - inference=False, **kwargs, ): - r"""Construct a layer to compute loss on energy, force and virial. + r"""Enable the layer to compute loss on hessian. Parameters ---------- - starter_learning_rate : float - The learning rate at the start of the training. - start_pref_e : float - The prefactor of energy loss at the start of the training. - limit_pref_e : float - The prefactor of energy loss at the end of the training. - start_pref_f : float - The prefactor of force loss at the start of the training. - limit_pref_f : float - The prefactor of force loss at the end of the training. - start_pref_v : float - The prefactor of virial loss at the start of the training. - limit_pref_v : float - The prefactor of virial loss at the end of the training. start_pref_h : float The prefactor of hessian loss at the start of the training. limit_pref_h : float The prefactor of hessian loss at the end of the training. - start_pref_ae : float - The prefactor of atomic energy loss at the start of the training. - limit_pref_ae : float - The prefactor of atomic energy loss at the end of the training. - start_pref_pf : float - The prefactor of atomic prefactor force loss at the start of the training. - limit_pref_pf : float - The prefactor of atomic prefactor force loss at the end of the training. - relative_f : float - If provided, relative force error will be used in the loss. The difference - of force will be normalized by the magnitude of the force in the label with - a shift given by relative_f - enable_atom_ener_coeff : bool - if true, the energy will be computed as \sum_i c_i E_i - start_pref_gf : float - The prefactor of generalized force loss at the start of the training. - limit_pref_gf : float - The prefactor of generalized force loss at the end of the training. - numb_generalized_coord : int - The dimension of generalized coordinates. - use_l1_all : bool - Whether to use L1 loss, if False (default), it will use L2 loss. - inference : bool - If true, it will output all losses found in output, ignoring the pre-factors. **kwargs Other keyword arguments. """ - super().__init__() - self.starter_learning_rate = starter_learning_rate - self.has_e = (start_pref_e != 0.0 and limit_pref_e != 0.0) or inference - self.has_f = (start_pref_f != 0.0 and limit_pref_f != 0.0) or inference - self.has_v = (start_pref_v != 0.0 and limit_pref_v != 0.0) or inference - self.has_h = (start_pref_h != 0.0 and limit_pref_h != 0.0) or inference - self.has_ae = (start_pref_ae != 0.0 and limit_pref_ae != 0.0) or inference - self.has_pf = (start_pref_pf != 0.0 and limit_pref_pf != 0.0) or inference - self.has_gf = start_pref_gf != 0.0 and limit_pref_gf != 0.0 + super().__init__(**kwargs) + self.has_h = (start_pref_h != 0.0 and limit_pref_h != 0.0) or self.inference - self.start_pref_e = start_pref_e - self.limit_pref_e = limit_pref_e - self.start_pref_f = start_pref_f - self.limit_pref_f = limit_pref_f - self.start_pref_v = start_pref_v - self.limit_pref_v = limit_pref_v self.start_pref_h = start_pref_h self.limit_pref_h = limit_pref_h - self.start_pref_ae = start_pref_ae - self.limit_pref_ae = limit_pref_ae - self.start_pref_pf = start_pref_pf - self.limit_pref_pf = limit_pref_pf - self.start_pref_gf = start_pref_gf - self.limit_pref_gf = limit_pref_gf - self.relative_f = relative_f - self.enable_atom_ener_coeff = enable_atom_ener_coeff - self.numb_generalized_coord = numb_generalized_coord - if self.has_gf and self.numb_generalized_coord < 1: - raise RuntimeError( - "When generalized force loss is used, the dimension of generalized coordinates should be larger than 0" - ) - self.use_l1_all = use_l1_all - self.inference = inference def forward(self, input_dict, model, label, natoms, learning_rate, mae=False): - """Return loss on energy and force. - - Parameters - ---------- - input_dict : dict[str, torch.Tensor] - Model inputs. - model : torch.nn.Module - Model to be used to output the predictions. - label : dict[str, torch.Tensor] - Labels. - natoms : int - The local atom number. - - Returns - ------- - model_pred: dict[str, torch.Tensor] - Model predictions. - loss: torch.Tensor - Loss for model to minimize. - more_loss: dict[str, torch.Tensor] - Other losses for display. - """ - model_pred = model(**input_dict) + model_pred, loss, more_loss = super().forward( + input_dict, model, label, natoms, learning_rate, mae=mae + ) coef = learning_rate / self.starter_learning_rate - pref_e = self.limit_pref_e + (self.start_pref_e - self.limit_pref_e) * coef - pref_f = self.limit_pref_f + (self.start_pref_f - self.limit_pref_f) * coef - pref_v = self.limit_pref_v + (self.start_pref_v - self.limit_pref_v) * coef pref_h = self.limit_pref_h + (self.start_pref_h - self.limit_pref_h) * coef - pref_ae = self.limit_pref_ae + (self.start_pref_ae - self.limit_pref_ae) * coef - pref_pf = self.limit_pref_pf + (self.start_pref_pf - self.limit_pref_pf) * coef - pref_gf = self.limit_pref_gf + (self.start_pref_gf - self.limit_pref_gf) * coef - - loss = torch.zeros(1, dtype=env.GLOBAL_PT_FLOAT_PRECISION, device=env.DEVICE)[0] - more_loss = {} - # more_loss['log_keys'] = [] # showed when validation on the fly - # more_loss['test_keys'] = [] # showed when doing dp test - atom_norm = 1.0 / natoms - if self.has_e and "energy" in model_pred and "energy" in label: - energy_pred = model_pred["energy"] - energy_label = label["energy"] - if self.enable_atom_ener_coeff and "atom_energy" in model_pred: - atom_ener_pred = model_pred["atom_energy"] - # when ener_coeff (\nu) is defined, the energy is defined as - # E = \sum_i \nu_i E_i - # instead of the sum of atomic energies. - # - # A case is that we want to train reaction energy - # A + B -> C + D - # E = - E(A) - E(B) + E(C) + E(D) - # A, B, C, D could be put far away from each other - atom_ener_coeff = label["atom_ener_coeff"] - atom_ener_coeff = atom_ener_coeff.reshape(atom_ener_pred.shape) - energy_pred = torch.sum(atom_ener_coeff * atom_ener_pred, dim=1) - find_energy = label.get("find_energy", 0.0) - pref_e = pref_e * find_energy - if not self.use_l1_all: - l2_ener_loss = torch.mean(torch.square(energy_pred - energy_label)) - if not self.inference: - more_loss["l2_ener_loss"] = self.display_if_exist( - l2_ener_loss.detach(), find_energy - ) - loss += atom_norm * (pref_e * l2_ener_loss) - rmse_e = l2_ener_loss.sqrt() * atom_norm - more_loss["rmse_e"] = self.display_if_exist( - rmse_e.detach(), find_energy - ) - # more_loss['log_keys'].append('rmse_e') - else: # use l1 and for all atoms - l1_ener_loss = F.l1_loss( - energy_pred.reshape(-1), - energy_label.reshape(-1), - reduction="sum", - ) - loss += pref_e * l1_ener_loss - more_loss["mae_e"] = self.display_if_exist( - F.l1_loss( - energy_pred.reshape(-1), - energy_label.reshape(-1), - reduction="mean", - ).detach(), - find_energy, - ) - # more_loss['log_keys'].append('rmse_e') - if mae: - mae_e = torch.mean(torch.abs(energy_pred - energy_label)) * atom_norm - more_loss["mae_e"] = self.display_if_exist(mae_e.detach(), find_energy) - mae_e_all = torch.mean(torch.abs(energy_pred - energy_label)) - more_loss["mae_e_all"] = self.display_if_exist( - mae_e_all.detach(), find_energy - ) - - if ( - (self.has_f or self.has_pf or self.relative_f or self.has_gf) - and "force" in model_pred - and "force" in label - ): - find_force = label.get("find_force", 0.0) - pref_f = pref_f * find_force - force_pred = model_pred["force"] - force_label = label["force"] - diff_f = (force_label - force_pred).reshape(-1) - - if self.relative_f is not None: - force_label_3 = force_label.reshape(-1, 3) - norm_f = force_label_3.norm(dim=1, keepdim=True) + self.relative_f - diff_f_3 = diff_f.reshape(-1, 3) - diff_f_3 = diff_f_3 / norm_f - diff_f = diff_f_3.reshape(-1) - - if self.has_f: - if not self.use_l1_all: - l2_force_loss = torch.mean(torch.square(diff_f)) - if not self.inference: - more_loss["l2_force_loss"] = self.display_if_exist( - l2_force_loss.detach(), find_force - ) - loss += (pref_f * l2_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) - rmse_f = l2_force_loss.sqrt() - more_loss["rmse_f"] = self.display_if_exist( - rmse_f.detach(), find_force - ) - else: - l1_force_loss = F.l1_loss(force_label, force_pred, reduction="none") - more_loss["mae_f"] = self.display_if_exist( - l1_force_loss.mean().detach(), find_force - ) - l1_force_loss = l1_force_loss.sum(-1).mean(-1).sum() - loss += (pref_f * l1_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) - if mae: - mae_f = torch.mean(torch.abs(diff_f)) - more_loss["mae_f"] = self.display_if_exist( - mae_f.detach(), find_force - ) - - if self.has_pf and "atom_pref" in label: - atom_pref = label["atom_pref"] - find_atom_pref = label.get("find_atom_pref", 0.0) - pref_pf = pref_pf * find_atom_pref - atom_pref_reshape = atom_pref.reshape(-1) - l2_pref_force_loss = (torch.square(diff_f) * atom_pref_reshape).mean() - if not self.inference: - more_loss["l2_pref_force_loss"] = self.display_if_exist( - l2_pref_force_loss.detach(), find_atom_pref - ) - loss += (pref_pf * l2_pref_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) - rmse_pf = l2_pref_force_loss.sqrt() - more_loss["rmse_pf"] = self.display_if_exist( - rmse_pf.detach(), find_atom_pref - ) - - if self.has_gf and "drdq" in label: - drdq = label["drdq"] - find_drdq = label.get("find_drdq", 0.0) - pref_gf = pref_gf * find_drdq - force_reshape_nframes = force_pred.reshape(-1, natoms * 3) - force_label_reshape_nframes = force_label.reshape(-1, natoms * 3) - drdq_reshape = drdq.reshape(-1, natoms * 3, self.numb_generalized_coord) - gen_force_label = torch.einsum( - "bij,bi->bj", drdq_reshape, force_label_reshape_nframes - ) - gen_force = torch.einsum( - "bij,bi->bj", drdq_reshape, force_reshape_nframes - ) - diff_gen_force = gen_force_label - gen_force - l2_gen_force_loss = torch.square(diff_gen_force).mean() - if not self.inference: - more_loss["l2_gen_force_loss"] = self.display_if_exist( - l2_gen_force_loss.detach(), find_drdq - ) - loss += (pref_gf * l2_gen_force_loss).to(GLOBAL_PT_FLOAT_PRECISION) - rmse_gf = l2_gen_force_loss.sqrt() - more_loss["rmse_gf"] = self.display_if_exist( - rmse_gf.detach(), find_drdq - ) - - if self.has_v and "virial" in model_pred and "virial" in label: - find_virial = label.get("find_virial", 0.0) - pref_v = pref_v * find_virial - diff_v = label["virial"] - model_pred["virial"].reshape(-1, 9) - l2_virial_loss = torch.mean(torch.square(diff_v)) - if not self.inference: - more_loss["l2_virial_loss"] = self.display_if_exist( - l2_virial_loss.detach(), find_virial - ) - loss += atom_norm * (pref_v * l2_virial_loss) - rmse_v = l2_virial_loss.sqrt() * atom_norm - more_loss["rmse_v"] = self.display_if_exist(rmse_v.detach(), find_virial) - if mae: - mae_v = torch.mean(torch.abs(diff_v)) * atom_norm - more_loss["mae_v"] = self.display_if_exist(mae_v.detach(), find_virial) if self.has_h and "hessian" in model_pred and "hessian" in label: find_hessian = label.get("find_hessian", 0.0) @@ -720,77 +451,27 @@ def forward(self, input_dict, model, label, natoms, learning_rate, mae=False): -1, ) - model_pred["hessian"].reshape( -1, - ) # tbd + ) l2_hessian_loss = torch.mean(torch.square(diff_h)) if not self.inference: more_loss["l2_hessian_loss"] = self.display_if_exist( l2_hessian_loss.detach(), find_hessian ) - loss += pref_h * l2_hessian_loss # tbd - rmse_h = l2_hessian_loss.sqrt() # tbd + loss += pref_h * l2_hessian_loss + rmse_h = l2_hessian_loss.sqrt() more_loss["rmse_h"] = self.display_if_exist(rmse_h.detach(), find_hessian) if mae: - mae_h = torch.mean(torch.abs(diff_h)) # tbd + mae_h = torch.mean(torch.abs(diff_h)) more_loss["mae_h"] = self.display_if_exist(mae_h.detach(), find_hessian) - if self.has_ae and "atom_energy" in model_pred and "atom_ener" in label: - atom_ener = model_pred["atom_energy"] - atom_ener_label = label["atom_ener"] - find_atom_ener = label.get("find_atom_ener", 0.0) - pref_ae = pref_ae * find_atom_ener - atom_ener_reshape = atom_ener.reshape(-1) - atom_ener_label_reshape = atom_ener_label.reshape(-1) - l2_atom_ener_loss = torch.square( - atom_ener_label_reshape - atom_ener_reshape - ).mean() - if not self.inference: - more_loss["l2_atom_ener_loss"] = self.display_if_exist( - l2_atom_ener_loss.detach(), find_atom_ener - ) - loss += (pref_ae * l2_atom_ener_loss).to(GLOBAL_PT_FLOAT_PRECISION) - rmse_ae = l2_atom_ener_loss.sqrt() - more_loss["rmse_ae"] = self.display_if_exist( - rmse_ae.detach(), find_atom_ener - ) - if not self.inference: more_loss["rmse"] = torch.sqrt(loss.detach()) return model_pred, loss, more_loss @property def label_requirement(self) -> list[DataRequirementItem]: - """Return data label requirements needed for this loss calculation.""" - label_requirement = [] - if self.has_e: - label_requirement.append( - DataRequirementItem( - "energy", - ndof=1, - atomic=False, - must=False, - high_prec=True, - ) - ) - if self.has_f: - label_requirement.append( - DataRequirementItem( - "force", - ndof=3, - atomic=True, - must=False, - high_prec=False, - ) - ) - if self.has_v: - label_requirement.append( - DataRequirementItem( - "virial", - ndof=9, - atomic=False, - must=False, - high_prec=False, - ) - ) + """Add hessian label requirement needed for this loss calculation.""" + label_requirement = super().label_requirement if self.has_h: label_requirement.append( DataRequirementItem( @@ -801,46 +482,4 @@ def label_requirement(self) -> list[DataRequirementItem]: high_prec=False, ) ) - if self.has_ae: - label_requirement.append( - DataRequirementItem( - "atom_ener", - ndof=1, - atomic=True, - must=False, - high_prec=False, - ) - ) - if self.has_pf: - label_requirement.append( - DataRequirementItem( - "atom_pref", - ndof=1, - atomic=True, - must=False, - high_prec=False, - repeat=3, - ) - ) - if self.has_gf: - label_requirement.append( - DataRequirementItem( - "drdq", - ndof=self.numb_generalized_coord * 3, - atomic=True, - must=False, - high_prec=False, - ) - ) - if self.enable_atom_ener_coeff: - label_requirement.append( - DataRequirementItem( - "atom_ener_coeff", - ndof=1, - atomic=True, - must=False, - high_prec=False, - default=1.0, - ) - ) return label_requirement diff --git a/source/tests/common/test_examples.py b/source/tests/common/test_examples.py index e23da116b9..126ce0fb6c 100644 --- a/source/tests/common/test_examples.py +++ b/source/tests/common/test_examples.py @@ -59,12 +59,12 @@ p_examples / "water" / "dpa2" / "input_torch_large.json", p_examples / "property" / "train" / "input_torch.json", p_examples / "water" / "se_e3_tebd" / "input_torch.json", - p_examples / "hessian"/ "single_task" / "input.json", + p_examples / "hessian" / "single_task" / "input.json", ) input_files_multi = ( p_examples / "water_multi_task" / "pytorch_example" / "input_torch.json", - p_examples / "hessian"/ "multi_task" / "input.json", + p_examples / "hessian" / "multi_task" / "input.json", ) From e3dbaaf26c808e0cf10a31f463272c49083ff5d8 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Thu, 24 Oct 2024 18:20:05 +0800 Subject: [PATCH 153/189] Update deepmd/calculator.py Co-authored-by: Han Wang <92130845+wanghan-iapcm@users.noreply.github.com> Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/calculator.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/deepmd/calculator.py b/deepmd/calculator.py index cc8975642d..78292852c1 100644 --- a/deepmd/calculator.py +++ b/deepmd/calculator.py @@ -123,12 +123,7 @@ def calculate( cell = None symbols = self.atoms.get_chemical_symbols() atype = [self.type_dict[k] for k in symbols] - results_tmp = self.dp.eval(coords=coord, cells=cell, atom_types=atype) - if len(results_tmp) == 3: - e, f, v = results_tmp - else: - e, f, v = results_tmp[:-1] # results_tmp[-1] is hessian - del results_tmp + e, f, v = self.dp.eval(coords=coord, cells=cell, atom_types=atype)[:3] self.results["energy"] = e[0][0] # see https://gitlab.com/ase/ase/-/merge_requests/2485 self.results["free_energy"] = e[0][0] From b65af4cac7af7f2c166c7d51670151268cf6dd14 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Thu, 24 Oct 2024 18:20:44 +0800 Subject: [PATCH 154/189] Update deepmd/driver.py Co-authored-by: Han Wang <92130845+wanghan-iapcm@users.noreply.github.com> Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/driver.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/deepmd/driver.py b/deepmd/driver.py index bbe4f6134c..da095010b5 100644 --- a/deepmd/driver.py +++ b/deepmd/driver.py @@ -67,12 +67,7 @@ def label(self, data: dict) -> dict: cell = data["cells"].reshape((nframes, 9)) else: cell = None - results_tmp = self.dp.eval(coords=coord, cells=cell, atom_types=atype) - if len(results_tmp) == 3: - e, f, v = results_tmp - else: - e, f, v = results_tmp[:-1] # results_tmp[-1] is hessian - del results_tmp + e, f, v = self.dp.eval(coords=coord, cells=cell, atom_types=atype)[:3] data = data.copy() data["energies"] = e.reshape((nframes,)) data["forces"] = f.reshape((nframes, natoms, 3)) From 73fc8fe60f6c93e8e759ca9cecfa4db2387b8feb Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Thu, 24 Oct 2024 18:25:43 +0800 Subject: [PATCH 155/189] Update test_models.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- source/tests/infer/test_models.py | 36 +++++++------------------------ 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/source/tests/infer/test_models.py b/source/tests/infer/test_models.py index c88c266615..7120780457 100644 --- a/source/tests/infer/test_models.py +++ b/source/tests/infer/test_models.py @@ -63,19 +63,14 @@ def test_attrs(self): def test_1frame(self): for ii, result in enumerate(self.case.results): - results_tmp = self.dp.eval( + ee, ff, vv = self.dp.eval( result.coord, result.box, result.atype, atomic=False, fparam=result.fparam, aparam=result.aparam, - ) - if len(results_tmp) == 3: - ee, ff, vv = results_tmp - else: - ee, ff, vv = results_tmp[:-1] # results_tmp[-1] is hessian - del results_tmp + )[:3] # check shape of the returns nframes = 1 natoms = len(result.atype) @@ -106,19 +101,14 @@ def test_1frame(self): def test_1frame_atm(self): for ii, result in enumerate(self.case.results): - results_tmp = self.dp.eval( + ee, ff, vv, ae, av = self.dp.eval( result.coord, result.box, result.atype, atomic=True, fparam=result.fparam, aparam=result.aparam, - ) - if len(results_tmp) == 5: - ee, ff, vv, ae, av = results_tmp - else: - ee, ff, vv, ae, av = results_tmp[:-1] # results_tmp[-1] is hessian - del results_tmp + )[:5] # check shape of the returns nframes = 1 natoms = len(result.atype) @@ -177,19 +167,14 @@ def test_2frame_atm(self): box2 = np.concatenate((result.box, result.box)) else: box2 = None - results_tmp = self.dp.eval( + ee, ff, vv, ae, av = self.dp.eval( coords2, box2, result.atype, atomic=True, fparam=result.fparam, aparam=result.aparam, - ) - if len(results_tmp) == 5: - ee, ff, vv, ae, av = results_tmp - else: - ee, ff, vv, ae, av = results_tmp[:-1] # results_tmp[-1] is hessian - del results_tmp + )[:5] # check shape of the returns nframes = 2 natoms = len(result.atype) @@ -236,7 +221,7 @@ def test_zero_input(self): self.skipTest("Segfault in GPUs") nframes = 1 for box in [np.eye(3, dtype=np.float64).reshape(1, 3, 3), None]: - results_tmp = self.dp.eval( + ee, ff, vv = self.dp.eval( np.zeros([nframes, 0, 3], dtype=np.float64), box, np.zeros([0], dtype=int), @@ -247,12 +232,7 @@ def test_zero_input(self): aparam=np.zeros([0, self.case.dim_aparam], dtype=np.float64) if self.case.dim_aparam else None, - ) - if len(results_tmp) == 3: - ee, ff, vv = results_tmp - else: - ee, ff, vv = results_tmp[:-1] # results_tmp[-1] is hessian - del results_tmp + )[:3] # check shape of the returns natoms = 0 self.assertEqual(ee.shape, (nframes, 1)) From e2ad4f04b04d2e72aa28c39ee3e408fd70d91be3 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Thu, 24 Oct 2024 18:58:17 +0800 Subject: [PATCH 156/189] Update __init__.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/pt/model/model/__init__.py b/deepmd/pt/model/model/__init__.py index a9ffac87e0..b8d301e916 100644 --- a/deepmd/pt/model/model/__init__.py +++ b/deepmd/pt/model/model/__init__.py @@ -288,7 +288,7 @@ def get_model(model_params): elif model_type == "linear_ener": return get_linear_model(model_params) else: - return get_standard_model(model_params) + return BaseModel.get_class_by_type(model_type).get_model(model_params) __all__ = [ From 267e2665434e5137f4cfda2dbe030c45d5069c7a Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 27 Oct 2024 21:46:20 +0800 Subject: [PATCH 157/189] Update deepmd/entrypoints/test.py Co-authored-by: Han Wang <92130845+wanghan-iapcm@users.noreply.github.com> Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index 7b9530b5a3..092b7b067d 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -356,7 +356,7 @@ def test_ener( force = force.reshape([numb_test, -1]) virial = virial.reshape([numb_test, 9]) if dp.has_hessian: - hessian = ret[-1] + hessian = ret[3] hessian = hessian.reshape([numb_test, -1]) if has_atom_ener: ae = ret[3] From f0bb34d85d200dea4ce493c7f25b980e8fb705a3 Mon Sep 17 00:00:00 2001 From: 1azyking <137011979@qq.com> Date: Tue, 29 Oct 2024 11:15:08 +0800 Subject: [PATCH 158/189] Resolving conversations --- CITATIONS.bib.original | 364 ++++++++++++++++++ deepmd/entrypoints/test.py | 15 +- deepmd/infer/deep_pot.py | 3 - deepmd/pt/infer/deep_eval.py | 4 +- ...2e94b4f98ca94a510cbcad2bd47b496b45b8b.json | 1 + .../pt/hessian/data/H8C4N2O/set.000/box.npy | Bin 0 -> 920 bytes .../pt/hessian/data/H8C4N2O/set.000/coord.npy | Bin 0 -> 4088 bytes .../hessian/data/H8C4N2O/set.000/energy.npy | Bin 0 -> 216 bytes .../pt/hessian/data/H8C4N2O/set.000/force.npy | Bin 0 -> 4088 bytes .../hessian/data/H8C4N2O/set.000/hessian.npy | Bin 0 -> 178328 bytes source/tests/pt/hessian/data/H8C4N2O/type.raw | 15 + .../pt/hessian/data/H8C4N2O/type_map.raw | 4 + source/tests/pt/test_loss.py | 134 +++++++ 13 files changed, 524 insertions(+), 16 deletions(-) create mode 100644 CITATIONS.bib.original create mode 100644 node_modules/.cache/prettier/.prettier-caches/9572e94b4f98ca94a510cbcad2bd47b496b45b8b.json create mode 100644 source/tests/pt/hessian/data/H8C4N2O/set.000/box.npy create mode 100644 source/tests/pt/hessian/data/H8C4N2O/set.000/coord.npy create mode 100644 source/tests/pt/hessian/data/H8C4N2O/set.000/energy.npy create mode 100644 source/tests/pt/hessian/data/H8C4N2O/set.000/force.npy create mode 100644 source/tests/pt/hessian/data/H8C4N2O/set.000/hessian.npy create mode 100644 source/tests/pt/hessian/data/H8C4N2O/type.raw create mode 100644 source/tests/pt/hessian/data/H8C4N2O/type_map.raw diff --git a/CITATIONS.bib.original b/CITATIONS.bib.original new file mode 100644 index 0000000000..d5524a14f6 --- /dev/null +++ b/CITATIONS.bib.original @@ -0,0 +1,364 @@ +The proposed feature of each article is described in the "annote" field. +Please cite a article if any feature is used +@article{Wang_ComputPhysCommun_2018_v228_p178, + annote = {general purpose}, + author = {Wang, Han and Zhang, Linfeng and Han, Jiequn and E, Weinan}, + doi = {10.1016/j.cpc.2018.03.016}, + year = 2018, + month = {jul}, + publisher = {Elsevier {BV}}, + volume = 228, + journal = {Comput. Phys. Comm.}, + title = { + {DeePMD-kit: A deep learning package for many-body potential energy + representation and molecular dynamics} + }, + pages = {178--184}, +} + +@article{Zeng_JChemPhys_2023_v159_p054801, + annote = {general purpose}, + title = {{DeePMD-kit v2: A software package for deep potential models}}, + author = { + Jinzhe Zeng and Duo Zhang and Denghui Lu and Pinghui Mo and Zeyu Li and + Yixiao Chen and Mari{\'a}n Rynik and Li'ang Huang and Ziyao Li and Shaochen + Shi and Yingze Wang and Haotian Ye and Ping Tuo and Jiabin Yang and Ye Ding + and Yifan Li and Davide Tisi and Qiyu Zeng and Han Bao and Yu Xia and + Jiameng Huang and Koki Muraoka and Yibo Wang and Junhan Chang and Fengbo + Yuan and Sigbj{\o}rn L{\o}land Bore and Chun Cai and Yinnian Lin and Bo + Wang and Jiayan Xu and Jia-Xin Zhu and Chenxing Luo and Yuzhi Zhang and + Rhys E A Goodall and Wenshuo Liang and Anurag Kumar Singh and Sikai Yao and + Jingchao Zhang and Renata Wentzcovitch and Jiequn Han and Jie Liu and Weile + Jia and Darrin M York and Weinan E and Roberto Car and Linfeng Zhang and + Han Wang + }, + journal = {J. Chem. Phys.}, + volume = 159, + issue = 5, + year = 2023, + pages = 054801, + doi = {10.1063/5.0155600}, +} + +@article{Lu_CompPhysCommun_2021_v259_p107624, + annote = {GPU support}, + title = { + {86 PFLOPS Deep Potential Molecular Dynamics simulation of 100 million + atoms with ab initio accuracy} + }, + author = { + Lu, Denghui and Wang, Han and Chen, Mohan and Lin, Lin and Car, Roberto and + E, Weinan and Jia, Weile and Zhang, Linfeng + }, + journal = {Comput. Phys. Comm.}, + volume = 259, + pages = 107624, + year = 2021, + publisher = {Elsevier}, + doi = {10.1016/j.cpc.2020.107624}, +} + +@article{Zhang_PhysRevLett_2018_v120_p143001, + annote = {local frame (loc\_frame)}, + author = {Linfeng Zhang and Jiequn Han and Han Wang and Roberto Car and Weinan E}, + journal = {Phys. Rev. Lett.}, + number = 14, + pages = 143001, + publisher = {APS}, + title = { + {Deep potential molecular dynamics: a scalable model with the accuracy of + quantum mechanics} + }, + volume = 120, + year = 2018, + doi = {10.1103/PhysRevLett.120.143001}, +} + +@incollection{Zhang_BookChap_NIPS_2018_v31_p4436, + annote = {DeepPot-SE (se\_e2\_a, se\_e2\_r, se\_e3, se\_atten)}, + title = { + {End-to-end Symmetry Preserving Inter-atomic Potential Energy Model for + Finite and Extended Systems} + }, + author = { + Zhang, Linfeng and Han, Jiequn and Wang, Han and Saidi, Wissam and Car, + Roberto and E, Weinan + }, + booktitle = {Advances in Neural Information Processing Systems 31}, + editor = { + S. Bengio and H. Wallach and H. Larochelle and K. Grauman and N. + Cesa-Bianchi and R. Garnett + }, + pages = {4436--4446}, + year = 2018, + publisher = {Curran Associates, Inc.}, + url = {https://dl.acm.org/doi/10.5555/3327345.3327356}, +} + +@article{Wang_NuclFusion_2022_v62_p126013, + annote = {three-body embedding DeepPot-SE (se\_e3)}, + author = {Xiaoyang Wang and Yinan Wang and Linfeng Zhang and Fuzhi Dai and Han Wang}, + title = { + {A tungsten deep neural-network potential for simulating mechanical + property degradation under fusion service environment} + }, + journal = {Nucl. Fusion}, + year = 2022, + volume = 62, + issue = 12, + pages = 126013, + doi = {10.1088/1741-4326/ac888b}, +} + +@article{Zhang_NpjComputMater_2024_v10_p94, + annote = {DPA-1, attention-based descriptor}, + author = { + Duo Zhang and Hangrui Bi and Fu-Zhi Dai and Wanrun Jiang and Xinzijian Liu + and Linfeng Zhang and Han Wang + }, + title = { + {Pretraining of attention-based deep learning potential model for molecular + simulation} + }, + journal = {Npj Comput. Mater}, + year = 2024, + volume = 10, + issue = 1, + pages = 94, + doi = {10.1038/s41524-024-01278-7}, +} + +@misc{Zhang_2023_DPA2, + annote = {DPA-2}, + author = { + Duo Zhang and Xinzijian Liu and Xiangyu Zhang and Chengqian Zhang and Chun + Cai and Hangrui Bi and Yiming Du and Xuejian Qin and Jiameng Huang and + Bowen Li and Yifan Shan and Jinzhe Zeng and Yuzhi Zhang and Siyuan Liu and + Yifan Li and Junhan Chang and Xinyan Wang and Shuo Zhou and Jianchuan Liu + and Xiaoshan Luo and Zhenyu Wang and Wanrun Jiang and Jing Wu and Yudi Yang + and Jiyuan Yang and Manyi Yang and Fu-Qiang Gong and Linshuang Zhang and + Mengchao Shi and Fu-Zhi Dai and Darrin M. York and Shi Liu and Tong Zhu and + Zhicheng Zhong and Jian Lv and Jun Cheng and Weile Jia and Mohan Chen and + Guolin Ke and Weinan E and Linfeng Zhang and Han Wang + }, + title = { + {DPA-2: Towards a universal large atomic model for molecular and material + simulation} + }, + publisher = {arXiv}, + year = 2023, + doi = {10.48550/arXiv.2312.15492}, +} + +@article{Zhang_PhysPlasmas_2020_v27_p122704, + annote = {frame-specific parameters (e.g. electronic temperature)}, + author = { + Zhang, Yuzhi and Gao, Chang and Liu, Qianrui and Zhang, Linfeng and Wang, + Han and Chen, Mohan + }, + title = { + {Warm dense matter simulation via electron temperature dependent deep + potential molecular dynamics} + }, + journal = {Phys. Plasmas}, + volume = 27, + number = 12, + pages = 122704, + year = 2020, + month = 12, + doi = {10.1063/5.0023265}, +} + +@misc{Zeng_2023_TTMDPMD, + annote = {atom-specific parameter (e.g. electron temperature)}, + author = { + Zeng, Qiyu and Chen, Bo and Zhang, Shen and Kang, Dongdong and Wang, Han + and Yu, Xiaoxiang and Dai, Jiayu + }, + title = {{Full-scale ab initio simulations of laser-driven atomistic dynamics}}, + publisher = {arXiv}, + year = 2023, + doi = {10.48550/arXiv.2308.13863}, +} + +@article{Zhang_PhysRevB_2020_v102_p41121, + annote = {fit dipole}, + title = {{Deep neural network for the dielectric response of insulators}}, + author = { + Zhang, Linfeng and Chen, Mohan and Wu, Xifan and Wang, Han and E, Weinan + and Car, Roberto + }, + journal = {Phys. Rev. B}, + volume = 102, + number = 4, + pages = {041121}, + year = 2020, + publisher = {APS}, + doi = {10.1103/PhysRevB.102.041121}, +} + +@article{Sommers_PhysChemChemPhys_2020_v22_p10592, + annote = {fit polarizability}, + title = { + {Raman spectrum and polarizability of liquid water from deep neural + networks} + }, + author = { + Sommers, Grace M and Andrade, Marcos F Calegari and Zhang, Linfeng and + Wang, Han and Car, Roberto + }, + journal = {Phys. Chem. Chem. Phys.}, + volume = 22, + number = 19, + pages = {10592--10602}, + year = 2020, + publisher = {Royal Society of Chemistry}, + doi = {10.1039/D0CP01893G}, +} + +@article{Zeng_JChemTheoryComput_2023_v19_p1261, + annote = {fit relative energies}, + author = {Jinzhe Zeng and Yujun Tao and Timothy J Giese and Darrin M York}, + title = {{QD{\pi}: A Quantum Deep Potential Interaction Model for Drug Discovery}}, + journal = {J. Chem. Theory Comput.}, + year = 2023, + volume = 19, + issue = 4, + pages = {1261--1275}, + doi = {10.1021/acs.jctc.2c01172}, +} + +@article{Zeng_PhysRevB_2022_v105_p174109, + annote = {fit density of states}, + author = { + Qiyu Zeng and Bo Chen and Xiaoxiang Yu and Shen Zhang and Dongdong Kang and + Han Wang and Jiayu Dai + }, + title = { + {Towards large-scale and spatiotemporally resolved diagnosis of electronic + density of states by deep learning} + }, + journal = {Phys. Rev. B}, + year = 2022, + volume = 105, + issue = 17, + pages = 174109, + doi = {10.1103/PhysRevB.105.174109}, +} + +@article{Zhang_JChemPhys_2022_v156_p124107, + annote = {DPLR, se\_e2\_r, hybrid descriptor}, + author = { + Linfeng Zhang and Han Wang and Maria Carolina Muniz and Athanassios Z + Panagiotopoulos and Roberto Car and Weinan E + }, + title = {{A deep potential model with long-range electrostatic interactions}}, + journal = {J. Chem. Phys.}, + year = 2022, + volume = 156, + issue = 12, + pages = 124107, + doi = {10.1063/5.0083669}, +} + +@article{Zeng_JChemTheoryComput_2021_v17_p6993, + annote = {DPRc}, + title = { + {Development of Range-Corrected Deep Learning Potentials for Fast, Accurate + Quantum Mechanical/molecular Mechanical Simulations of Chemical Reactions + in Solution} + }, + author = { + Zeng, Jinzhe and Giese, Timothy J and Ekesan, {\c{S}}{\"o}len and York, + Darrin M + }, + journal = {J. Chem. Theory Comput.}, + year = 2021, + volume = 17, + issue = 11, + pages = {6993--7009}, + doi = {10.1021/acs.jctc.1c00201}, +} + +@article{Wang_ApplPhysLett_2019_v114_p244101, + annote = {Interpolation with a pair-wise potential}, + title = { + {Deep learning inter-atomic potential model for accurate irradiation damage + simulations} + }, + author = {Wang, Hao and Guo, Xun and Zhang, Linfeng and Wang, Han and Xue, Jianming}, + journal = {Appl. Phys. Lett.}, + volume = 114, + number = 24, + pages = 244101, + year = 2019, + publisher = {AIP Publishing LLC}, + doi = {10.1063/1.5098061}, +} + +@article{Zhang_PhysRevMater_2019_v3_p23804, + annote = {model deviation}, + title = { + {Active learning of uniformly accurate interatomic potentials for materials + simulation} + }, + author = {Linfeng Zhang and De-Ye Lin and Han Wang and Roberto Car and Weinan E}, + journal = {Phys. Rev. Mater.}, + volume = 3, + issue = 2, + pages = 23804, + year = 2019, + publisher = {American Physical Society}, + doi = {10.1103/PhysRevMaterials.3.023804}, +} + +@article{Lu_JChemTheoryComput_2022_v18_p5555, + annote = {DP Compress}, + author = { + Denghui Lu and Wanrun Jiang and Yixiao Chen and Linfeng Zhang and Weile Jia + and Han Wang and Mohan Chen + }, + title = { + {DP Compress: A Model Compression Scheme for Generating Efficient Deep + Potential Models} + }, + journal = {J. Chem. Theory Comput.}, + year = 2022, + volume = 18, + issue = 9, + pages = {5555--5567}, + doi = {10.1021/acs.jctc.2c00102}, +} + +@article{Mo_npjComputMater_2022_v8_p107, + annote = {NVNMD}, + author = { + Pinghui Mo and Chang Li and Dan Zhao and Yujia Zhang and Mengchao Shi and + Junhua Li and Jie Liu + }, + title = { + {Accurate and efficient molecular dynamics based on machine learning and + non von Neumann architecture} + }, + journal = {npj Comput. Mater.}, + year = 2022, + volume = 8, + issue = 1, + pages = 107, + doi = {10.1038/s41524-022-00773-z}, +} + +@article{Zeng_EnergyFuels_2021_v35_p762, + annote = {relative or atomic model deviation}, + author = {Jinzhe Zeng and Linfeng Zhang and Han Wang and Tong Zhu}, + title = { + {Exploring the Chemical Space of Linear Alkane Pyrolysis via Deep Potential + GENerator} + }, + journal = {Energy \& Fuels}, + volume = 35, + number = 1, + pages = {762--769}, + year = 2021, + doi = {10.1021/acs.energyfuels.0c03211}, +} diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index 092b7b067d..f6d0c20e6a 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -546,19 +546,12 @@ def test_ener( if dp.has_hessian: _n_frames_, _n_hessian_ = test_data["hessian"][:numb_test].shape _n_atoms_ = np.int32(np.sqrt(_n_hessian_) / 3) # n_hessian = 3na*3na - triu_indices = np.triu_indices( - _n_atoms_ * 3 - ) # upper triangle hessian indices - data_h_triu = test_data["hessian"][:numb_test][ - :, triu_indices[0] * _n_atoms_ * 3 + triu_indices[1] - ].reshape(-1, 1) - pred_h_triu = hessian[ - :, triu_indices[0] * _n_atoms_ * 3 + triu_indices[1] - ].reshape(-1, 1) + data_h = test_data["hessian"][:numb_test].reshape(-1, 1) + pred_h = hessian.reshape(-1, 1) h = np.concatenate( ( - data_h_triu, - pred_h_triu, + data_h, + pred_h, ), axis=1, ) diff --git a/deepmd/infer/deep_pot.py b/deepmd/infer/deep_pot.py index 1865be786f..cdfc9cd1f5 100644 --- a/deepmd/infer/deep_pot.py +++ b/deepmd/infer/deep_pot.py @@ -21,9 +21,6 @@ class DeepPot(DeepEval): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - """Potential energy model. Parameters diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index 1d5f086ff7..d1a627df1b 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -160,8 +160,8 @@ def __init__( self._has_spin = getattr(self.dp.model["Default"], "has_spin", False) if callable(self._has_spin): self._has_spin = self._has_spin() - self._has_hessian = hasattr(self, "input_param") and getattr( - self.input_param, "hessian_mode", False + self._has_hessian = hasattr(self, "input_param") and self.input_param.get( + "hessian_mode", False ) if callable(self._has_hessian): self._has_hessian = self._has_hessian() diff --git a/node_modules/.cache/prettier/.prettier-caches/9572e94b4f98ca94a510cbcad2bd47b496b45b8b.json b/node_modules/.cache/prettier/.prettier-caches/9572e94b4f98ca94a510cbcad2bd47b496b45b8b.json new file mode 100644 index 0000000000..f375e7a9f2 --- /dev/null +++ b/node_modules/.cache/prettier/.prettier-caches/9572e94b4f98ca94a510cbcad2bd47b496b45b8b.json @@ -0,0 +1 @@ +{"0765adf934b9346e26b8bfb827bcd935a35bd04c":{"files":{"doc/model/train-se-e2-r.md":["yuFfQl9cStJgS9k1tdey02KHgpc=",true],"doc/freeze/compress.md":["JnJr3Rjrl7IndgLMi4a/1g9j31w=",true],"doc/development/create-a-model-tf.md":["FdtlKxeC+1S+UbGzo9KVxT1201A=",true],"doc/model/train-se-e2-a-tebd.md":["T9HkWkC/ltc8tiIcyoFB2/HzJNs=",true]},"modified":1730171362928}} \ No newline at end of file diff --git a/source/tests/pt/hessian/data/H8C4N2O/set.000/box.npy b/source/tests/pt/hessian/data/H8C4N2O/set.000/box.npy new file mode 100644 index 0000000000000000000000000000000000000000..9fbff5b5c4837fd77cd99c79ee0ee0203b5e3809 GIT binary patch literal 920 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 literal 0 HcmV?d00001 diff --git a/source/tests/pt/hessian/data/H8C4N2O/set.000/energy.npy b/source/tests/pt/hessian/data/H8C4N2O/set.000/energy.npy new file mode 100644 index 0000000000000000000000000000000000000000..4761a45a4183c19d1a7d6c87d6e2777bdd3aad9a GIT binary patch literal 216 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-hB}%$3bhL411|3H4<8J54js7KcepVfNMCsxz}XL^*Ijh+DF@QqIkN7` z=pH(-=H{J+TY&uQ@wxV$T89p7KFabq0LWh`x?3e)`_KWIkT;LDf%JFFB&$0>c>oA- BN`e3Y literal 0 HcmV?d00001 diff --git a/source/tests/pt/hessian/data/H8C4N2O/set.000/force.npy b/source/tests/pt/hessian/data/H8C4N2O/set.000/force.npy new file mode 100644 index 0000000000000000000000000000000000000000..c69ac3552d881cb46f45e099b3cae1cd2727553e GIT binary patch literal 4088 zcmbW1=_3@1+lFn)sg9IWoFY^QCrd&p%AYnBL&WymNq$TF5^ zkg63a(+vv;XQ zFxJ>qI6y&g_WUrk^cXv$1jDXID}q3!o#|g`b@+6;S5VEB0oVGCEctHG!At7BZZ?Y! z4qpU$oxi+68n;7saa#>&Z3!b+hQ7sn@5gFBZCwDV5SIDIeHP9|P#%jFvVbNvITNKe zgZaDsKc6w5hl&2dnr$t^xVcL=sECV&#>6Ba56M=vwQ^IB=4Zh@8_C02q&jqw^dVOt z8pd_K7}<;10k&!z(j7KTpgj6iL>()FYirS$-1P#{M7*{1w|OeI{7^L}{Fx8-*V^Q! zs*p$mmX-gGISAytT94OXfroT;+?M z$p}uuBZrQ=uB%0&))8|TR(uA;D-%LT|EV*76Z=>4L^_Ig-bp=EQ3|=|cb|77lCf|-&y8XK1h!`x%&)NOK+XDG(0j8isBsjU zGf-w>l(%$hVMiDSr`(ZV`VoVgx6Qb{Rr=vzfT|1qP6b%YZxeYskd3VLD+6=JlR)mj zDX?M5P9Grmhh8F#0X3tbD|`6|QaRoaE2oX1XsIXMdi)Dwj>mKunT+Acoy^Ta+KZqN zle{_2EEV>q4UB2j3;|s(=G5=iZ{T3`!uXoSGS=%jdR%W>0N=8jvwibD;I%MQ|Dtam zQ`d#VoX#DG0s+kH`kC>B4SQ z@Guru_NAis=(x}9ax^OH@w1)PeuC8ozg|2J>Vk{L9s~BYDrkE6aQAgv1UCg^@$&n1 z@H*=Pf8lH$N>84gG_iXP$u1+k%Yk$-P_UMYfH)X;kUSx%Tn!!^Gf&r+TJX;tMsIUu z3H~$V<1M>HN8u}vkEO5QgW>5TmmAqA$ed8y^3A00rJV+hv=W*?TXDZfpv9D`kh+e-<4gJAeTdQxuk2V5ZSyt?UH0hl!A z6NFPnpw&u9P`{@eyKOpMnT~Wk&-Ufe>efc6x;mL#RQMT7uhjY43uxk(Udj;_N*`2> za`Ab%b>Y4(%@PgTEC}eOnTDj-;tA7fA(N7K&@VM-t4XfHCp;fy}n$OX{9@SDt*(`l((TUfWbo)RaIZ<%|?9N*XnX_q73h~HBrRs7J%4dli5-}13?dMk16@I;)s#8ow~?Ryz6O7`Jw+C zn=1Br9L#tHLKc&!w&&D=ccR|qnVKdz(c@7dZ^J;dGEw%ZaSsT!5z}9A?gja0X%%WA zck#2@`;Xp1186}F92_fW06n|#`E}Pk$TeLRSd@E%+q1MU=lxrWv9IobACyT&-YW&o zGwf@yRZ8q*#^fk`4)C0kruU$z?qP*P^%KB-Qo}1~vJ3WB3#)Uojlilp`-D2DgOlxt;16jx%++SatUs~?el zYUf4itVYNot}(m=dO>qw=M!h8cKmj$ASceV@xLEL?LgifNbk>Z;j%4-tYaOIcU|a% zxm?N5>SyV&Em-UAl28v?MXeN0I5IIUy4lWcwg_h!3*N-DU0~d`$)D)di3a(t5djK~ zQ2a<^y6QtQI2#=v%kgZ17aut7^5jckYWn+t)nWw(8t|pr1&e!jo^zV`DE zk{e+_!CX98G}bisZGM1g;4gIAj4^248OD2{o^jTq6#-p z?ftAayij}SZeD&bDEW==6g*FcTWTE_{VojSm@8YC=dVw=_`yUnL7#z){D(aXflN5_ zp|?F%tOzxRieuVU6H!v~)5Wd2B~b9Ie7mq?21p#c`f*2gD5yl;W%j54*YDx-V>&)B z(f7#G=Eu@`m|F2p%p@Wb2e>}&n967XxAx^7I`P#od^dd}&D8@J*(L_V_$n~$P=r9j z!3vapOO0opqrw0Tfx-uzq6pm&AfnSKxKFznMW@7foS@WToX+uk zu7ecpGTh~muSy5e4`0sm9Hl}hRq#TqS2-kMI&JWE5-3e6t!h|#;)6W}-*}fx;bD{e zQ>VYU~uBWGNk0+9)Y|p41^BC!FsyvjJzL za`eM%`cbi=uhp0o19l4E*d)$ZM zjb%yB3;S{;O{QmUn{7ZPHj}1lX)3n9?Y!2V(}GgxEq4B;Ru8A+xsQD6_QV;kU0mr0 z!*H+Z^;;b)2^dzaazMwA2&GoRS;03V;F2tJfT|G*iqpqb_^k@yp;bjh*M(F#qLpp6 zbM`r=^+*?-G718g{6>AnmTXw!xv4rk=naBTvItMLV{l}T=#rU8G*n7KMAow;6#j;P zfAx=nw#cFvmYF`-{Y8TNr=1bd7CtOpsZT^HwLcFpoeu|#d8@mUAu%AE_SZW>hqpMY z+_agfQvw`@MQ+y?S|D?^Qm-~G8S}Xw03@{Fh>zh~O;8JPd>TJ?;?@cdbZGnh?f)HK zZGB+gaFT@fIn7>QGS3(E+!MG6Vg>K7tdQsklsT z7oN*CJD|_m30^wlTK>N#(Atr#*Lb5IW@Hkq&)PB|Vo5jins6KZBvn+$jOD^tiEG0R zQnxVtl|=uWgg8tHdjo-Io?)>2pJE|DgYjqD#&fUdNg(_+%2u=SG2Y|u=AR>{VSc;4 z*t<@16dIndV{wM!kv&u7aVHPl`h4DYJv|%@cu$HYcX)xHPgxc7KmbUtJ5JA41!DH= zUw+RbeL>tjbUtje4@f90f0NAcgs;n#KRotF0N2Dp&E@0CC?5KS*ixH_snH9hj367B zTu2fv7O2JF_0qdELg)~k7V?k0sTNQT$4ta&)ev(hrOKVy30&JwY84-@hQ3gh@j<@( zSkwH%#wb4-hV%vs7o#a?x}&gW!LSxZh6Z(Q4pu|)w-o`t(@*fiMRhxVVg>##jdoM| z@gDmvA`f5xMT0!O+;^0tvDlp^@#Eiww|KBh>zl*j73&eBHZ2> zN!8H2i^XO7na2S$SUZf8c^d>YIejBn8_On`qb z?cP0H7!OzF{$)<`nPV|dyx>rYMhV`~McK1YfUrZcx-{7VYQKL|KiwaN`vz4jc2FK+ zwt&(yCy5tM(-jzV-LW{nEr6X7bOv5NdVU#$1Mum5q_9l(a|{{aa5uP}1$5$2lYwJ2 z#MDP}l+;y2efy0=)*)p;_@(Oq2caB=LPGMCL!z;y$WLjW84os!3HDku&yX0gY%k#3 zfP49sV-~XWAiBhy`fgJ#?tpqJ5wSWDGNlQ&up>ks^}YV*tP5T(ccG2%eg%5YUaFln zwaApsvSue#pjvjXWPcJ9{fzwfH!&mOrt{>@JsEwl#4CJrSdE6i`=xZ;n_l4wz2SMf zT@c#Xy*sa4MFPL1zKm0sNqEmQo#%`-4ao-HZ|+D`!mF>x#SKkfqf_g^7M|2nY!P#_ ztf{Pk^eJ7fs7x|0O58TB>a7Lb|1-t7D+&C=726k2v07g)6qPk&&c4&;4F2~)c%5t-&xoi zSikfTwVA|<7+(DYU-JzMk8rK0%_4b3EgneGj;D+Ftl^)Ad(qu7a!ku<{egW6+phhA zoV0ujZjA%4LDiF0f@8p!Q^3h2yqR_kqvN>r1gY!>Y0-+muffl+pJ%<$2$UXQ9BCnp z6#qiKCyV(9%j$^=OG85ILM=JVVl*LP9ZUqDk@oek#pq=l*pi-K=Av|C9HOQR#Hp#o z75>JrBpmHxwhDLabE>Wrv6MMRdq^ZuE{+NY!BL1<)T!LtP-c;n3h>TD+L9g9;_VzPXc`Pk8`T$F--gZbCD;&K#1j!ZFywY61NXkrzN_y%Dk-2oId(F%Q zfnnCbEqQO?;H7PY?>Sqb-Sei?GOk+EnRL@bfH9V2$!_QPYW|F~TWv*;E z7+OR~^T6@T(&Z%R^v7QkKT`kOUz~4Wv_B{N9}Q~FaFm^z`3kx?d8kN0;XzBzbp$`)&Jw3uuP8BUcIC?b+#l5?AO#6jdSgTSq0VpKwyg8r=R{wKVjV6w7$+ThqAC}f-~X>V&H%s-^vI0{?Hq++CwttG&{ z=e}|^ieAvRNAiFmk$zaz6iFnH)eg}@1*A!9vGSu$4ym6KV2pTDLJ%Kte{sIL zt_;G@UaM2)!HoiCSz>f{M%m2t$Wpq8OEj!7b3M(WbTQt_g@b+*rdVk8hLz%Ye{Zd= z#xnYnvZ~LCeO@G{LF}bKWWT!n2vjv&?LNC5R z!I3NX&+Qw9v574`4afQk;xFO@?k~>QNcd!lY0qJ5KX23|+pcaxm_>4~WpL0g0nH)W z>o(D?LPI&C#yr%4&m}IqVPY zmXzAbJX4^n1@;}Q1wJZtCP>C>u>*Fj%VT@K<~y7hi~W0rd?tyTJomqNKMPi8{Z}jc z{e@5==4<<+X2@ZFXW<7y{p9pV9_6GpBjm;9`mbRk1OMGO#9zb*++Uop@>23}ob?FR z_hA202bE=1OSSKc?{~N~w zJ!N7l31mB6tm1CB5b-@KTOYfhAuhjWmmKrWCdO9&r_ZmKCci6>ERoyANY}h~(@qo| z2ceq73ldUu@L+VwQB{qLTcBFkUwF8~!dvMQ;sR7`QA(o3HBe zndRL<-!H+l`X= zuq~H}9e=_4EUx1ar}@d4&KXn7C68PdOQUW&G3q@r@tO8@Ww7x@F;H^g7W z2i#wr?}$=`RGp~?`NQi)a_noPu1h(PD)|}fjHQ<#v9Md{O}-0kO_i>6GJZsw`}bN1Nt`FG@jvR! z6=T7A)$^$xa&92=_)bX2pEl9Ugsudvlk~YgiE`cH#1t2H5}i=7F^}1CUK#C_MBU zgD}p@;;mH!Bv0O@D1&)|@LliSBK~QbBz6i0p58bHuXPh5viue4nX`Rg#~D}CB3X@0 zVaufG(-*tyi}-;1i}UTb-`T31beM9Ub>g4B%}tdn&pv9`iy~19dk^U!c}1!!C&cf! zl!Cj0g+hfx3~^QIXel+hN-BOe|J^frAHLKcAudY;$nM6({LG?dMEGMB*Vfr^a(TUi zs*6$)+;yDsV>?E}*wwdRecpTpcq*&3M?W*;BmaJN#9DmqDQPV_4lZAc?Z zqveyCS)N2w{bYlQXdyXE=|->GUkQ8AZ(#Y?6m#iWk{YqhyLzatLqI->Z?^1RuT0uZ0#!-wGDe{Qjo+3sg z|3!X=`~mM9;xFO@?k~=F+Wj|=ii8Fw_wH!b7K=R6y31d2?qUnPe0MEr|7qxR{VX0C7_k-IrC?dx02`VphXY-2{J3zcqK{OvL83DjN+q-V(ii2yBq2L>dm3W zCzjKn4t!q_-Ni)(8y(ukeNT|q%8LK9s%I@VaEdwlMz0t>@^ETCYH=rx`U3ec@-yTQ zc;EiR-~ZtQ?k~>wd^zWq$TT&oT3&ADE=~n{k3xD$>jf^#=tj?h)8Z0z=*SL<+KdG< zvnnuCp{*Ynvsg8}ns{lInN@F|9TcG2nYuJ4Qddy6hkm;>pIlEHo3ULlc_c*XWJqwH zZ{nw)ERic{_a6mW(^aW~GryqfIHTx3mLBrp?_>U}|8$Z|r02FDj944< zmV0fnzBA<0y^On`{`>+aR;QPG>2*ZbGX7Ea@@ldIo)&r!XA>va>LRDIDxw&pFI6d; z1N;}>U9f#o4MUbm!sYH|kQTOFa3rXRI6Aps;W?T|7W8;K8N(kyV+mWZa9s&VH(Xw+ zrJM^+8Pn~b)$KsV_P(ufju()iGM?<15LjGm794iy8bSSr`U3ec@-yTQc;66z5g%}W zalT&c%e0^99;SGtzJJy#+f6szGQM=<*LN}{Hp3R&AWqLSJ~vwVbO<)SJDS|yE=ZA6 z_l}FFhYC1tx030?Bploq1AHP& zXs@qLJU@Q)1Jm{cVbbH@$VI}k&oyWW)(s?G@v`|qQs3fbU?Qi@mXm^Pe)lV4ie zcn80b?7E$b*KVeRy6O~Lpj$K~PO?3fajb?{;~s_uiG6T#|J?Uh{}u?@y!?IWWEW(A zjdohj6$er)tP(5gD`7ILz5JPZF**F}n7{qVOE8WekWn6f17}*zW;(7I0`s)zs!``~ z!dqFQE6s5qTJFc&)vt^tsE<*qOoZhDd)>m8R?1dA*{IS*cRJlBpuGKj4)dKh$DBPi_ocQww}6NJm;T zB^G?#O~u}Gv<_xIN`uj^%e}gNu!7cmsnqs~=vnWcm{IS9M7rkNrU*uAqigEJhT}|h zr=xQ5#jMXH`5D)}(xfT4v-y^UyXrJydZ@8oHt0JDILOL1F3p3H>Y$MoJ5z}6R%>pX zrN@rikd3Y6*GTU#Xm*>jKlC*30w!35#;U;#S%H;`Aa5a_HW?#gA{sZb`)NiOS zkpCh-L;isG4e=N80rwZ@>puNvi|m?X6r1l;!RBlgTKR~H(0&$CO1-o$yoLQR{aiw0 z=$8qhRIZC{Vx5tu)$QK8JFMy>65l!)?PmI5s@W_h+Jcj+IsfB(3Qs3USs%IR{)3I0 z<$mU0>bn5GijTZfvui+x@!2F(+&dz`!T~R$)sw#w&)3g5Hc*JrLw6cg(N)ci&dV@h?v{J2KY-$DNY^)c!<)ECHqk)I)d!25>yi}-;1i}T&teM2^>j-s>; zvVJW2yn%k6Gs%3Waxc~VSuSQUV>kV?RlR!pg#@+QPQzk-dKQU0_*b{RrB{6NW=%2KVx1A#ok+Xe?A{Y!WVeUbQyz5W?AQjecN6Vc?JR1_f!IT zGw&{#kpF^|FUoB4JC{#Vl^iPa_ueC?*#}m}pMOAp|1i&PXSzwI)*3JE>avFq5q^)} zx;hiaZ_h--Ducm%y;E_``v;J7dB>YSTAQFgPI1?o^MM5YGxT@Re?WbV`VI93@?Yd< z$RF^&A^svh;Qr!#Nki@d8-0qpx%rbrPo^-vb;3*T@8*@%z22RL{AV`O)d#ds+~((| zxW8TTT4Bsc{r%~iw9jEJ?H9O1&9$75p1SCKFnF4odbRNki@Mna6geNNJ4d~SsD}r) z{pO*K# zcF6OY4u$J3M`Evgr;|NYysdQcBf`eRSVJGXPiW!Gm1fMB;DF)Vb8c}Ye{y0zDDOQ)j@3pv{ZV)b>xAbO9Ai=j`lhTZ{`PL|@LgzPx-Tub7 zn$mBS_$-`SM^=1pO=de3PB`y;ig_MB3&!V0kDt)zqs?BgW3=BnMq*aYm)$pHriHFL zb^m354Fhwf9Hl#p$diU6M-HoI!-Tp8{Zoi0&nu003JRx#>FCu(6RUg>`_y}A_=O|% zEV;sEX=ej0oV)v&ICQ}GYt)U_^myQ3-IC{VB?_W{EkxKkL_w|DG3UHqXNbM4tfkR+ z3y#HGJva6^0F}X~xcL6z!<|`^tQIi~2v$!`+rsNd%G;&Jqx!u_SC8MfxqTL3W+=QE zVYi;3|BL=4`e*3xp#OmS81)ND3~PT{-UzeVkF{Sa`lJ+&A4lWr2U4K)G3DL zi$QJ=h-G@+pE0!@_+r}9zy4u3$@FBuqM-YT$h?iJz4bK^jJR>j|Dr#M{u%l^=s%!7M*W8R0{JiUGvp6=-w=NhA8>zhz6*C%m6z2XprSS$ zyye0@NVfR0L_CV`hg~LO4QsXx!-0^vET61@ek@}}eJCRjrL0rS(zBP)ho&BKjt>@) zJtzDd7st!t+h3c?wyNjE&%3s(=k5z~)OPgDI?Wu2tfuBH?*{?bv*H=%tY@Tc{W^o+ ziV5WQ(y&Zz$qX_SE`RvuYk$b7c%!lTrzi0hQ#o{zC5mX4e3bK>^di-%?5T~C7YL)) zYPSta)&wq@y^!!XgphrVJ+IdI!RMWWiT)Y-JLo^4K1Th9`U3ec@-yTQc;66z5g%}WalT76duL3Y#i-`&;-5`> zR?|m@HOg0qt)SRew8;2*w~-Blj*9EUX{ZR3VVbagLWb`SUpkjjNw~Yobtj$IQ2*6* z)@k|y(0RrevS7Mnp5_{o&I|5m2rcoc(6uAz9zjtuhfF2Wk~7|1Qf1 zh}dj6v2=4XX;;*Z7~)Wdo9*xUTc@K*rEiLxaFsLs^naO<&S3_a4`F_R{yO@<=ue`5 zhW-xv52%k(zoEWB{)_w!`2*fJ#9zb*++Un;?jOy=Gi#No2b=!LN4Qe7f4#3~S5F2} z<9}}TG366sKG3JW<@P(Ud2=GW>T5G>F?q&%Rkn&qZ9X=u+K>U@V0XJUwUN9tOwX}+ zRtLW{<9n+%my_Z;aqrNG$7I|@>t@gXSn~JF(ybg_>BOR*t5UD8hKQe(-zKx9ntVCE zdeJ+%f^0P3?c4DC9>lc86^tItBj2UW8gyh*;D%58{==P-U}sRzN6*oux?GcG>rPW* zIl1+K*&iq3;c;*}dXFLb&A7DBXHzg>{)YJw<`?L%qyLNkB>HFQ@1XyH`WW>a>I>w* z$j^{J;C(~;)DS4GNZzEew6@gL3oVbw zf?}BCmt?Z5CsyFc=R{b&Yc^`fkvvf2mh9f_kpZ7+=GWJJQ(t%68$suchG-8eT@1I^#$@@Z&_R!nz zLp_L(1-`I*&snznk^0_Km(KCIkuOZc#3c7Rsa;s;?DV(`=|;W!ZLaaelPqnnFS|#k zBDfDYUiBlxe|3WS{ce!9q2JH-mT1BKg%9*QejAc1?Y{Z&QAdc@Iu~uGeiwYRR%?_m zm_UyO%i6z}%^{tos!rhkdH8ksklEyCZNU5(^G(d(FdxGF0{wOLf6<>r{|x;d^dC?k zqkcnuf&3Tw8S)3bZ-~E$54gWLU*>o}oyi_S>dAt7JF-hqQF>r_^YYxBqbw<(`PG&uSyyk^Jw7+Ef z&c^f{X8xWHm0g!aDoahO#TlJRb$592v$5N7l1bYz=ZXzj{5mmQRB;k+HE!V65ZDWE z<2y@JZe9XC+RD`?(u!1SY*vzde3L96Qel6Tbc5`BqutS(c8WYr)$`lfVF#GcV}6YJ zCgyLL4`F_R{yO@<=ue`5hW-xv52%k(zoEWB{)_w!`2*fJ#9zb*++UpU(g2Y5T}4rA zf;tbcWwRqer+RW(Rkwkm+{2KYf}X^`{Eho_h=W1@JFC9)coBu;ql1gboXErMy*@WL zJCc>#I}>!RHHlW`c>T%9GZ0dhZZOU20&YHPF~XcC|M)WhD$bi)p!<0FsDP*~42Hzc zs{gnErk(rbuL*-liTNAmLzrKnzmEPd`jhCNp}&Lv1L|YcZ>TSj{~|v_{($!l z@fYy{_ZR0YIC_k>i!p_?%h0epXo-UL1qa${iEucW5Ru4P%^?JmDW#qMig2+ zp8X&zWLQkv``f{H$F|iJ{T959zZHmwH^W`4W33HFg`~iKd+@5p=j32h)5(^GRFc1> zIWbb$oAf99itY%FB|_{wGy?3ZBw0rPpxk1^lG{0;LV%rDSiNByi}-;1i}N+nOET==BS8g+c5hugAwflp$HcSUoy2%Y-wJ34W!k&p1Ee=2NRcm>q+*PLX;?<77za|FlZC~nfW@2=zV_8E}8d& z)Eesg{$#sGa&k*s!{j4K^{wZry+IK~wRWRYLv&vm*Y9nAtS+jOuPiLG`y;f8%axS+ zbx)te_?N9RVeJpW=kQ(qFW;UL?C)T|1p5z|&tra!`6lLXm=9rof&M!Bzvxe*e}?`J z`VXj&QNN+SK>myT4EY1zH^g7W2i#wrZ=9avIj2!2Ds!1wg!pV1$)Bo$5z|4~5ol0e z6)_HVUZP$O>}|0B>fW44vjX7y!FiGCbrzIrsWYF{zClFa&RgtINFi!%0s{xP1`&&j z^G$PZSKu}0MefD+SQ2zdO(Un{A({7%x2Tl40s$he@xJ2PL_oIcTM2^+Os6$0vwoQf zcc-+Djjl<7;s)-LM@4A_`%&26!F~z$A26TC{2231%-=8{!u$gLb@YGHpG5x*{T=il zP#>dyLw$k#7x@|T2fS~HzlaaGzc}Ag2NND(+dx^e>zZkoi&IyO?|hw3Uq#F9W;nL3 z{w3LKXk!~76H9aq4X4C349GgMf}6f7X++J2!QE(mHt{%6xb(isOR`y$wf)(_Ea<$| zcJSloa#-@qAf(kNgHT4^@B9CF6XDXW*-!SkfmyT4EY1zH^g7W2i#wr@63jBZ7-*76ld@%^9?4F^mNO4<{aY)GH=GcUPs>p+H&?> zd^O}r9t!`|zrPSm&TyFzoro+TCloziem{N=n8 z{V443V7~XMhJm$xkZ({z2`4Hw8=&z&yi~c0~XXx*s|A6`! z^&9F7!@Y@A|;y*vq1(HdYQ>vnxOD=gjdowwcJqHpcKSXWbnMG86EoCLcN=Vrz2Kg-J zCt&}8y)?eO1$sVJzI4!SgRhaCMl0l7|Lgx^KNb6v*zd#s8TO;Fzk~e}>_1>WkNGj? zo0z|0K7{!N`s?WbqCbiL8TvcuKcGHF{f7Di`7iP_WD=Rjx4Utl! zzOdHc&b|H)awM}0tlqYh&2!Vi0bjn8J8Yu*4TAT`WK5~zsS#! zKj3{s{6&1g{l)oOl;-lEage7<4GtFSrf;N8^sL`B3G-1q^QE#iC-TYui#v}hG^7xo zh@}#>n-gJt`={)kiDkgT-sYtKu@!v1QcuZVs3#sB;~B#*o{>rBHn&mpO!9L#uzi_JpMOvc?SAynxKW)~p7@ci^f1Eh@pJ9zKfP_i&hQA|0*T^Q;%{z~hAEvl@zp zL`U_k$|2F41m6c>e;xbP*#E_TD)uL_--rD(>_=gL2m2-1f53bm^JC06F@M8+2=fc{ z*U|q)e-iyO^movIKz)q*4fO@`U*u=VAMm~*{vtl${^ERR4v%EM7F$i}J>K7OHr9&F zbNhUCIhqY&L+XVZQaL2A=(q~|rD~F5b$%D$p++L(l_&xyp>)5Zx{x9}Zu|J9ZKJ1@iKMMOh*e}8U1LpIXA7j3W`5Wd# zm|vj3j{YzDljxtJzk~h*>SNSzs4tNJB0od^fcFjY7x4l27v~$dPk~Q|YX_xbyJkrghsOxFJ;cA1?Ix1Z zk^0X40|ei<;QI@FAAtRJ>{ny|7yGH$pTvG2_Rp{%h5a4umtg+^^LfmVG2g`e4f7$) zFVJ5{{}=s9^v}@WLH_~uG3qze7s!8+pCNz1`-b?7_<;M1^QDdinn9T;wRl~Q!!UuL zGPO7qJG*;ZzXG_#r^V)9k5a~6KUb7kY=ahD!wUn?fDFYwcU?}zYx z3%1My(UNZw84Pt2BCDl zTCf_l=)3Tw18(p9OZRMNq=yHp+O0NygCe=LQ7fXR0pDlg`yqVag6}WzeE{~?v0sh- zU+kx1e-itB*gwO56!v$pUxNJy%;zyb#(Wd=H_V4Hzd(N-{a^Ga(LY0f2mJ@s$Ee>> zUm*WQeun%3?;GMT;sfq4&Q~jU`_(6FvhWr8V8{#kG1MV-* z_ie&9xvoFksYc(g)4Zj;bi_E@#5b?cMB5@_R)%31c6Vj9w=HS@?|mV@f5Z1#_?@Qb?jGT{}=nI*q_9HANJ3%ABFuL?3ZBw0rPpxk1^lG{0;LV%rDSiNByi}-;1i}Rg$r#%vRNQjaZr)n?D{wA@@vK{WI zFw$4WxnvzQ2MNC4#P@~x{te$};rk(c--7Qi@O=RG*RfxX{a@^-Vt*3*eb_(4eiZh1 zuwR1x2h8U&KgN6$^Eb?gFuy>59sOVQC(%Dce+T^s)W@jbP+uVbMSh0-0q+~)FX994 zFV463JL8e`!995Bq1>kHY>A_Dit;fcZS;$Cz(o{)YJw<`?L%qyLNkB>HFQ@1XyH z`WW>a>I>w*$j^{J;C(~!XU7W)A$M`-L-*4jkLVW** z@3Zjz5Wa80_ZRp+0Q>9Mug3l__EWJxiTyt8pJ6`=`#ab#!Ttm0^OzrFzKQu8=0liY zpudj(FZz?{pP|2l{sZb`)NiOSkpCh-L;isG4e=N80rwZ@E14v|kjA7;39vG}Y-&16 zRVb(KI-b6So;6X-6udk^*!-td^&Ynp*40AP9{noVo$BVw(fyV%yGU)_8C(c0!TZwZ zSGdF6r_imJIrHI_%G8~_o8=%Zx2o}qYc>>oT+}n>|MyIzeeB>CzIF6Cr}Kr1sVVqf zxezi|+fNGauHV90nhkD|CH8l_Zo_MVx6^H_f{5l8QMWs%`l0#s{C)lo4m$eUa9@Pw zD3OTlaJ;_F5_AI?$_q-pK)}>+%lMTrVp2NeyL(AA`DpTm_4t8!;=c4o@?Tje@>*SY zL!nY2BuA!{zT9#bm|9IYs1>>Ze z@MnKIV9P*VZREIZ?KlW5JVMvnHFwc6+Y4SV@0cNStClN%kdvgkqNSq1LxB$3xViC0 zz5sR4@@nDDN-6qbYW8l(FpuvP3pzwO=7d; zGl$C`MYFHu<@8|_LBCcAX7Nor@81qxGxrMZDx*MHb4P&J13mI&1!u)x-Y3K^PK|j6 za!H-Rlki2$dXjDz+P2u!1p6H~j!c~T0>vd6m8QxHw98egJ=<6CQD>a~SgvlYfO)M% z&eYLcMEld5oO}P?8@oGL9*a+0LVM?lyzvelgDtg-D-ZY2k(FsYmot7|CyH%KA-v-r zM0swFQ{|HT2zCm*9O_(o8P= z8(au_+ikxqmo&tDD)#)H2q&B0m@hssCb?J3jzY#qLTR75G*RnIuq!8Noo8ZOTaB?*8k}do#6BdoO;QZ4|+r`L(ghZ{;4t?ZF?r2td7A&hEy}x@lrzEG5g|5b1 zI@`lZ#$X1uMzWQN&S{?eer1|u1#tYi9@9r+JT-J*T#%%sdi*=8^}Ard=j7IO{|)ph zW}`>qqf)e9Rk%iqAU`!&p<6s*DofjJs0ly(?iYNJw$#Z}+C(#PZ*NsPq(CD+;Qr!# z>&GqhZr;Xh3fUF+7-QEMX-E^U>dB>(*_{j%Fg|NOSq^YjyOdI$R&`H)ot^jd>n z*D;xmG|%1T{5FvyRDy|~Vx7n|nb=z6xc=lSx;&co#lG4RawP2I@}0+LL1<0EwoCmy zv}^vObLV(?sV+x*jhbr>U?J=hBA&UEo|bEflDRENePH3;G@i>r-zz)7QBz(4YgoUs zNg9k1sg}5%sjC`^VkOI6p7ozddA!vo$Mzp2Do{azh?4T6tIbZv&laVZ|&O2lJE{9HM`adYjgm3N;4|?4G?bSf(J|W$KZsH0OtX=H(*iF5fbhk z3)zf6zGQiJkSkV+(E++2h!wZq;e59oBI*`UXXO$H8oNZVCU>Ti+dOxwQG z>T@C3ht?EK$-L;OX2!2QMfdghd*i`yzv zYJa3=mcy>;(9vMn94;(Ez2I2f}pJ#)}1=r&rU0T|eg! z>s3I=JAYmnhQ_WMh&jO|sQ-@yI7Qo-;@zU)MWu$dXrTaI>Pofs<8l1@3 zM=pPsYHTrVCsQH}@8GF@M@+Gc!kJsQJINVLE zyZf-1ygcv5VsNnd51Xg z-IvI4o4<%eKHj?dM4d0ue7IqiFmoobeb5NouXTdtE*(-7G^>TJCKoQh(n$M{Kj3{s z{6&1g{l)p-e58>2@s2XJBX(Zxe$!_#4G?BuGA&Or%sKip@11}=8Hx1meQluY$hBcA zsSP@n(^4jDYKe&7z_WndI->9GzHRnIEohtd>l;b^BJL|*TIa3!L=GzFzvj5#L9VUK zDbw@kr{CQU4XmhILPvAnJ1Vw}k@gifQP(a_Bo6})`aS*h0USBje-^ov13IghSWicY z&^<$$hi3HFQDw?kg6}5r&_lcGs?8tF1K(PM%?gX-Aa}oGWup2T+S7lz;8Qy{*sxQ7 z!s22*89#KJ?QDMyneSxU%eJlr*pttkaDCuJW-m&AyIA~!Y~RmDrx@4)mt4#y&kIqY z{WU4g;Mr61#!g$k$YvWccNMG=(7p+!M${g|`bRJ}wc6_2X${i7?XXk)=}?lsO>8Er z#^pbLhWr8V8{#kG1MV-*H+*SO`?bx-DCrj6cSaig^o1eL;kfxFbc+Ft{)aqHTF4?f zda2J0?0IG0Bb8fBY;OCsgu+Xp-U^z%vW|z$zd2Dw+yn4s*IHWp&ljk9sO{bK?>imO zIk&gRmR7+|2UVf<3{xaw&D(92&TVALvEwYNVQ)eHqv=hNh+dNKwd+t)RvFp1*Y)D* z`!WHjVafgZvJr1fUF+V_tm_96aFfepO+&MaBBJAGb6#Fu<_)9?crU) z#8_X(@V4V!IBaumYx?3-!tjFWHXVME^l}$@@o78-qt`O~O>7?#do9zs{frkNV$J&Q z&t<*@`7iP_WhV1t_IYKn3mP56VPmlG(<9nygZbnSOu63MxCKi3f z-g4+ret!+D8@kP*BUT2wxpbV1T?6q57V+49whZ1qFyCFW@R z=|*Nh_8n4l3LsxsPybjO5&-Hc%){bOuMwRgH&`a%OF9mxNPOjd0PD=2dX}(z5!4sR zf03Udf57{O_>1`P-~Rq@zDGYb3nZ!>r;?Tng5<>?z_9yhB(+O|+8x4We(O^|xuD~I za6sw<(bFp0dtpUAVZ3{My7=85T3W*Q`O`TTYR>uRFWybdXoqJ$RhxVnXcxUhF2eM3 z%Gdtk1_g-<*fF89up?syt-8l;TSQ%xx%EFX4oUerwbPrsqQK>myT4EY1z zH^g7W2i#wrZ+#Efp@du7l)@v)!i;5dl*`05i>9^HaI~;Hlzq4cb}9?Rv}wKu{T8|6 zFUD)BAX7d5qvxdQh2EYtxydGw+E+|z269kqGdGT?9vKGj)NOs;Cx%Gb*T?np+c{~C z_XDbTBF2e`L0_-jwq6MTJ=I&?Q4REnRIsvB9W-*rRtWDYhisQIn~n|jWMi;tXy%hw zAn~H;=GbU75pFzbXW1i7m$3+SvheSuomYuh)xcVsFIWAgwR;Q%co)9yn2Uvyi(Q+I z*5{JY!I!U+2cN=;lgH08+)oAGedEhmOp;+)R1j06XEU*CWi+bTZM?|(# zotB54Zols)))&48eTu3fE6>_27o4~R58CYKCKFib;LF>c+d@Y{d!wOq?nz#{ba%UY zUB)B~UsW&NpfybxZ}qP6%S$I2N{3?Po{z#IVe_P4yI+zrsSZtzOX+aiwBzLBiW+!Z z>#FQ46As&I4TQc3IKdU^JCdq;`6N3ic>jg`GWfhZ=oFuSArWD3Uu!BOKp%VX=kdw| zYiZ7bFO5wL!gOdd@4e>OXHYlO_?T784bFrF^Gqi{C3^Xues8MP0zZR#~Q#Y+v7+yKJobpMX zyCu1{66^{K*O}~iNLVE|_}MkL!{xHTi8H!~X_@EG>JB<7(B9j+hrg(>QwO<<0!sTW zfl=8_W6QD^WX9@}o0?}LSu+q%e|=E_!{2wXr@BXj?}qHAN3WB>yTJ33P+BF}4}aD+ za7%;lKDV4$93GHx*2zDAtu>Jmr_dvb$Ar2 ze}YuJ_^OeCx3E+EV)D_IpJD9dhtiEUs*QU|&!!lxAYc!rC= z@%JmppX=AWo+(MyHLg?t60(W@8Zxr4Crf}bSjimj%kvPnUU2^I%~wY@_-QbGpgN%Q z&7K^E)ve@X)21!DnGm?!53_yl7?b|xnxHZFxkKUNpBK6Q^oIB$Crvr8V1 z-#hmC@zvYVC^&z0`+HY7ATe^;eCs39FYJBm*4QdKM#c5-OHM(mZI0)dmZS(Bv^W~3 zm6k(XwoO?&zMdt&1^lTh`+kVgFNRP2J;b<~f1}+V2I_mmyT4EY1zH^g7W2i#wrZ)2_2uh3L= z>b`t;fR*4n>Vb2GZo17HN-wL!bw|CFBPU{@BR||X!Y-VEX)z#lS7+8mN~HX>|`rQt3>APU6iF? zdYu>UOWaS(_|?Q6n-HKq{H?MC7@G-S=wcwWz9epzqd&En8X?c-{6Ur6JaDwyaj)h~ zA^ECbG5K9E5t<+IpS%9=yFVvw)vwn|OpqT-B38I(zkNnIE$bXTaA%FPq zzWon>5g%}WalVs}UnamyT4EY1zH^g7W z2i#wr?_f@1-9nZQ)p=9Wq`_O3GM(qX#CLUuoNvCqmzmiRk8XnK`=+W^+GXUPkb=y&b%n&vx4vF= zI0E(=)+j{Uz6S3G7hj>gPU4@}X2uOXw1u75I&_@zi85V8R;k*ewx>60z3 zfnY!m2xJKz&|zr=xd)%!8AnnezTTqUL;E?IJy9RV?Op)v8MApOk7NHFQ@1XyH`WW>a>I>w*$j^{J;C(~nAp_hoFHBXukMPJ4^1Y(=fG^n z?tDjhbll_v^Wp4&-<|sVyXz=TwuCdQPYble@kf@^7rpu+?~Frsg!C9ZDefK{RPKUL zUumP=y=BDi+lK&-Tvw>B^dE0omQ4aLZ2n-;W={I}w+={|wLrnAs?9mv6(nH#mOcN# zTjF?N7q6@G0ErZS_jC8HM!59sOVQC(%Dce+T^s)W@jbP+uVbMSh0- z0q+~)FX994FV5HC@xjXG&k7WiXf(@q!)=s->U>n^FgK+>OkQa$FC*-iB#g(^Vj--A z?M*oK64=hwU;e}%4Xtyh7jtd@y-#ykco{bP@4nSkbg1!ufFZM9sjo&YgrlW?!;(vh zWLZ?Irr@1_-!pR7pVr&pP6So&j_nP!B8$m4dVaQ@A!74EuQfh<5sNsBpNYNpa4gAj z$y>L3F#jm+>7l@FKc>BEn~1G#_JO!zl7c)W!dw?`#2p4=EszsS#!Kj3{s{6&1g{l)q6=Uqt+o~9@ns{dl* zU14gJwIm>1e~-LIIXT-WPdhUd!Y_EQdZRpZn>TkN@&{%#ShO#QY8OA1^}`-}7axaMQ2Nx@R;TDqOu&*VZl?W%XwNzVw@Hrv_d?hGZCw&kKF-59sOVQC(%Dce+T^s z)W@jbP+uVbMSh0-0q+~)FX994FV2@MJE+oiVht6yWsUwfskBAa)4eE&Z#A|py#LS?21MMLYlE0shV zq(rHVjD)15q(nlpqa`aOBda8?+X`7xRtlBKNFhSS&+nY?^Yg!RoO7M)ysqbXyxwlZOEHuc&s{>J?}Vi zDhc-q3Oh!@a6e4~6|~ex7M`TnURa!6!nvKk?7z?(E4r7ME-XC${nc@@b7)~% z6^{$~?#ntn?>a%jM}gk~UjqIC{XF_(^qc74&<~-%K)#Or7x^UeGvqtSAK;JS-{3Ex zf1%HyAMn0m{$f7h{^EQ~#)B$KngtlsHjUDXL;XZlAiVpv=>o>vX=ByLUk~V@M?{zD z!bg+7S%deAZaVQqh0+RwK;bqK=I=~V)5KJ{a2AOZD~l>;#zry za5gPVtBZA|e~YiH+;(!IOCImoXH)J@W>ZJYk{_nzuyxv(-%Csh_&)Gw;G@9rfG+|6 zfPNnRG5SsPZ|H~6Um#yc{)>DP`5E#ZW-?F0G zJicl$yV4R}wgj6JQ}GK5g*MmdaiiQuqs5UVKj^S*xY;FAF{Aul#h4sjuKOuZZt~os zqVx7o-)ci6=4TZpGbgChukO<8(xx=@^()!QET728-iml4;7BxXEGp#wvXRd$QT`dFY(1(>i z(kJuj^DP`5E#ZW$Ep@|ifXK3qtkUN{{ug{I_(|}6;LpHEf!_gN0{#L0Jo;nwo9N%r523$6zK;AC`6Ti) zq`-630Ea;lt;jH3Ut>WjS{9+Jq@^$3D$S0AXA>Tp%0Dlbs27dwl3w;LtfcFja7xMx47w4;>nY>~4wlz$8 z_4i*d&&#nZKDSGl^olcFcf%yw$1{o1#^(p^zda$tGtUk;G)B@Q(wb)cDvema>fKTu z_KL>eIa|K-ZaQhpw~aB5{FmpHSev<}J)Bs!IJuN;dP2dkgRchv3qBS6B=|n?XW*m2 z?|?4>|A2lT{W1DY^l#{g&|e^5NB)a^68RbO9pn%2$MA3P7tp`ZXV4FL-!OkMA8>zh zzQO;Z-Y4EvV9sQIS6SyF%Ek-me+>AOL|q~RJA9PW$QxnzfYuUsvgo+h!XwU=boEBP z!Q_=6>0jZf)s0&w^S=;byTXOOG$?g@_N$u9RAa65N8Nqt6#D_-*TGkV{{^24eiD2i z_%rZP;CH~6fPX+gkNz0_Ci*w@L+CG%uOt6OK8gGc`3~|2_+$7t_zUP?=riaCyl_yJLN98vWb6^wqcFyTs41v;Ji~OB*++D4hCU zLXPe~ddrn#lKYBVv|MFYD0LC#sa6^;C(o`-b{BT@KNA*z?Xo3KtGTE82u*t zH}pg3FOaVz|3yBD{0#XH@(1{1_&4|q=wIkF=m)%Sn7^10xW72xfXI!lwL9gQoUNm_ zt9!?275Az&%kR69mIWEhelde2;Fs69xDE#+VBvMsb7wSloNih1^H@K{eirtJu-}6H z3+xAgUk6_e{ug{I_(|}6;LpHEf!_gN0{#L0Jo;nwo9N%r523$6zK;AC`6Ti)q@^$3D$S0AXA>Tp%0Dlbs_MgA_pZq@^$3D$S0AX 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%0Dlbs27dwl3w;LtfcFja z7xMx47w5~t8Z@n#wPN!39+js@FN~y|3BT2X-#C#2nR`|n6~<1!Z*R*+=0?yZ;XjS^ zZ)8*d1HOIn#!qPHK$YR;@I-MAtmfaBH{g5uuvwYtt;z=%~ zRbR8S4<|zrN1J>1vvic&?c+7hC9*}o_U>Bwf`Xp}-v|B-d=&T{@Fn0M(9fekM!$*v z4gC=M3*_s_f00ijKSREQ`~m(L{tf;D`WN~P`T_47<}c<0?k~=FT;DZ;j;k`wsww$9 zUM^=hT$H%Btd^76BsXn;$@4+F!A#L_JmoL3u+UE|zEV$1b#A8#jcSuTkE3Ug?%qIy zhPMqS)|gUuU%O-$rzx>2a~7_?vyGU`6&ZUc@1WH)))@Gf{n>P?K0j=gwF8OsDl8eg zWlO=Qf}aH62mTCv6!;zRCEy>>&!ay^zlr`0{Sf*K(@CP!x*@YkRE0L(YG}#`m_u`F z<-10;^=SS0;K#H{f24La*0jUIk%IpPp9+2wd>{BT@KNA*z?Xo3KtGTE82u*tH}pg3 zFOaVz|3yBD{0#XH@(1{1_&4|q=wIkF=m)%Sn7^10xW72xe_nZ>`ngk?q1_ib-D3(# z@OZM(XT>tQ?VZhA!z0DSUcaSIYPu$U`dDH4d2LC-SA+irp9+2wd>{BT@KNA* zz?Xo3KtGTE82u*tH}pg3FOaVz|3yBD{0#XH@(1{1_&4|q=wIkF=m)%Sn7^10xW72x zz)f~KQ{@;&Zm(YcZso;HY3g&$X)G^$r|ny3!9_DFD;Xy5`TiJncyZ+7*!6SNQNpK? z5i}&JqPNeivzMYtHP^MW0|n^U=K%#V37e=0m%rjplby8u-w%<8>61&d|Kr!eSA+ir zp9+2wd>{BT@KNA*z?Xo3KtGTE82u*tH}pg3FOaVz|3yBD{0#XH@(1{1_&4|q=wIkF z=m)%Sn7^10xW72x{qnncFHK#@-25lb!~0{HuJSv>f9JFS5qoNJGhyH$vGvamtnN8Q z&mN7rqu6?aL#H$a96!ODVHx$><55f z2VV{T7kn!CN$`E(&%j54-vM6&{sH|w`eXE)=-r_P_}LW0-ldg$+Sq&mXo zni?@X$?P8vnO~OX;lY*l?#r_5M1Hi9? zuLl1MJ{9~V_&)Gw;G@9rfG+|6fPNnRG5SsPZ|H~6Um#yc{)>DP`5E#ZWq8m=SialJ$th+cxpkj|KvplA7=h60sjFtb%2riRBGCx0< zn5-Kj5l-T=<^{bZdgI3$osk||@uWC&@8&+b>xkOgov*w8+i$`C1@;5LuY<1!{|i19 z{3Q53@Mqwo!0&)B0snx09{n-;P4sW*htOXjUq}9nd=mK?@*U(4@W=3P@E6d(&}YyO zc;7I8F&}V$alSdmzt$hlT)`}s;GfpcBgKdqc@4EG4AAvreOvn@2I-<-c|T56bzi}`^2i}U?CamM!0=yIk=J^d47FUu;f zk{#A>U(Bk$y&0-z-AgB~Y?2mv-bdCP0Dc{OHTYlf zso*EU_klkH9|e8~d&yepRe}F%Re}lh({)Ik+ ze!%;N`HT60`-}5k{#5dx^B*N`0KX2t8vHN#RPdAF`@o-pj{?5~z6AUO`g!!n=r_^7p&vqj zfqWhLFY-y`XUKPuKfoWuzrkNX|3aTZKj3}C{Kb60{l)nv9zFJNLRp?U&2@98+vNV< zDxb}j-4c`E^%_zT|6tHZvA>D^LhQd`KMVUq*l)r91@;5LuY<1!{|i19{3Q53@Mqwo z!0&)B0snx09{n-;P4sW*htOXjUq}9nd=mK?@*U(4@W=3P@E6d(&}YyOc;7I8F&}V$ zalVbVhyAzYE@eLUe3MRSmSBwkuHb!=Da~R(7Wq@^$3D$S0AXA>Tp%0Dlbs27dwl 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 D3P99R literal 0 HcmV?d00001 diff --git a/source/tests/pt/hessian/data/H8C4N2O/type.raw b/source/tests/pt/hessian/data/H8C4N2O/type.raw new file mode 100644 index 0000000000..a6510b1c81 --- /dev/null +++ b/source/tests/pt/hessian/data/H8C4N2O/type.raw @@ -0,0 +1,15 @@ +0 +0 +0 +2 +2 +0 +3 +1 +1 +1 +1 +1 +1 +1 +1 diff --git a/source/tests/pt/hessian/data/H8C4N2O/type_map.raw b/source/tests/pt/hessian/data/H8C4N2O/type_map.raw new file mode 100644 index 0000000000..5d0a0b4b31 --- /dev/null +++ b/source/tests/pt/hessian/data/H8C4N2O/type_map.raw @@ -0,0 +1,4 @@ +C +H +N +O diff --git a/source/tests/pt/test_loss.py b/source/tests/pt/test_loss.py index ddce7b6322..6fb5afde40 100644 --- a/source/tests/pt/test_loss.py +++ b/source/tests/pt/test_loss.py @@ -12,6 +12,7 @@ ) from deepmd.pt.loss import ( + EnergyHessianStdLoss, EnergySpinLoss, EnergyStdLoss, ) @@ -52,6 +53,18 @@ def setUp(self): if not self.spin: self.system = str(Path(__file__).parent / "water/data/data_0") self.type_map = ["H", "O"] + if self.hess: + self.system = str(Path(__file__).parent / "hessian/data/H8C4N2O") + self.type_map = ["C", "H", "N", "O"] + energy_data_requirement.append( + DataRequirementItem( + "hessian", + ndof=1, + atomic=True, + must=False, + high_prec=False, + ) + ) else: self.system = str(Path(__file__).parent / "NiO/data/data_0") self.type_map = ["Ni", "O"] @@ -238,6 +251,14 @@ def setUp(self): "drdq": torch.from_numpy(drdq), "atom_ener_coeff": torch.from_numpy(atom_ener_coeff), } + if self.hess: + l_hessian = np_batch["hessian"] + p_hessian = np.ones_like(l_hessian) + self.model_pred["hessian"] = torch.from_numpy(p_hessian) + self.label["hessian"] = torch.from_numpy(l_hessian) + self.label["find_hessian"] = 1.0 + self.label_absent["hessian"] = torch.from_numpy(l_hessian) + else: self.model_pred = { "energy": torch.from_numpy(p_energy), @@ -310,6 +331,7 @@ def setUp(self): self.limit_pref_v, ) self.spin = False + self.hess = False super().setUp() def test_consistency(self): @@ -399,6 +421,7 @@ def setUp(self): numb_generalized_coord=self.numb_generalized_coord, ) self.spin = False + self.hess = False super().setUp() def test_consistency(self): @@ -469,6 +492,7 @@ def setUp(self): enable_atom_ener_coeff=True, ) self.spin = False + self.hess = False super().setUp() def test_consistency(self): @@ -539,6 +563,90 @@ def setUp(self): relative_f=0.1, ) self.spin = False + self.hess = False + super().setUp() + + def test_consistency(self): + with tf.Session(graph=self.g) as sess: + tf_loss, tf_more_loss = sess.run( + self.tf_loss_sess, feed_dict=self.feed_dict + ) + + def fake_model(): + return self.model_pred + + _, pt_loss, pt_more_loss = self.pt_loss( + {}, + fake_model, + self.label, + self.nloc, + self.cur_lr, + ) + _, pt_loss_absent, pt_more_loss_absent = self.pt_loss( + {}, + fake_model, + self.label_absent, + self.nloc, + self.cur_lr, + ) + pt_loss = pt_loss.detach().cpu() + pt_loss_absent = pt_loss_absent.detach().cpu() + self.assertTrue(np.allclose(tf_loss, pt_loss.numpy())) + self.assertTrue(np.allclose(0.0, pt_loss_absent.numpy())) + for key in ["ener", "force", "virial"]: + self.assertTrue( + np.allclose( + tf_more_loss[f"l2_{key}_loss"], pt_more_loss[f"l2_{key}_loss"] + ) + ) + self.assertTrue(np.isnan(pt_more_loss_absent[f"l2_{key}_loss"])) + + +class TestEnerHessStdLoss(LossCommonTest): + def setUp(self): + self.start_lr = 1.1 + self.start_pref_e = 0.02 + self.limit_pref_e = 1.0 + self.start_pref_f = 1000.0 + self.limit_pref_f = 1.0 + self.start_pref_v = 0.02 + self.limit_pref_v = 1.0 + self.start_pref_h = 10.0 + self.limit_pref_h = 1.0 + # tf + self.tf_loss = EnerStdLoss( + self.start_lr, + self.start_pref_e, + self.limit_pref_e, + self.start_pref_f, + self.limit_pref_f, + self.start_pref_v, + self.limit_pref_v, + ) + # pt + self.pt_loss = EnergyStdLoss( + self.start_lr, + self.start_pref_e, + self.limit_pref_e, + self.start_pref_f, + self.limit_pref_f, + self.start_pref_v, + self.limit_pref_v, + ) + # pt-hess + self.pt_loss_h = EnergyHessianStdLoss( + starter_learning_rate=self.start_lr, + start_pref_e=self.start_pref_e, + limit_pref_e=self.limit_pref_e, + start_pref_f=self.start_pref_f, + limit_pref_f=self.limit_pref_f, + start_pref_v=self.start_pref_v, + limit_pref_v=self.limit_pref_v, + start_pref_h=self.start_pref_h, + limit_pref_h=self.limit_pref_h, + ) + self.spin = False + self.hess = True super().setUp() def test_consistency(self): @@ -566,15 +674,38 @@ def fake_model(): ) pt_loss = pt_loss.detach().cpu() pt_loss_absent = pt_loss_absent.detach().cpu() + _, pt_loss_h, pt_more_loss_h = self.pt_loss_h( + {}, + fake_model, + self.label, + self.nloc, + self.cur_lr, + ) + _, pt_loss_h_absent, pt_more_loss_h_absent = self.pt_loss_h( + {}, + fake_model, + self.label_absent, + self.nloc, + self.cur_lr, + ) + pt_loss_h_absent = pt_loss_h_absent.detach().cpu() self.assertTrue(np.allclose(tf_loss, pt_loss.numpy())) self.assertTrue(np.allclose(0.0, pt_loss_absent.numpy())) + self.assertTrue(np.allclose(0.0, pt_loss_h_absent.numpy())) for key in ["ener", "force", "virial"]: self.assertTrue( np.allclose( tf_more_loss[f"l2_{key}_loss"], pt_more_loss[f"l2_{key}_loss"] ) ) + self.assertTrue( + np.allclose( + pt_more_loss[f"l2_{key}_loss"], pt_more_loss_h[f"l2_{key}_loss"] + ) + ) self.assertTrue(np.isnan(pt_more_loss_absent[f"l2_{key}_loss"])) + for key in ["ener", "force", "virial", "hessian"]: + self.assertTrue(np.isnan(pt_more_loss_h_absent[f"l2_{key}_loss"])) class TestEnerSpinLoss(LossCommonTest): @@ -610,6 +741,7 @@ def setUp(self): self.limit_pref_fm, ) self.spin = True + self.hess = False super().setUp() def test_consistency(self): @@ -687,6 +819,7 @@ def setUp(self): limit_pref_ae=self.limit_pref_ae, ) self.spin = True + self.hess = False super().setUp() def test_consistency(self): @@ -760,6 +893,7 @@ def setUp(self): enable_atom_ener_coeff=True, ) self.spin = True + self.hess = False super().setUp() def test_consistency(self): From 0256c0b33ce3ef38eda2a97d3b80f26d31c7054b Mon Sep 17 00:00:00 2001 From: 1azyking <137011979@qq.com> Date: Tue, 29 Oct 2024 11:32:14 +0800 Subject: [PATCH 159/189] Resolving conversations --- CITATIONS.bib.original | 364 ------------------ ...2e94b4f98ca94a510cbcad2bd47b496b45b8b.json | 1 - 2 files changed, 365 deletions(-) delete mode 100644 CITATIONS.bib.original delete mode 100644 node_modules/.cache/prettier/.prettier-caches/9572e94b4f98ca94a510cbcad2bd47b496b45b8b.json diff --git a/CITATIONS.bib.original b/CITATIONS.bib.original deleted file mode 100644 index d5524a14f6..0000000000 --- a/CITATIONS.bib.original +++ /dev/null @@ -1,364 +0,0 @@ -The proposed feature of each article is described in the "annote" field. -Please cite a article if any feature is used -@article{Wang_ComputPhysCommun_2018_v228_p178, - annote = {general purpose}, - author = {Wang, Han and Zhang, Linfeng and Han, Jiequn and E, Weinan}, - doi = {10.1016/j.cpc.2018.03.016}, - year = 2018, - month = {jul}, - publisher = {Elsevier {BV}}, - volume = 228, - journal = {Comput. Phys. Comm.}, - title = { - {DeePMD-kit: A deep learning package for many-body potential energy - representation and molecular dynamics} - }, - pages = {178--184}, -} - -@article{Zeng_JChemPhys_2023_v159_p054801, - annote = {general purpose}, - title = {{DeePMD-kit v2: A software package for deep potential models}}, - author = { - Jinzhe Zeng and Duo Zhang and Denghui Lu and Pinghui Mo and Zeyu Li and - Yixiao Chen and Mari{\'a}n Rynik and Li'ang Huang and Ziyao Li and Shaochen - Shi and Yingze Wang and Haotian Ye and Ping Tuo and Jiabin Yang and Ye Ding - and Yifan Li and Davide Tisi and Qiyu Zeng and Han Bao and Yu Xia and - Jiameng Huang and Koki Muraoka and Yibo Wang and Junhan Chang and Fengbo - Yuan and Sigbj{\o}rn L{\o}land Bore and Chun Cai and Yinnian Lin and Bo - Wang and Jiayan Xu and Jia-Xin Zhu and Chenxing Luo and Yuzhi Zhang and - Rhys E A Goodall and Wenshuo Liang and Anurag Kumar Singh and Sikai Yao and - Jingchao Zhang and Renata Wentzcovitch and Jiequn Han and Jie Liu and Weile - Jia and Darrin M York and Weinan E and Roberto Car and Linfeng Zhang and - Han Wang - }, - journal = {J. Chem. Phys.}, - volume = 159, - issue = 5, - year = 2023, - pages = 054801, - doi = {10.1063/5.0155600}, -} - -@article{Lu_CompPhysCommun_2021_v259_p107624, - annote = {GPU support}, - title = { - {86 PFLOPS Deep Potential Molecular Dynamics simulation of 100 million - atoms with ab initio accuracy} - }, - author = { - Lu, Denghui and Wang, Han and Chen, Mohan and Lin, Lin and Car, Roberto and - E, Weinan and Jia, Weile and Zhang, Linfeng - }, - journal = {Comput. Phys. Comm.}, - volume = 259, - pages = 107624, - year = 2021, - publisher = {Elsevier}, - doi = {10.1016/j.cpc.2020.107624}, -} - -@article{Zhang_PhysRevLett_2018_v120_p143001, - annote = {local frame (loc\_frame)}, - author = {Linfeng Zhang and Jiequn Han and Han Wang and Roberto Car and Weinan E}, - journal = {Phys. Rev. Lett.}, - number = 14, - pages = 143001, - publisher = {APS}, - title = { - {Deep potential molecular dynamics: a scalable model with the accuracy of - quantum mechanics} - }, - volume = 120, - year = 2018, - doi = {10.1103/PhysRevLett.120.143001}, -} - -@incollection{Zhang_BookChap_NIPS_2018_v31_p4436, - annote = {DeepPot-SE (se\_e2\_a, se\_e2\_r, se\_e3, se\_atten)}, - title = { - {End-to-end Symmetry Preserving Inter-atomic Potential Energy Model for - Finite and Extended Systems} - }, - author = { - Zhang, Linfeng and Han, Jiequn and Wang, Han and Saidi, Wissam and Car, - Roberto and E, Weinan - }, - booktitle = {Advances in Neural Information Processing Systems 31}, - editor = { - S. Bengio and H. Wallach and H. Larochelle and K. Grauman and N. - Cesa-Bianchi and R. Garnett - }, - pages = {4436--4446}, - year = 2018, - publisher = {Curran Associates, Inc.}, - url = {https://dl.acm.org/doi/10.5555/3327345.3327356}, -} - -@article{Wang_NuclFusion_2022_v62_p126013, - annote = {three-body embedding DeepPot-SE (se\_e3)}, - author = {Xiaoyang Wang and Yinan Wang and Linfeng Zhang and Fuzhi Dai and Han Wang}, - title = { - {A tungsten deep neural-network potential for simulating mechanical - property degradation under fusion service environment} - }, - journal = {Nucl. Fusion}, - year = 2022, - volume = 62, - issue = 12, - pages = 126013, - doi = {10.1088/1741-4326/ac888b}, -} - -@article{Zhang_NpjComputMater_2024_v10_p94, - annote = {DPA-1, attention-based descriptor}, - author = { - Duo Zhang and Hangrui Bi and Fu-Zhi Dai and Wanrun Jiang and Xinzijian Liu - and Linfeng Zhang and Han Wang - }, - title = { - {Pretraining of attention-based deep learning potential model for molecular - simulation} - }, - journal = {Npj Comput. Mater}, - year = 2024, - volume = 10, - issue = 1, - pages = 94, - doi = {10.1038/s41524-024-01278-7}, -} - -@misc{Zhang_2023_DPA2, - annote = {DPA-2}, - author = { - Duo Zhang and Xinzijian Liu and Xiangyu Zhang and Chengqian Zhang and Chun - Cai and Hangrui Bi and Yiming Du and Xuejian Qin and Jiameng Huang and - Bowen Li and Yifan Shan and Jinzhe Zeng and Yuzhi Zhang and Siyuan Liu and - Yifan Li and Junhan Chang and Xinyan Wang and Shuo Zhou and Jianchuan Liu - and Xiaoshan Luo and Zhenyu Wang and Wanrun Jiang and Jing Wu and Yudi Yang - and Jiyuan Yang and Manyi Yang and Fu-Qiang Gong and Linshuang Zhang and - Mengchao Shi and Fu-Zhi Dai and Darrin M. York and Shi Liu and Tong Zhu and - Zhicheng Zhong and Jian Lv and Jun Cheng and Weile Jia and Mohan Chen and - Guolin Ke and Weinan E and Linfeng Zhang and Han Wang - }, - title = { - {DPA-2: Towards a universal large atomic model for molecular and material - simulation} - }, - publisher = {arXiv}, - year = 2023, - doi = {10.48550/arXiv.2312.15492}, -} - -@article{Zhang_PhysPlasmas_2020_v27_p122704, - annote = {frame-specific parameters (e.g. electronic temperature)}, - author = { - Zhang, Yuzhi and Gao, Chang and Liu, Qianrui and Zhang, Linfeng and Wang, - Han and Chen, Mohan - }, - title = { - {Warm dense matter simulation via electron temperature dependent deep - potential molecular dynamics} - }, - journal = {Phys. Plasmas}, - volume = 27, - number = 12, - pages = 122704, - year = 2020, - month = 12, - doi = {10.1063/5.0023265}, -} - -@misc{Zeng_2023_TTMDPMD, - annote = {atom-specific parameter (e.g. electron temperature)}, - author = { - Zeng, Qiyu and Chen, Bo and Zhang, Shen and Kang, Dongdong and Wang, Han - and Yu, Xiaoxiang and Dai, Jiayu - }, - title = {{Full-scale ab initio simulations of laser-driven atomistic dynamics}}, - publisher = {arXiv}, - year = 2023, - doi = {10.48550/arXiv.2308.13863}, -} - -@article{Zhang_PhysRevB_2020_v102_p41121, - annote = {fit dipole}, - title = {{Deep neural network for the dielectric response of insulators}}, - author = { - Zhang, Linfeng and Chen, Mohan and Wu, Xifan and Wang, Han and E, Weinan - and Car, Roberto - }, - journal = {Phys. Rev. B}, - volume = 102, - number = 4, - pages = {041121}, - year = 2020, - publisher = {APS}, - doi = {10.1103/PhysRevB.102.041121}, -} - -@article{Sommers_PhysChemChemPhys_2020_v22_p10592, - annote = {fit polarizability}, - title = { - {Raman spectrum and polarizability of liquid water from deep neural - networks} - }, - author = { - Sommers, Grace M and Andrade, Marcos F Calegari and Zhang, Linfeng and - Wang, Han and Car, Roberto - }, - journal = {Phys. Chem. Chem. Phys.}, - volume = 22, - number = 19, - pages = {10592--10602}, - year = 2020, - publisher = {Royal Society of Chemistry}, - doi = {10.1039/D0CP01893G}, -} - -@article{Zeng_JChemTheoryComput_2023_v19_p1261, - annote = {fit relative energies}, - author = {Jinzhe Zeng and Yujun Tao and Timothy J Giese and Darrin M York}, - title = {{QD{\pi}: A Quantum Deep Potential Interaction Model for Drug Discovery}}, - journal = {J. Chem. Theory Comput.}, - year = 2023, - volume = 19, - issue = 4, - pages = {1261--1275}, - doi = {10.1021/acs.jctc.2c01172}, -} - -@article{Zeng_PhysRevB_2022_v105_p174109, - annote = {fit density of states}, - author = { - Qiyu Zeng and Bo Chen and Xiaoxiang Yu and Shen Zhang and Dongdong Kang and - Han Wang and Jiayu Dai - }, - title = { - {Towards large-scale and spatiotemporally resolved diagnosis of electronic - density of states by deep learning} - }, - journal = {Phys. Rev. B}, - year = 2022, - volume = 105, - issue = 17, - pages = 174109, - doi = {10.1103/PhysRevB.105.174109}, -} - -@article{Zhang_JChemPhys_2022_v156_p124107, - annote = {DPLR, se\_e2\_r, hybrid descriptor}, - author = { - Linfeng Zhang and Han Wang and Maria Carolina Muniz and Athanassios Z - Panagiotopoulos and Roberto Car and Weinan E - }, - title = {{A deep potential model with long-range electrostatic interactions}}, - journal = {J. Chem. Phys.}, - year = 2022, - volume = 156, - issue = 12, - pages = 124107, - doi = {10.1063/5.0083669}, -} - -@article{Zeng_JChemTheoryComput_2021_v17_p6993, - annote = {DPRc}, - title = { - {Development of Range-Corrected Deep Learning Potentials for Fast, Accurate - Quantum Mechanical/molecular Mechanical Simulations of Chemical Reactions - in Solution} - }, - author = { - Zeng, Jinzhe and Giese, Timothy J and Ekesan, {\c{S}}{\"o}len and York, - Darrin M - }, - journal = {J. Chem. Theory Comput.}, - year = 2021, - volume = 17, - issue = 11, - pages = {6993--7009}, - doi = {10.1021/acs.jctc.1c00201}, -} - -@article{Wang_ApplPhysLett_2019_v114_p244101, - annote = {Interpolation with a pair-wise potential}, - title = { - {Deep learning inter-atomic potential model for accurate irradiation damage - simulations} - }, - author = {Wang, Hao and Guo, Xun and Zhang, Linfeng and Wang, Han and Xue, Jianming}, - journal = {Appl. Phys. Lett.}, - volume = 114, - number = 24, - pages = 244101, - year = 2019, - publisher = {AIP Publishing LLC}, - doi = {10.1063/1.5098061}, -} - -@article{Zhang_PhysRevMater_2019_v3_p23804, - annote = {model deviation}, - title = { - {Active learning of uniformly accurate interatomic potentials for materials - simulation} - }, - author = {Linfeng Zhang and De-Ye Lin and Han Wang and Roberto Car and Weinan E}, - journal = {Phys. Rev. Mater.}, - volume = 3, - issue = 2, - pages = 23804, - year = 2019, - publisher = {American Physical Society}, - doi = {10.1103/PhysRevMaterials.3.023804}, -} - -@article{Lu_JChemTheoryComput_2022_v18_p5555, - annote = {DP Compress}, - author = { - Denghui Lu and Wanrun Jiang and Yixiao Chen and Linfeng Zhang and Weile Jia - and Han Wang and Mohan Chen - }, - title = { - {DP Compress: A Model Compression Scheme for Generating Efficient Deep - Potential Models} - }, - journal = {J. Chem. Theory Comput.}, - year = 2022, - volume = 18, - issue = 9, - pages = {5555--5567}, - doi = {10.1021/acs.jctc.2c00102}, -} - -@article{Mo_npjComputMater_2022_v8_p107, - annote = {NVNMD}, - author = { - Pinghui Mo and Chang Li and Dan Zhao and Yujia Zhang and Mengchao Shi and - Junhua Li and Jie Liu - }, - title = { - {Accurate and efficient molecular dynamics based on machine learning and - non von Neumann architecture} - }, - journal = {npj Comput. Mater.}, - year = 2022, - volume = 8, - issue = 1, - pages = 107, - doi = {10.1038/s41524-022-00773-z}, -} - -@article{Zeng_EnergyFuels_2021_v35_p762, - annote = {relative or atomic model deviation}, - author = {Jinzhe Zeng and Linfeng Zhang and Han Wang and Tong Zhu}, - title = { - {Exploring the Chemical Space of Linear Alkane Pyrolysis via Deep Potential - GENerator} - }, - journal = {Energy \& Fuels}, - volume = 35, - number = 1, - pages = {762--769}, - year = 2021, - doi = {10.1021/acs.energyfuels.0c03211}, -} diff --git a/node_modules/.cache/prettier/.prettier-caches/9572e94b4f98ca94a510cbcad2bd47b496b45b8b.json b/node_modules/.cache/prettier/.prettier-caches/9572e94b4f98ca94a510cbcad2bd47b496b45b8b.json deleted file mode 100644 index f375e7a9f2..0000000000 --- a/node_modules/.cache/prettier/.prettier-caches/9572e94b4f98ca94a510cbcad2bd47b496b45b8b.json +++ /dev/null @@ -1 +0,0 @@ -{"0765adf934b9346e26b8bfb827bcd935a35bd04c":{"files":{"doc/model/train-se-e2-r.md":["yuFfQl9cStJgS9k1tdey02KHgpc=",true],"doc/freeze/compress.md":["JnJr3Rjrl7IndgLMi4a/1g9j31w=",true],"doc/development/create-a-model-tf.md":["FdtlKxeC+1S+UbGzo9KVxT1201A=",true],"doc/model/train-se-e2-a-tebd.md":["T9HkWkC/ltc8tiIcyoFB2/HzJNs=",true]},"modified":1730171362928}} \ No newline at end of file From 90d2468042dfaf0adeadc87142d8fcbf254d386b Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:21:08 +0800 Subject: [PATCH 160/189] Update env_mat.py Taking idea from [deepmd/dpmodel/utils/env_mat.py](https://github.com/deepmodeling/deepmd-kit/blob/0517b593066b7810563c207e59b8e0e088289f9c/deepmd/dpmodel/utils/env_mat.py) Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/dpmodel/utils/env_mat.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deepmd/dpmodel/utils/env_mat.py b/deepmd/dpmodel/utils/env_mat.py index f4bc333a03..3cd729f553 100644 --- a/deepmd/dpmodel/utils/env_mat.py +++ b/deepmd/dpmodel/utils/env_mat.py @@ -61,6 +61,8 @@ def _make_env_mat( # nf x nloc x nnei x 3 diff = coord_r - coord_l # nf x nloc x nnei + # the grad of JAX vector_norm is NaN at x=0 + diff = xp.where(xp.abs(diff) < 1e-30, xp.full_like(diff, 1e-30), diff) length = xp.linalg.vector_norm(diff, axis=-1, keepdims=True) # for index 0 nloc atom length = length + xp.astype(~xp.expand_dims(mask, axis=-1), length.dtype) From 002defd3377dbbfed365715d9f4fc9cb471a3567 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:40:38 +0800 Subject: [PATCH 161/189] Update env_mat.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/dpmodel/utils/env_mat.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/deepmd/dpmodel/utils/env_mat.py b/deepmd/dpmodel/utils/env_mat.py index 3cd729f553..f4bc333a03 100644 --- a/deepmd/dpmodel/utils/env_mat.py +++ b/deepmd/dpmodel/utils/env_mat.py @@ -61,8 +61,6 @@ def _make_env_mat( # nf x nloc x nnei x 3 diff = coord_r - coord_l # nf x nloc x nnei - # the grad of JAX vector_norm is NaN at x=0 - diff = xp.where(xp.abs(diff) < 1e-30, xp.full_like(diff, 1e-30), diff) length = xp.linalg.vector_norm(diff, axis=-1, keepdims=True) # for index 0 nloc atom length = length + xp.astype(~xp.expand_dims(mask, axis=-1), length.dtype) From 737a4153066fdbb9de80ffe04afbc61b169f2e68 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:44:48 +0800 Subject: [PATCH 162/189] Update env_mat.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/descriptor/env_mat.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deepmd/pt/model/descriptor/env_mat.py b/deepmd/pt/model/descriptor/env_mat.py index dc7142249a..596bb26eaa 100644 --- a/deepmd/pt/model/descriptor/env_mat.py +++ b/deepmd/pt/model/descriptor/env_mat.py @@ -28,6 +28,8 @@ def _make_env_mat( coord_r = torch.gather(coord_pad, 1, index) coord_r = coord_r.view(bsz, natoms, nnei, 3) diff = coord_r - coord_l + # avoid the possibility that coord[:, -1:, :] + rcut is the same as the coordinate of a real atom + diff = xp.where(xp.abs(diff) < 1e-30, xp.full_like(diff, 1e-30), diff) length = torch.linalg.norm(diff, dim=-1, keepdim=True) # for index 0 nloc atom length = length + ~mask.unsqueeze(-1) From d04600d1cd6cb516d0a8d3356493a6bb3480b88f Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:52:09 +0800 Subject: [PATCH 163/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index ce9a6c2561..463afa9e85 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -545,7 +545,6 @@ def test_ener( ) if dp.has_hessian: _n_frames_, _n_hessian_ = test_data["hessian"][:numb_test].shape - _n_atoms_ = np.int32(np.sqrt(_n_hessian_) / 3) # n_hessian = 3na*3na data_h = test_data["hessian"][:numb_test].reshape(-1, 1) pred_h = hessian.reshape(-1, 1) h = np.concatenate( @@ -558,7 +557,7 @@ def test_ener( save_txt_file( detail_path.with_suffix(".h.out"), h, - header=f"{system}: data_h pred_h", + header=f"{system}: data_h pred_h (3N×3N matrix in row-major order)", append=append_detail, ) if not out_put_spin: From 4f11b666f3d92a7925474078f387753a9c4be59a Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:50:15 +0800 Subject: [PATCH 164/189] Update env_mat.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/dpmodel/utils/env_mat.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deepmd/dpmodel/utils/env_mat.py b/deepmd/dpmodel/utils/env_mat.py index f4bc333a03..3cd729f553 100644 --- a/deepmd/dpmodel/utils/env_mat.py +++ b/deepmd/dpmodel/utils/env_mat.py @@ -61,6 +61,8 @@ def _make_env_mat( # nf x nloc x nnei x 3 diff = coord_r - coord_l # nf x nloc x nnei + # the grad of JAX vector_norm is NaN at x=0 + diff = xp.where(xp.abs(diff) < 1e-30, xp.full_like(diff, 1e-30), diff) length = xp.linalg.vector_norm(diff, axis=-1, keepdims=True) # for index 0 nloc atom length = length + xp.astype(~xp.expand_dims(mask, axis=-1), length.dtype) From d1ff245693ecc7da39cbd75e85238c3cab31583f Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:50:48 +0800 Subject: [PATCH 165/189] Update env_mat.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/descriptor/env_mat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/pt/model/descriptor/env_mat.py b/deepmd/pt/model/descriptor/env_mat.py index 596bb26eaa..90855843fb 100644 --- a/deepmd/pt/model/descriptor/env_mat.py +++ b/deepmd/pt/model/descriptor/env_mat.py @@ -29,7 +29,7 @@ def _make_env_mat( coord_r = coord_r.view(bsz, natoms, nnei, 3) diff = coord_r - coord_l # avoid the possibility that coord[:, -1:, :] + rcut is the same as the coordinate of a real atom - diff = xp.where(xp.abs(diff) < 1e-30, xp.full_like(diff, 1e-30), diff) + diff = torchp.where(torch.abs(diff) < 1e-30, torch.full_like(diff, 1e-30), diff) length = torch.linalg.norm(diff, dim=-1, keepdim=True) # for index 0 nloc atom length = length + ~mask.unsqueeze(-1) From 7c3d1260009eb874a3a54d3e57e2edd6e8a152aa Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:51:17 +0800 Subject: [PATCH 166/189] Update test.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/entrypoints/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index 463afa9e85..565c65e251 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -557,7 +557,7 @@ def test_ener( save_txt_file( detail_path.with_suffix(".h.out"), h, - header=f"{system}: data_h pred_h (3N×3N matrix in row-major order)", + header=f"{system}: data_h pred_h (3Na*3Na matrix in row-major order)", append=append_detail, ) if not out_put_spin: From ffce842103d99d17e36af5fa8954c821cd523c4b Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:53:47 +0800 Subject: [PATCH 167/189] Update deep_pot.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/infer/deep_pot.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/deepmd/infer/deep_pot.py b/deepmd/infer/deep_pot.py index cdfc9cd1f5..734f545fbf 100644 --- a/deepmd/infer/deep_pot.py +++ b/deepmd/infer/deep_pot.py @@ -100,7 +100,9 @@ def eval( aparam: Optional[np.ndarray], mixed_type: bool, **kwargs: Any, - ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]: + ) -> tuple[ + np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray] + ]: pass @overload @@ -114,7 +116,7 @@ def eval( aparam: Optional[np.ndarray], mixed_type: bool, **kwargs: Any, - ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + ) -> tuple[np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray]]: pass @overload From 652ac0c59faad8999d6148c17a01cc7239687d40 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:57:21 +0800 Subject: [PATCH 168/189] Update env_mat.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/descriptor/env_mat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/pt/model/descriptor/env_mat.py b/deepmd/pt/model/descriptor/env_mat.py index 90855843fb..d349353d13 100644 --- a/deepmd/pt/model/descriptor/env_mat.py +++ b/deepmd/pt/model/descriptor/env_mat.py @@ -29,7 +29,7 @@ def _make_env_mat( coord_r = coord_r.view(bsz, natoms, nnei, 3) diff = coord_r - coord_l # avoid the possibility that coord[:, -1:, :] + rcut is the same as the coordinate of a real atom - diff = torchp.where(torch.abs(diff) < 1e-30, torch.full_like(diff, 1e-30), diff) + diff = torch.where(torch.abs(diff) < 1e-30, torch.full_like(diff, 1e-30), diff) length = torch.linalg.norm(diff, dim=-1, keepdim=True) # for index 0 nloc atom length = length + ~mask.unsqueeze(-1) From 664767a771faee9519f806dfdeed7adef0feea2f Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:24:58 +0800 Subject: [PATCH 169/189] Update deepmd/infer/deep_pot.py Co-authored-by: Jinzhe Zeng Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/infer/deep_pot.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/deepmd/infer/deep_pot.py b/deepmd/infer/deep_pot.py index 734f545fbf..82ca729656 100644 --- a/deepmd/infer/deep_pot.py +++ b/deepmd/infer/deep_pot.py @@ -100,9 +100,11 @@ def eval( aparam: Optional[np.ndarray], mixed_type: bool, **kwargs: Any, - ) -> tuple[ - np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray] - ]: + ) -> Union[tuple[ + np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray + ], tuple[ + np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray + ]]: pass @overload From 444b20d1f0ed5b89b11d6ca23f1ad85695118552 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:25:21 +0800 Subject: [PATCH 170/189] Update deepmd/pt/model/descriptor/env_mat.py Co-authored-by: Jinzhe Zeng Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/descriptor/env_mat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deepmd/pt/model/descriptor/env_mat.py b/deepmd/pt/model/descriptor/env_mat.py index d349353d13..6fb6a4ad8c 100644 --- a/deepmd/pt/model/descriptor/env_mat.py +++ b/deepmd/pt/model/descriptor/env_mat.py @@ -29,8 +29,8 @@ def _make_env_mat( coord_r = coord_r.view(bsz, natoms, nnei, 3) diff = coord_r - coord_l # avoid the possibility that coord[:, -1:, :] + rcut is the same as the coordinate of a real atom - diff = torch.where(torch.abs(diff) < 1e-30, torch.full_like(diff, 1e-30), diff) - length = torch.linalg.norm(diff, dim=-1, keepdim=True) + diff_ = torch.where(torch.abs(diff) < 1e-30, torch.full_like(diff, 1e-30), diff) + length = torch.linalg.norm(diff_, dim=-1, keepdim=True) # for index 0 nloc atom length = length + ~mask.unsqueeze(-1) t0 = 1 / (length + protection) From 4e8a9b53e2c210d6407eb9b42d685add94a37456 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:31:52 +0800 Subject: [PATCH 171/189] Update argcheck.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/utils/argcheck.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/deepmd/utils/argcheck.py b/deepmd/utils/argcheck.py index d55d3af9e0..3c04933401 100644 --- a/deepmd/utils/argcheck.py +++ b/deepmd/utils/argcheck.py @@ -2375,8 +2375,6 @@ def loss_ener_spin(): doc_limit_pref_fm = limit_pref("force_magnetic") doc_start_pref_v = start_pref("virial") doc_limit_pref_v = limit_pref("virial") - # doc_start_pref_h = start_pref("hessian") - # doc_limit_pref_h = limit_pref("hessian") doc_start_pref_ae = start_pref("atom_ener") doc_limit_pref_ae = limit_pref("atom_ener") doc_start_pref_pf = start_pref("atom_pref") @@ -2440,20 +2438,6 @@ def loss_ener_spin(): default=0.00, doc=doc_limit_pref_v, ), - # Argument( - # "start_pref_h", - # [float, int], - # optional=True, - # default=0.00, - # doc=doc_start_pref_h, - # ), - # Argument( - # "limit_pref_h", - # [float, int], - # optional=True, - # default=0.00, - # doc=doc_limit_pref_h, - # ), Argument( "start_pref_ae", [float, int], From 4343d16ef22eb05c93ad672cc689bacbbac6b979 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:34:43 +0800 Subject: [PATCH 172/189] Update deepmd/infer/deep_pot.py Co-authored-by: Jinzhe Zeng Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/infer/deep_pot.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/deepmd/infer/deep_pot.py b/deepmd/infer/deep_pot.py index 82ca729656..b992daef88 100644 --- a/deepmd/infer/deep_pot.py +++ b/deepmd/infer/deep_pot.py @@ -118,7 +118,10 @@ def eval( aparam: Optional[np.ndarray], mixed_type: bool, **kwargs: Any, - ) -> tuple[np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray]]: + ) -> Union[ + tuple[np.ndarray, np.ndarray, np.ndarray], + tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray], + ]: pass @overload From e577b595e554ccd7719aa02ac271362f45f9162e Mon Sep 17 00:00:00 2001 From: 1azyking <137011979@qq.com> Date: Sun, 10 Nov 2024 10:42:03 +0800 Subject: [PATCH 173/189] Resolving conversations --- deepmd/dpmodel/utils/env_mat.py | 4 +- deepmd/infer/deep_pot.py | 9 ++-- deepmd/pt/infer/deep_eval.py | 10 ++-- deepmd/pt/model/model/ener_hess_model.py | 51 ++++-------------- deepmd/pt/model/model/make_hessian_model.py | 59 +-------------------- doc/model/overall.md | 3 ++ doc/model/train-energy-hessian.md | 6 +-- 7 files changed, 27 insertions(+), 115 deletions(-) diff --git a/deepmd/dpmodel/utils/env_mat.py b/deepmd/dpmodel/utils/env_mat.py index 3cd729f553..aa8520202e 100644 --- a/deepmd/dpmodel/utils/env_mat.py +++ b/deepmd/dpmodel/utils/env_mat.py @@ -62,8 +62,8 @@ def _make_env_mat( diff = coord_r - coord_l # nf x nloc x nnei # the grad of JAX vector_norm is NaN at x=0 - diff = xp.where(xp.abs(diff) < 1e-30, xp.full_like(diff, 1e-30), diff) - length = xp.linalg.vector_norm(diff, axis=-1, keepdims=True) + diff_ = xp.where(xp.abs(diff) < 1e-30, xp.full_like(diff, 1e-30), diff) + length = xp.linalg.vector_norm(diff_, axis=-1, keepdims=True) # for index 0 nloc atom length = length + xp.astype(~xp.expand_dims(mask, axis=-1), length.dtype) t0 = 1 / (length + protection) diff --git a/deepmd/infer/deep_pot.py b/deepmd/infer/deep_pot.py index b992daef88..6e00a30f91 100644 --- a/deepmd/infer/deep_pot.py +++ b/deepmd/infer/deep_pot.py @@ -100,11 +100,10 @@ def eval( aparam: Optional[np.ndarray], mixed_type: bool, **kwargs: Any, - ) -> Union[tuple[ - np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray - ], tuple[ - np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray - ]]: + ) -> Union[ + tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray], + tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray], + ]: pass @overload diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index b24c47a92e..4d4878ab9c 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -130,8 +130,10 @@ def __init__( ] = state_dict[item].clone() state_dict = state_dict_head model = get_model(self.input_param).to(DEVICE) - if "Hessian" not in str(type(model)): + try: model = torch.jit.script(model) + except RuntimeError: + model = model self.dp = ModelWrapper(model) self.dp.load_state_dict(state_dict) elif str(self.model_path).endswith(".pth"): @@ -160,11 +162,7 @@ def __init__( self._has_spin = getattr(self.dp.model["Default"], "has_spin", False) if callable(self._has_spin): self._has_spin = self._has_spin() - self._has_hessian = hasattr(self, "input_param") and self.input_param.get( - "hessian_mode", False - ) - if callable(self._has_hessian): - self._has_hessian = self._has_hessian() + self._has_hessian = self.model_def_script.get("hessian_mode", False) def get_rcut(self) -> float: """Get the cutoff radius of this model.""" diff --git a/deepmd/pt/model/model/ener_hess_model.py b/deepmd/pt/model/model/ener_hess_model.py index 07d0de89da..2bfd652c5b 100644 --- a/deepmd/pt/model/model/ener_hess_model.py +++ b/deepmd/pt/model/model/ener_hess_model.py @@ -69,7 +69,6 @@ def forward( aparam: Optional[torch.Tensor] = None, do_atomic_virial: bool = False, ) -> dict[str, torch.Tensor]: - self.requires_hessian("energy") model_ret = self.forward_common( coord, atype, @@ -78,6 +77,15 @@ def forward( aparam=aparam, do_atomic_virial=do_atomic_virial, ) + self.requires_hessian("energy") + hess = self._cal_hessian_all( + coord, + atype, + box, + fparam=fparam, + aparam=aparam, + ) + model_ret.update(hess) if self.get_fitting_net() is not None: model_predict = {} model_predict["atom_energy"] = model_ret["energy"] @@ -100,44 +108,3 @@ def forward( model_predict = model_ret model_predict["updated_coord"] += coord return model_predict - - @torch.jit.export - def forward_lower( - self, - extended_coord, - extended_atype, - nlist, - mapping: Optional[torch.Tensor] = None, - fparam: Optional[torch.Tensor] = None, - aparam: Optional[torch.Tensor] = None, - do_atomic_virial: bool = False, - comm_dict: Optional[dict[str, torch.Tensor]] = None, - ): - model_ret = self.forward_common_lower( - extended_coord, - extended_atype, - nlist, - mapping, - fparam=fparam, - aparam=aparam, - do_atomic_virial=do_atomic_virial, - comm_dict=comm_dict, - ) - if self.get_fitting_net() is not None: - model_predict = {} - model_predict["atom_energy"] = model_ret["energy"] - model_predict["energy"] = model_ret["energy_redu"] - if self.do_grad_r("energy"): - model_predict["extended_force"] = model_ret["energy_derv_r"].squeeze(-2) - if self.do_grad_c("energy"): - model_predict["virial"] = model_ret["energy_derv_c_redu"].squeeze(-2) - if do_atomic_virial: - model_predict["extended_virial"] = model_ret[ - "energy_derv_c" - ].squeeze(-3) - else: - assert model_ret["dforce"] is not None - model_predict["dforce"] = model_ret["dforce"] - else: - model_predict = model_ret - return model_predict diff --git a/deepmd/pt/model/model/make_hessian_model.py b/deepmd/pt/model/model/make_hessian_model.py index 4584827f03..84cab1314b 100644 --- a/deepmd/pt/model/model/make_hessian_model.py +++ b/deepmd/pt/model/model/make_hessian_model.py @@ -58,68 +58,13 @@ def atomic_output_def(self): """Get the fitting output def.""" return self.hess_fitting_def - def forward_common( + def _cal_hessian_all( self, coord, atype, box: Optional[torch.Tensor] = None, fparam: Optional[torch.Tensor] = None, aparam: Optional[torch.Tensor] = None, - do_atomic_virial: bool = False, - ) -> dict[str, torch.Tensor]: - """Return model prediction. - - Parameters - ---------- - coord - The coordinates of the atoms. - shape: nf x (nloc x 3) - atype - The type of atoms. shape: nf x nloc - box - The simulation box. shape: nf x 9 - fparam - frame parameter. nf x ndf - aparam - atomic parameter. nf x nloc x nda - do_atomic_virial - If calculate the atomic virial. - - Returns - ------- - ret_dict - The result dict of type dict[str,torch.Tensor]. - The keys are defined by the `ModelOutputDef`. - - """ - ret = super().forward_common( - coord, - atype, - box=box, - fparam=fparam, - aparam=aparam, - do_atomic_virial=do_atomic_virial, - ) - vdef = self.atomic_output_def() - hess_yes = [vdef[kk].r_hessian for kk in vdef.keys()] - if any(hess_yes): - hess = self._cal_hessian_all( - coord, - atype, - box=box, - fparam=fparam, - aparam=aparam, - ) - ret.update(hess) - return ret - - def _cal_hessian_all( - self, - coord: torch.Tensor, - atype: torch.Tensor, - box: Optional[torch.Tensor] = None, - fparam: Optional[torch.Tensor] = None, - aparam: Optional[torch.Tensor] = None, ) -> dict[str, torch.Tensor]: nf, nloc = atype.shape coord = coord.view([nf, (nloc * 3)]) @@ -184,7 +129,7 @@ def __init__( self, obj: CM, ci: int, - atype: torch.Tensor, + atype, box: Optional[torch.Tensor], fparam: Optional[torch.Tensor], aparam: Optional[torch.Tensor], diff --git a/doc/model/overall.md b/doc/model/overall.md index 8499c64b79..2028e5b6a2 100644 --- a/doc/model/overall.md +++ b/doc/model/overall.md @@ -58,5 +58,8 @@ DeePMD-kit implements the following descriptors: The fitting of the following physical properties is supported 1. [`ener`](train-energy.md): Fit the energy of the system. The force (derivative with atom positions), the virial (derivative with the box tensor) and the hessian (second-order derivative with atom positions) can also be trained. + :::{warning} + Due to the restrictions of torch jit script, the models trained with hessian are not jitable so that the frozen models cannot output hessians. + ::: 2. [`dipole`](train-fitting-tensor.md): The dipole moment. 3. [`polar`](train-fitting-tensor.md): The polarizability. diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md index 6cb80bd943..a8b350b1b3 100644 --- a/doc/model/train-energy-hessian.md +++ b/doc/model/train-energy-hessian.md @@ -8,7 +8,7 @@ To train a model that takes Hessian matrices, i.e., the second order derivatives ## Energy Hessian Loss -If you want to train with Hessians, you are expected to add the start and limit prefactors of Hessians, i.e., {ref}`start_pref_h ` and {ref}`limit_pref_h ` to the {ref}`loss ` section in the `input.json`: +If you want to train with Hessians, you are expected to add the start and limit prefactors of Hessians, i.e., {ref}`start_pref_h ` and {ref}`limit_pref_h ` to the {ref}`loss ` section in the `input.json`: ```json "loss": { @@ -24,9 +24,9 @@ If you want to train with Hessians, you are expected to add the start and limit }, ``` -The options {ref}`start_pref_e `, {ref}`limit_pref_e `, {ref}`start_pref_f `, {ref}`limit_pref_f `, {ref}`start_pref_v ` and {ref}`limit_pref_v ` determine the start and limit prefactors of energy, force, and virial, respectively. The calculation and definition of Hessian loss are the same as for the other terms. +The options {ref}`start_pref_e `, {ref}`limit_pref_e `, {ref}`start_pref_f `, {ref}`limit_pref_f `, {ref}`start_pref_v ` and {ref}`limit_pref_v ` determine the start and limit prefactors of energy, force, and virial, respectively. The calculation and definition of Hessian loss are the same as for the other terms. -If one does not want to train with virial, then he/she may set the virial prefactors {ref}`start_pref_v ` and {ref}`limit_pref_v ` to 0. +If one does not want to train with virial, then he/she may set the virial prefactors {ref}`start_pref_v ` and {ref}`limit_pref_v ` to 0. ## Hessian format in PyTorch From c6bafebd16fdfe4645b488e32902693c3ff57b2a Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 10 Nov 2024 11:14:48 +0800 Subject: [PATCH 174/189] Update overall.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/overall.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/model/overall.md b/doc/model/overall.md index 2028e5b6a2..cc72aa3887 100644 --- a/doc/model/overall.md +++ b/doc/model/overall.md @@ -58,8 +58,10 @@ DeePMD-kit implements the following descriptors: The fitting of the following physical properties is supported 1. [`ener`](train-energy.md): Fit the energy of the system. The force (derivative with atom positions), the virial (derivative with the box tensor) and the hessian (second-order derivative with atom positions) can also be trained. - :::{warning} - Due to the restrictions of torch jit script, the models trained with hessian are not jitable so that the frozen models cannot output hessians. - ::: + +:::{warning} +Due to the restrictions of torch jit script, the models trained with hessian are not jitable so that the frozen models cannot output hessians. +::: + 2. [`dipole`](train-fitting-tensor.md): The dipole moment. 3. [`polar`](train-fitting-tensor.md): The polarizability. From 1e7c55821b7127ca761dd2e347877ed76926984b Mon Sep 17 00:00:00 2001 From: 1azyking <137011979@qq.com> Date: Sun, 10 Nov 2024 11:30:38 +0800 Subject: [PATCH 175/189] Resolving conversations --- deepmd/entrypoints/test.py | 1 - deepmd/pt/infer/deep_eval.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/deepmd/entrypoints/test.py b/deepmd/entrypoints/test.py index 565c65e251..9a13bfe804 100644 --- a/deepmd/entrypoints/test.py +++ b/deepmd/entrypoints/test.py @@ -544,7 +544,6 @@ def test_ener( append=append_detail, ) if dp.has_hessian: - _n_frames_, _n_hessian_ = test_data["hessian"][:numb_test].shape data_h = test_data["hessian"][:numb_test].reshape(-1, 1) pred_h = hessian.reshape(-1, 1) h = np.concatenate( diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index 4d4878ab9c..1672d92b94 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -133,7 +133,7 @@ def __init__( try: model = torch.jit.script(model) except RuntimeError: - model = model + pass self.dp = ModelWrapper(model) self.dp.load_state_dict(state_dict) elif str(self.model_path).endswith(".pth"): From d28d50d5e4a784584f5ccd28d88694e3b97f536b Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 10 Nov 2024 11:57:03 +0800 Subject: [PATCH 176/189] Update train-energy-hessian.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/train-energy-hessian.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md index a8b350b1b3..81d5a3db7c 100644 --- a/doc/model/train-energy-hessian.md +++ b/doc/model/train-energy-hessian.md @@ -4,6 +4,10 @@ **Supported backends**: PyTorch {{ pytorch_icon }} ::: +:::{warning} +The model trained with Hessian cannot be frozen. +::: + To train a model that takes Hessian matrices, i.e., the second order derivatives of energies w.r.t coordinates as input, you only need to prepare full Hessian matrices and modify the `loss` section to define the Hessian-specific settings, keeping other sections the same as the normal energy model's input script. ## Energy Hessian Loss From c61df40bee9e386eda7a13414a4be623af1bdf05 Mon Sep 17 00:00:00 2001 From: 1azyking <137011979@qq.com> Date: Sun, 10 Nov 2024 12:33:06 +0800 Subject: [PATCH 177/189] Resolving conversations --- deepmd/pt/model/model/ener_hess_model.py | 10 +--- deepmd/pt/model/model/make_hessian_model.py | 55 +++++++++++++++++++++ 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/deepmd/pt/model/model/ener_hess_model.py b/deepmd/pt/model/model/ener_hess_model.py index 2bfd652c5b..9a5db1abc1 100644 --- a/deepmd/pt/model/model/ener_hess_model.py +++ b/deepmd/pt/model/model/ener_hess_model.py @@ -69,6 +69,7 @@ def forward( aparam: Optional[torch.Tensor] = None, do_atomic_virial: bool = False, ) -> dict[str, torch.Tensor]: + self.requires_hessian("energy") model_ret = self.forward_common( coord, atype, @@ -77,15 +78,6 @@ def forward( aparam=aparam, do_atomic_virial=do_atomic_virial, ) - self.requires_hessian("energy") - hess = self._cal_hessian_all( - coord, - atype, - box, - fparam=fparam, - aparam=aparam, - ) - model_ret.update(hess) if self.get_fitting_net() is not None: model_predict = {} model_predict["atom_energy"] = model_ret["energy"] diff --git a/deepmd/pt/model/model/make_hessian_model.py b/deepmd/pt/model/model/make_hessian_model.py index 84cab1314b..deb67ef4b7 100644 --- a/deepmd/pt/model/model/make_hessian_model.py +++ b/deepmd/pt/model/model/make_hessian_model.py @@ -58,6 +58,61 @@ def atomic_output_def(self): """Get the fitting output def.""" return self.hess_fitting_def + def forward_common( + self, + coord, + atype, + box: Optional[torch.Tensor] = None, + fparam: Optional[torch.Tensor] = None, + aparam: Optional[torch.Tensor] = None, + do_atomic_virial: bool = False, + ) -> dict[str, torch.Tensor]: + """Return model prediction. + + Parameters + ---------- + coord + The coordinates of the atoms. + shape: nf x (nloc x 3) + atype + The type of atoms. shape: nf x nloc + box + The simulation box. shape: nf x 9 + fparam + frame parameter. nf x ndf + aparam + atomic parameter. nf x nloc x nda + do_atomic_virial + If calculate the atomic virial. + + Returns + ------- + ret_dict + The result dict of type dict[str,torch.Tensor]. + The keys are defined by the `ModelOutputDef`. + + """ + ret = super().forward_common( + coord, + atype, + box=box, + fparam=fparam, + aparam=aparam, + do_atomic_virial=do_atomic_virial, + ) + vdef = self.atomic_output_def() + hess_yes = [vdef[kk].r_hessian for kk in vdef.keys()] + if any(hess_yes): + hess = self._cal_hessian_all( + coord, + atype, + box=box, + fparam=fparam, + aparam=aparam, + ) + ret.update(hess) + return ret + def _cal_hessian_all( self, coord, From 63719f036112f476e787ee6c02ccf4d9683509b2 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 10 Nov 2024 14:39:38 +0800 Subject: [PATCH 178/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/jax/infer/deep_eval.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deepmd/jax/infer/deep_eval.py b/deepmd/jax/infer/deep_eval.py index fc526a502e..eeec941c2b 100644 --- a/deepmd/jax/infer/deep_eval.py +++ b/deepmd/jax/infer/deep_eval.py @@ -405,6 +405,9 @@ def _get_output_shape(self, odef, nframes, natoms): elif odef.category == OutputVariableCategory.OUT: # atom_energy, atom_tensor return [nframes, natoms, *odef.shape, 1] + elif odef.category == OutputVariableCategory.DERV_R_DERV_R: + # hessian + return [nframes, 3 * natoms, 3 * natoms] else: raise RuntimeError("unknown category") From f5c5e2a64623505c99d0b7d23bb9d2f263d9234d Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:33:44 +0800 Subject: [PATCH 179/189] Update deep_eval.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/dpmodel/infer/deep_eval.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deepmd/dpmodel/infer/deep_eval.py b/deepmd/dpmodel/infer/deep_eval.py index 5463743ada..983696e291 100644 --- a/deepmd/dpmodel/infer/deep_eval.py +++ b/deepmd/dpmodel/infer/deep_eval.py @@ -383,6 +383,9 @@ def _get_output_shape(self, odef, nframes, natoms): # Something wrong here? # return [nframes, *shape, natoms, 1] return [nframes, natoms, *odef.shape, 1] + elif odef.category == OutputVariableCategory.DERV_R_DERV_R: + # hessian + return [nframes, 3 * natoms, 3 * natoms] else: raise RuntimeError("unknown category") From d947f24d55986cd4dc5b8db312e061f63f049369 Mon Sep 17 00:00:00 2001 From: 1azyking <137011979@qq.com> Date: Sat, 30 Nov 2024 21:24:18 +0800 Subject: [PATCH 180/189] Resolving conversations --- deepmd/pt/train/training.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index 536d3335e3..8ba6d1acd4 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -276,7 +276,11 @@ def get_lr(lr_params): } # Model - self.model = get_model_for_wrapper(model_params, resuming=resuming, loss_param_tmp) + self.model = get_model_for_wrapper( + model_params, + resuming=resuming, + loss_param_tmp=loss_param_tmp, + ) # Loss if not self.multi_task: From 6d351ed027042b6f716c0457cd87a1dd6bb9c48c Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:46:32 +0800 Subject: [PATCH 181/189] Update training.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/train/training.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepmd/pt/train/training.py b/deepmd/pt/train/training.py index 8ba6d1acd4..57aae3366b 100644 --- a/deepmd/pt/train/training.py +++ b/deepmd/pt/train/training.py @@ -279,7 +279,7 @@ def get_lr(lr_params): self.model = get_model_for_wrapper( model_params, resuming=resuming, - loss_param_tmp=loss_param_tmp, + _loss_params=loss_param_tmp, ) # Loss From c699e08fc829890085e0ef8cc86e6b798d0b3f3b Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:20:51 +0800 Subject: [PATCH 182/189] Update env_mat.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/descriptor/env_mat.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deepmd/pt/model/descriptor/env_mat.py b/deepmd/pt/model/descriptor/env_mat.py index 6fb6a4ad8c..dc7142249a 100644 --- a/deepmd/pt/model/descriptor/env_mat.py +++ b/deepmd/pt/model/descriptor/env_mat.py @@ -28,9 +28,7 @@ def _make_env_mat( coord_r = torch.gather(coord_pad, 1, index) coord_r = coord_r.view(bsz, natoms, nnei, 3) diff = coord_r - coord_l - # avoid the possibility that coord[:, -1:, :] + rcut is the same as the coordinate of a real atom - diff_ = torch.where(torch.abs(diff) < 1e-30, torch.full_like(diff, 1e-30), diff) - length = torch.linalg.norm(diff_, dim=-1, keepdim=True) + length = torch.linalg.norm(diff, dim=-1, keepdim=True) # for index 0 nloc atom length = length + ~mask.unsqueeze(-1) t0 = 1 / (length + protection) From d1361cad021e584a0c362591ebc3131f2b0ce19e Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:26:38 +0800 Subject: [PATCH 183/189] Update make_hessian_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/make_hessian_model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deepmd/pt/model/model/make_hessian_model.py b/deepmd/pt/model/model/make_hessian_model.py index f8410a3e37..4269976095 100644 --- a/deepmd/pt/model/model/make_hessian_model.py +++ b/deepmd/pt/model/model/make_hessian_model.py @@ -115,8 +115,8 @@ def forward_common( def _cal_hessian_all( self, - coord, - atype, + coord: torch.Tensor, + atype: torch.Tensor, box: Optional[torch.Tensor] = None, fparam: Optional[torch.Tensor] = None, aparam: Optional[torch.Tensor] = None, @@ -184,7 +184,7 @@ def __init__( self, obj: CM, ci: int, - atype, + atype: torch.Tensor, box: Optional[torch.Tensor], fparam: Optional[torch.Tensor], aparam: Optional[torch.Tensor], From 9c8700b6811aea4dd97ab4b7bf950c129a979159 Mon Sep 17 00:00:00 2001 From: 1azyking <137011979@qq.com> Date: Sat, 14 Dec 2024 15:55:01 +0800 Subject: [PATCH 184/189] Resovling conversations --- deepmd/pt/infer/deep_eval.py | 4 +- deepmd/pt/model/model/__init__.py | 8 +- deepmd/pt/model/model/ener_hess_model.py | 102 ------------- deepmd/pt/model/model/ener_model.py | 13 ++ deepmd/pt/model/model/make_hessian_model.py | 2 +- deepmd/utils/argcheck.py | 138 ------------------ doc/model/train-energy-hessian.md | 2 +- .../tests/pt/model/test_dp_hessian_model.py | 16 +- 8 files changed, 28 insertions(+), 257 deletions(-) delete mode 100644 deepmd/pt/model/model/ener_hess_model.py diff --git a/deepmd/pt/infer/deep_eval.py b/deepmd/pt/infer/deep_eval.py index 01ca84731d..dc3992dd90 100644 --- a/deepmd/pt/infer/deep_eval.py +++ b/deepmd/pt/infer/deep_eval.py @@ -130,10 +130,8 @@ def __init__( ] = state_dict[item].clone() state_dict = state_dict_head model = get_model(self.input_param).to(DEVICE) - try: + if not self.input_param.get("hessian_mode"): model = torch.jit.script(model) - except RuntimeError: - pass self.dp = ModelWrapper(model) self.dp.load_state_dict(state_dict) elif str(self.model_path).endswith(".pth"): diff --git a/deepmd/pt/model/model/__init__.py b/deepmd/pt/model/model/__init__.py index 2dd58841d1..37e664e82a 100644 --- a/deepmd/pt/model/model/__init__.py +++ b/deepmd/pt/model/model/__init__.py @@ -48,9 +48,6 @@ from .dp_zbl_model import ( DPZBLModel, ) -from .ener_hess_model import ( - EnergyHessianModel, -) from .ener_model import ( EnergyModel, ) @@ -266,8 +263,6 @@ def get_standard_model(model_params): modelcls = DOSModel elif fitting_net_type in ["ener", "direct_force_ener"]: modelcls = EnergyModel - if model_params.get("hessian_mode"): - modelcls = EnergyHessianModel elif fitting_net_type == "property": modelcls = PropertyModel else: @@ -281,6 +276,8 @@ def get_standard_model(model_params): pair_exclude_types=pair_exclude_types, preset_out_bias=preset_out_bias, ) + if model_params.get("hessian_mode"): + model.enable_hessian() model.model_def_script = json.dumps(model_params_old) return model @@ -306,7 +303,6 @@ def get_model(model_params): "DPModelCommon", "DPZBLModel", "DipoleModel", - "EnergyHessianModel", "EnergyModel", "FrozenModel", "LinearEnergyModel", diff --git a/deepmd/pt/model/model/ener_hess_model.py b/deepmd/pt/model/model/ener_hess_model.py deleted file mode 100644 index 9a5db1abc1..0000000000 --- a/deepmd/pt/model/model/ener_hess_model.py +++ /dev/null @@ -1,102 +0,0 @@ -# SPDX-License-Identifier: LGPL-3.0-or-later -from copy import ( - deepcopy, -) -from typing import ( - Optional, -) - -import torch - -from deepmd.pt.model.atomic_model import ( - DPEnergyAtomicModel, -) -from deepmd.pt.model.model.model import ( - BaseModel, -) - -from .dp_model import ( - DPModelCommon, -) -from .make_hessian_model import ( - make_hessian_model, -) -from .make_model import ( - make_model, -) - -DPEnergyModel_ = make_model(DPEnergyAtomicModel) -DPEnergyModel_ = make_hessian_model(DPEnergyModel_) - - -@BaseModel.register("ener_hess") -class EnergyHessianModel(DPModelCommon, DPEnergyModel_): - model_type = "ener_hess" - - def __init__( - self, - *args, - **kwargs, - ): - DPModelCommon.__init__(self) - DPEnergyModel_.__init__(self, *args, **kwargs) - - def translated_output_def(self): - out_def_data = self.model_output_def().get_data() - output_def = { - "atom_energy": deepcopy(out_def_data["energy"]), - "energy": deepcopy(out_def_data["energy_redu"]), - } - if self.do_grad_r("energy"): - output_def["force"] = deepcopy(out_def_data["energy_derv_r"]) - output_def["force"].squeeze(-2) - if self.do_grad_c("energy"): - output_def["virial"] = deepcopy(out_def_data["energy_derv_c_redu"]) - output_def["virial"].squeeze(-2) - output_def["atom_virial"] = deepcopy(out_def_data["energy_derv_c"]) - output_def["atom_virial"].squeeze(-3) - output_def["hessian"] = deepcopy(out_def_data["energy_derv_r_derv_r"]) - if "mask" in out_def_data: - output_def["mask"] = deepcopy(out_def_data["mask"]) - return output_def - - def forward( - self, - coord, - atype, - box: Optional[torch.Tensor] = None, - fparam: Optional[torch.Tensor] = None, - aparam: Optional[torch.Tensor] = None, - do_atomic_virial: bool = False, - ) -> dict[str, torch.Tensor]: - self.requires_hessian("energy") - model_ret = self.forward_common( - coord, - atype, - box, - fparam=fparam, - aparam=aparam, - do_atomic_virial=do_atomic_virial, - ) - if self.get_fitting_net() is not None: - model_predict = {} - model_predict["atom_energy"] = model_ret["energy"] - model_predict["energy"] = model_ret["energy_redu"] - if self.do_grad_r("energy"): - model_predict["force"] = model_ret["energy_derv_r"].squeeze(-2) - if self.do_grad_c("energy"): - model_predict["virial"] = model_ret["energy_derv_c_redu"].squeeze(-2) - if do_atomic_virial: - model_predict["atom_virial"] = model_ret["energy_derv_c"].squeeze( - -3 - ) - else: - model_predict["force"] = model_ret["dforce"] - if "mask" in model_ret: - model_predict["mask"] = model_ret["mask"] - model_predict["hessian"] = model_ret["energy_derv_r_derv_r"] - model_predict["hessian"].squeeze(-2) - else: - model_predict = model_ret - model_predict["updated_coord"] += coord - return model_predict diff --git a/deepmd/pt/model/model/ener_model.py b/deepmd/pt/model/model/ener_model.py index 9487bcc5bb..560ba12e84 100644 --- a/deepmd/pt/model/model/ener_model.py +++ b/deepmd/pt/model/model/ener_model.py @@ -15,11 +15,15 @@ from .dp_model import ( DPModelCommon, ) +from .make_hessian_model import ( + make_hessian_model, +) from .make_model import ( make_model, ) DPEnergyModel_ = make_model(DPEnergyAtomicModel) +DPEnergyModel_ = make_hessian_model(DPEnergyModel_) @BaseModel.register("ener") @@ -33,6 +37,11 @@ def __init__( ) -> None: DPModelCommon.__init__(self) DPEnergyModel_.__init__(self, *args, **kwargs) + self._hessian_enabled = False + + def enable_hessian(self): + self.requires_hessian("energy") + self._hessian_enabled = True def translated_output_def(self): out_def_data = self.model_output_def().get_data() @@ -50,6 +59,8 @@ def translated_output_def(self): output_def["atom_virial"].squeeze(-3) if "mask" in out_def_data: output_def["mask"] = out_def_data["mask"] + if self._hessian_enabled: + output_def["hessian"] = out_def_data["energy_derv_r_derv_r"] return output_def def forward( @@ -85,6 +96,8 @@ def forward( model_predict["force"] = model_ret["dforce"] if "mask" in model_ret: model_predict["mask"] = model_ret["mask"] + if self._hessian_enabled: + model_predict["hessian"] = model_ret["energy_derv_r_derv_r"].squeeze(-2) else: model_predict = model_ret model_predict["updated_coord"] += coord diff --git a/deepmd/pt/model/model/make_hessian_model.py b/deepmd/pt/model/model/make_hessian_model.py index 4269976095..000b9abea4 100644 --- a/deepmd/pt/model/model/make_hessian_model.py +++ b/deepmd/pt/model/model/make_hessian_model.py @@ -175,7 +175,7 @@ def _cal_hessian_one_component( hess = torch.autograd.functional.hessian( wc, coord, - create_graph=True, + create_graph=self.training, ) return hess diff --git a/deepmd/utils/argcheck.py b/deepmd/utils/argcheck.py index 290cb426db..7d7fd18602 100644 --- a/deepmd/utils/argcheck.py +++ b/deepmd/utils/argcheck.py @@ -2306,144 +2306,6 @@ def loss_ener(): ] -@loss_args_plugin.register("ener_hess") -def loss_ener_hess(): # loss of fitting EnergyHessianModel - doc_start_pref_e = start_pref("energy", abbr="e") - doc_limit_pref_e = limit_pref("energy") - doc_start_pref_f = start_pref("force", abbr="f") - doc_limit_pref_f = limit_pref("force") - doc_start_pref_v = start_pref("virial", abbr="v") - doc_limit_pref_v = limit_pref("virial") - doc_start_pref_h = start_pref("hessian", abbr="h") - doc_limit_pref_h = limit_pref("hessian") - doc_start_pref_ae = start_pref("atomic energy", label="atom_ener", abbr="ae") - doc_limit_pref_ae = limit_pref("atomic energy") - doc_start_pref_pf = start_pref( - "atomic prefactor force", label="atom_pref", abbr="pf" - ) - doc_limit_pref_pf = limit_pref("atomic prefactor force") - doc_start_pref_gf = start_pref("generalized force", label="drdq", abbr="gf") - doc_limit_pref_gf = limit_pref("generalized force") - doc_numb_generalized_coord = "The dimension of generalized coordinates. Required when generalized force loss is used." - doc_relative_f = "If provided, relative force error will be used in the loss. The difference of force will be normalized by the magnitude of the force in the label with a shift given by `relative_f`, i.e. DF_i / ( || F || + relative_f ) with DF denoting the difference between prediction and label and || F || denoting the L2 norm of the label." - doc_enable_atom_ener_coeff = "If true, the energy will be computed as \\sum_i c_i E_i. c_i should be provided by file atom_ener_coeff.npy in each data system, otherwise it's 1." - return [ - Argument( - "start_pref_e", - [float, int], - optional=True, - default=0.02, - doc=doc_start_pref_e, - ), - Argument( - "limit_pref_e", - [float, int], - optional=True, - default=1.00, - doc=doc_limit_pref_e, - ), - Argument( - "start_pref_f", - [float, int], - optional=True, - default=1000, - doc=doc_start_pref_f, - ), - Argument( - "limit_pref_f", - [float, int], - optional=True, - default=1.00, - doc=doc_limit_pref_f, - ), - Argument( - "start_pref_v", - [float, int], - optional=True, - default=0.00, - doc=doc_start_pref_v, - ), - Argument( - "limit_pref_v", - [float, int], - optional=True, - default=0.00, - doc=doc_limit_pref_v, - ), - Argument( - "start_pref_h", - [float, int], - optional=True, - default=0.00, - doc=doc_start_pref_h, - ), - Argument( - "limit_pref_h", - [float, int], - optional=True, - default=0.00, - doc=doc_limit_pref_h, - ), - Argument( - "start_pref_ae", - [float, int], - optional=True, - default=0.00, - doc=doc_start_pref_ae, - ), - Argument( - "limit_pref_ae", - [float, int], - optional=True, - default=0.00, - doc=doc_limit_pref_ae, - ), - Argument( - "start_pref_pf", - [float, int], - optional=True, - default=0.00, - doc=doc_start_pref_pf, - ), - Argument( - "limit_pref_pf", - [float, int], - optional=True, - default=0.00, - doc=doc_limit_pref_pf, - ), - Argument("relative_f", [float, None], optional=True, doc=doc_relative_f), - Argument( - "enable_atom_ener_coeff", - [bool], - optional=True, - default=False, - doc=doc_enable_atom_ener_coeff, - ), - Argument( - "start_pref_gf", - float, - optional=True, - default=0.0, - doc=doc_start_pref_gf, - ), - Argument( - "limit_pref_gf", - float, - optional=True, - default=0.0, - doc=doc_limit_pref_gf, - ), - Argument( - "numb_generalized_coord", - int, - optional=True, - default=0, - doc=doc_numb_generalized_coord, - ), - ] - - @loss_args_plugin.register("ener_spin") def loss_ener_spin(): doc_start_pref_e = start_pref("energy") diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md index 81d5a3db7c..02b3c52052 100644 --- a/doc/model/train-energy-hessian.md +++ b/doc/model/train-energy-hessian.md @@ -5,7 +5,7 @@ ::: :::{warning} -The model trained with Hessian cannot be frozen. +The model trained with Hessian cannot be frozen. If freezing is forced, the model will be treated as a standard energy model, and the frozen model will no longer be able to output Hessian predictions. ::: To train a model that takes Hessian matrices, i.e., the second order derivatives of energies w.r.t coordinates as input, you only need to prepare full Hessian matrices and modify the `loss` section to define the Hessian-specific settings, keeping other sections the same as the normal energy model's input script. diff --git a/source/tests/pt/model/test_dp_hessian_model.py b/source/tests/pt/model/test_dp_hessian_model.py index b4b1ff2c33..55631f67c6 100644 --- a/source/tests/pt/model/test_dp_hessian_model.py +++ b/source/tests/pt/model/test_dp_hessian_model.py @@ -7,7 +7,6 @@ DescrptSeA, ) from deepmd.pt.model.model import ( - EnergyHessianModel, EnergyModel, ) from deepmd.pt.model.task.ener import ( @@ -44,8 +43,10 @@ def test_self_consistency(self): mixed_types=ds.mixed_types(), ).to(env.DEVICE) type_map = ["foo", "bar"] - md0 = EnergyHessianModel(ds, ft, type_map=type_map).to(env.DEVICE) - md1 = EnergyHessianModel.deserialize(md0.serialize()).to(env.DEVICE) + md0 = EnergyModel(ds, ft, type_map=type_map).to(env.DEVICE) + md1 = EnergyModel.deserialize(md0.serialize()).to(env.DEVICE) + md0.enable_hessian() + md1.enable_hessian() args = [to_torch_tensor(ii) for ii in [self.coord, self.atype, self.cell]] ret0 = md0.forward(*args) ret1 = md1.forward(*args) @@ -95,7 +96,8 @@ def test_energy_consistency(self): ).to(env.DEVICE) type_map = ["foo", "bar"] md0 = EnergyModel(ds, ft, type_map=type_map).to(env.DEVICE) - md1 = EnergyHessianModel.deserialize(md0.serialize()).to(env.DEVICE) + md1 = EnergyModel.deserialize(md0.serialize()).to(env.DEVICE) + md1.enable_hessian() args = [to_torch_tensor(ii) for ii in [self.coord, self.atype, self.cell]] ret0 = md0.forward(*args) ret1 = md1.forward(*args) @@ -139,8 +141,10 @@ def test_forward_consistency(self): mixed_types=ds.mixed_types(), ).to(env.DEVICE) type_map = ["foo", "bar"] - md0 = EnergyHessianModel(ds, ft, type_map=type_map).to(env.DEVICE) - md1 = EnergyHessianModel.deserialize(md0.serialize()).to(env.DEVICE) + md0 = EnergyModel(ds, ft, type_map=type_map).to(env.DEVICE) + md1 = EnergyModel.deserialize(md0.serialize()).to(env.DEVICE) + md0.enable_hessian() + md1.enable_hessian() md0.requires_hessian("energy") args = [to_torch_tensor(ii) for ii in [self.coord, self.atype, self.cell]] ret0 = md0.forward_common(*args) From 7dead9c6cef822e274ac57bca60d6aef22e9f833 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 15 Dec 2024 16:42:19 +0800 Subject: [PATCH 185/189] Update ener_model.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- deepmd/pt/model/model/ener_model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deepmd/pt/model/model/ener_model.py b/deepmd/pt/model/model/ener_model.py index 560ba12e84..8064d3eac7 100644 --- a/deepmd/pt/model/model/ener_model.py +++ b/deepmd/pt/model/model/ener_model.py @@ -23,7 +23,6 @@ ) DPEnergyModel_ = make_model(DPEnergyAtomicModel) -DPEnergyModel_ = make_hessian_model(DPEnergyModel_) @BaseModel.register("ener") @@ -40,6 +39,8 @@ def __init__( self._hessian_enabled = False def enable_hessian(self): + self.__class__ = make_hessian_model(type(self)) + self.hess_fitting_def = super(type(self), self).atomic_output_def() self.requires_hessian("energy") self._hessian_enabled = True From 6a31b02081e366dac56e64ea91048914b8a6925b Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Sun, 15 Dec 2024 21:49:45 +0800 Subject: [PATCH 186/189] Update test_change_bias.py Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- source/tests/pt/test_change_bias.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/source/tests/pt/test_change_bias.py b/source/tests/pt/test_change_bias.py index a3cf3edbbc..58fd953656 100644 --- a/source/tests/pt/test_change_bias.py +++ b/source/tests/pt/test_change_bias.py @@ -87,6 +87,7 @@ def setUp(self) -> None: self.model_path_user_bias = Path(current_path) / ( model_name + "user_bias" + ".pt" ) + self.loss_params = self.config["loss"] def test_change_bias_with_data(self) -> None: run_dp( @@ -96,7 +97,10 @@ def test_change_bias_with_data(self) -> None: str(self.model_path_data_bias), map_location=DEVICE, weights_only=True ) model_params = state_dict["model"]["_extra_state"]["model_params"] - model_for_wrapper = get_model_for_wrapper(model_params) + model_for_wrapper = get_model_for_wrapper( + model_params, + _loss_params=self.loss_params, + ) wrapper = ModelWrapper(model_for_wrapper) wrapper.load_state_dict(state_dict["model"]) updated_bias = wrapper.model["Default"].get_out_bias() @@ -119,7 +123,10 @@ def test_change_bias_with_data_sys_file(self) -> None: str(self.model_path_data_file_bias), map_location=DEVICE, weights_only=True ) model_params = state_dict["model"]["_extra_state"]["model_params"] - model_for_wrapper = get_model_for_wrapper(model_params) + model_for_wrapper = get_model_for_wrapper( + model_params, + _loss_params=self.loss_params, + ) wrapper = ModelWrapper(model_for_wrapper) wrapper.load_state_dict(state_dict["model"]) updated_bias = wrapper.model["Default"].get_out_bias() @@ -140,7 +147,10 @@ def test_change_bias_with_user_defined(self) -> None: str(self.model_path_user_bias), map_location=DEVICE, weights_only=True ) model_params = state_dict["model"]["_extra_state"]["model_params"] - model_for_wrapper = get_model_for_wrapper(model_params) + model_for_wrapper = get_model_for_wrapper( + model_params, + _loss_params=self.loss_params, + ) wrapper = ModelWrapper(model_for_wrapper) wrapper.load_state_dict(state_dict["model"]) updated_bias = wrapper.model["Default"].get_out_bias() From 5f07d2bf91d9aa43824209a160bd3ac69f43208f Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 25 Dec 2024 16:37:56 +0800 Subject: [PATCH 187/189] Update train-energy-hessian.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/train-energy-hessian.md | 122 ++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 5 deletions(-) diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md index 02b3c52052..fb4b72e9c3 100644 --- a/doc/model/train-energy-hessian.md +++ b/doc/model/train-energy-hessian.md @@ -4,10 +4,6 @@ **Supported backends**: PyTorch {{ pytorch_icon }} ::: -:::{warning} -The model trained with Hessian cannot be frozen. If freezing is forced, the model will be treated as a standard energy model, and the frozen model will no longer be able to output Hessian predictions. -::: - To train a model that takes Hessian matrices, i.e., the second order derivatives of energies w.r.t coordinates as input, you only need to prepare full Hessian matrices and modify the `loss` section to define the Hessian-specific settings, keeping other sections the same as the normal energy model's input script. ## Energy Hessian Loss @@ -32,7 +28,7 @@ The options {ref}`start_pref_e `, {ref}`limit_pref If one does not want to train with virial, then he/she may set the virial prefactors {ref}`start_pref_v ` and {ref}`limit_pref_v ` to 0. -## Hessian format in PyTorch +## Hessian Format in PyTorch In the PyTorch backend, Hessian matrices are listed in `hessian.npy` files, and the data format may contain the following files: @@ -52,3 +48,119 @@ This system contains `Nframes` frames with the same atom number `Natoms`, the to | hessian | Hessian matrices | hessian.npy | eV/Å^2 | Nframes \* (Natoms \* 3 \* Natoms \* 3) | Second-order derivatives of energies w.r.t coordinates. | Note that the `hessian.npy` should contain the **full** Hessian matrices with shape of `(3Natoms * 3Natoms)` for each frame, rather than the upper or lower triangular matrices with shape of `(3Natoms * (3Natoms + 1) / 2)` for each frame. + +## Train the Model + +There are two approaches to training a Hessian model. The first method involves training the model from scratch using the same command as in the `ener` mode within the PyTorch backend: + +::::{tab-set} + +:::{tab-item} PyTorch {{ pytorch_icon }} + +```bash +dp --pt train input.json +``` + +::: + +:::: + +The second approach is to train a Hessian model from a pretrained energy model, following the same command as the `finetune` strategy within the PyTorch backend: + +::::{tab-set} + +:::{tab-item} PyTorch {{ pytorch_icon }} + +```bash +dp --pt train input.json --finetune pretrained_energy.pt +``` + +::: + +:::: + +The detailed loss can be found in `lcurve.out`: + +``` +# step rmse_val rmse_trn rmse_e_val rmse_e_trn rmse_f_val rmse_f_trn rmse_h_val rmse_h_trn lr + 0 1.05e+02 2.28e+01 2.11e-01 1.59e+00 3.25e+00 3.37e-01 6.00e+00 6.37e+00 1.0e-03 + 200 1.86e+01 3.23e+01 9.24e-03 1.54e-01 2.51e-01 4.70e-01 5.31e+00 9.05e+00 1.0e-03 + 400 2.69e+01 2.98e+01 1.03e-01 1.07e-01 5.67e-01 4.17e-01 6.35e+00 8.47e+00 1.0e-03 + 600 2.00e+01 1.90e+01 7.23e-02 6.90e-03 3.35e-01 2.58e-01 5.37e+00 5.41e+00 1.0e-03 + 800 1.68e+01 1.48e+01 4.06e-02 2.27e-01 2.35e-01 1.98e-01 4.76e+00 4.24e+00 1.0e-03 + 1000 1.70e+01 1.81e+01 3.90e-01 1.66e-01 2.02e-01 1.99e-01 4.98e+00 5.37e+00 1.0e-03 +``` + +## Test the Model + +:::{warning} +A model trained with Hessian cannot be frozen. If freezing is enforced, the model will be treated as a standard energy model, and the frozen one will no longer be able to output Hessian predictions. +::: + +If one do freeze and test a Hessian model using the commands: + +::::{tab-set} + +:::{tab-item} PyTorch {{ pytorch_icon }} + +```bash + +dp --pt freeze -o frozen_model.pth + +dp --pt test -m frozen_model.pth -s test_system -d ${output_prefix} -a -n 1 +``` + +::: + +:::: + +If `dp test -d ${output_prefix} -a` is specified, the output files will be the same as those in the `ener` mode, i.e., + +``` +${output_prefix}.e.out ${output_prefix}.e_peratom.out ${output_prefix}.f.out +${output_prefix}.v.out ${output_prefix}.v_peratom.out +``` + +If one intends to use the trained model for Hessian predictions, then he/she is supposed to test the model directly without performing a freezing operation: + +::::{tab-set} + +:::{tab-item} PyTorch {{ pytorch_icon }} + +```bash + +dp --pt test -m model.pt -s test_system -d ${output_prefix} -a -n 1 +``` + +::: + +:::: + +If `dp test -d ${output_prefix} -a` is specified, the predicted Hessian for each frame are output in an additional file in the working directory: + +``` +${output_prefix}.h.out +``` + +For `*.h.out.*`, it contains matrix with shape of `(2, n_hess)`: + +``` +# frame - 0: data_h pred_h (3Na*3Na matrix in row-major order) +5.897392891323943331e+01 2.909700516268236825e+01 +-7.682282297964052376e+00 2.535680817045881774e+00 +-1.266442953072092514e+01 -2.127310638041492652e+01 +5.442541716174009031e-02 7.202825779190234756e-02 +5.198263170894957939e-05 -8.110080221576332349e-02 +7.443552765043950914e-02 -2.248597801730128215e-02 +1.029910175689553675e+00 1.938646932394622047e-03 +1.213862217511276764e+00 5.344132558814301825e-02 +-1.221943904909605250e+00 1.602557574981743893e-01 +``` + +The full Hessian matrices are stored in a flattened form in the row-major order. Here, `n_hess` is the total number of Hessian matrix elements across all frames, calculated as: + +```math +n_\text{hess} = \sum_{i} 3N_{\text{atom}, i}*3N_{\text{atom}, i} +``` + +where $N_{\text{atom}, i}$ represents the number of atoms in the $i^{\text{th}}$ frame. From d9602a11ce24afd186ec6a21c509f9383c43f7b6 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Wed, 25 Dec 2024 17:14:03 +0800 Subject: [PATCH 188/189] Update train-energy-hessian.md Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- doc/model/train-energy-hessian.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/model/train-energy-hessian.md b/doc/model/train-energy-hessian.md index fb4b72e9c3..d77e7f3e88 100644 --- a/doc/model/train-energy-hessian.md +++ b/doc/model/train-energy-hessian.md @@ -114,7 +114,7 @@ dp --pt test -m frozen_model.pth -s test_system -d ${output_prefix} -a -n 1 :::: -If `dp test -d ${output_prefix} -a` is specified, the output files will be the same as those in the `ener` mode, i.e., +If `dp --pt test -d ${output_prefix} -a` is specified, the output files will be the same as those in the `ener` mode, i.e., ``` ${output_prefix}.e.out ${output_prefix}.e_peratom.out ${output_prefix}.f.out @@ -136,7 +136,7 @@ dp --pt test -m model.pt -s test_system -d ${output_prefix} -a -n 1 :::: -If `dp test -d ${output_prefix} -a` is specified, the predicted Hessian for each frame are output in an additional file in the working directory: +If `dp --pt test -d ${output_prefix} -a` is specified, the predicted Hessian for each frame are output in an additional file in the working directory: ``` ${output_prefix}.h.out From 56ff530f4b2e5aa2a151c3574e62841b768ce517 Mon Sep 17 00:00:00 2001 From: Anchor Yu <91590308+1azyking@users.noreply.github.com> Date: Fri, 27 Dec 2024 10:46:08 +0800 Subject: [PATCH 189/189] Update input.json Signed-off-by: Anchor Yu <91590308+1azyking@users.noreply.github.com> --- examples/hessian/single_task/input.json | 33 +++++++++++++++++-------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/examples/hessian/single_task/input.json b/examples/hessian/single_task/input.json index e227cf342c..3e61deac52 100644 --- a/examples/hessian/single_task/input.json +++ b/examples/hessian/single_task/input.json @@ -11,8 +11,8 @@ "type": "dpa2", "repinit": { "tebd_dim": 8, - "rcut": 9.0, - "rcut_smth": 8.0, + "rcut": 6.0, + "rcut_smth": 0.5, "nsel": 120, "neuron": [ 25, @@ -20,13 +20,17 @@ 100 ], "axis_neuron": 12, - "activation_function": "tanh" + "activation_function": "tanh", + "three_body_sel": 48, + "three_body_rcut": 4.0, + "three_body_rcut_smth": 3.5, + "use_three_body": true }, "repformer": { "rcut": 4.0, "rcut_smth": 3.5, - "nsel": 40, - "nlayers": 12, + "nsel": 48, + "nlayers": 3, "g1_dim": 128, "g2_dim": 32, "attn2_hidden": 32, @@ -38,11 +42,18 @@ "update_g1_has_conv": true, "update_g1_has_grrg": true, "update_g1_has_drrd": true, - "update_g1_has_attn": true, - "update_g2_has_g1g1": true, - "update_g2_has_attn": true, - "attn2_has_gate": true + "update_g1_has_attn": false, + "update_g2_has_g1g1": false, + "update_g2_has_attn": false, + "update_style": "res_residual", + "update_residual": 0.01, + "update_residual_init": "norm", + "attn2_has_gate": true, + "use_sqrt_nnei": true, + "g1_out_conv": true, + "g1_out_mlp": true }, + "precision": "float64", "add_tebd_to_repinit_out": false }, "fitting_net": { @@ -52,6 +63,7 @@ 240 ], "resnet_dt": true, + "precision": "float64", "seed": 1, "_comment": " that's all" }, @@ -60,7 +72,7 @@ "learning_rate": { "type": "exp", "decay_steps": 5000, - "start_lr": 0.0002, + "start_lr": 0.001, "stop_lr": 3.51e-08, "_comment": "that's all" }, @@ -77,6 +89,7 @@ "_comment": " that's all" }, "training": { + "stat_file": "./hess.hdf5", "training_data": { "systems": [ "../data/H8C4N2O"