-
Notifications
You must be signed in to change notification settings - Fork 608
feat: Add eval-desc CLI command for descriptor evaluation with 3D output format #4903
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
91f6553
7fa7058
465b54e
592c6f9
9b9cd3a
4ac8fd6
0a85bf5
f92aa8a
006b2e7
4441769
b85e483
f5e1c70
b1ad7b1
0469a5b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| # SPDX-License-Identifier: LGPL-3.0-or-later | ||
| """Evaluate descriptors using trained DeePMD model.""" | ||
|
|
||
| import logging | ||
| import os | ||
| from pathlib import ( | ||
| Path, | ||
| ) | ||
| from typing import ( | ||
| Optional, | ||
| ) | ||
|
|
||
| import numpy as np | ||
|
|
||
| from deepmd.common import ( | ||
| expand_sys_str, | ||
| ) | ||
| from deepmd.infer.deep_eval import ( | ||
| DeepEval, | ||
| ) | ||
| from deepmd.utils.data import ( | ||
| DeepmdData, | ||
| ) | ||
|
|
||
| __all__ = ["eval_desc"] | ||
|
|
||
| log = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def eval_desc( | ||
| *, | ||
| model: str, | ||
| system: str, | ||
| datafile: str, | ||
| output: str = "desc", | ||
| head: Optional[str] = None, | ||
| **kwargs, | ||
| ) -> None: | ||
| """Evaluate descriptors for given systems. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| model : str | ||
| path where model is stored | ||
| system : str | ||
| system directory | ||
| datafile : str | ||
| the path to the list of systems to process | ||
| output : str | ||
| output directory for descriptor files | ||
| head : Optional[str], optional | ||
| (Supported backend: PyTorch) Task head if in multi-task mode. | ||
| **kwargs | ||
| additional arguments | ||
|
|
||
| Notes | ||
| ----- | ||
| Descriptors are saved as 3D numpy arrays with shape (nframes, natoms, ndesc) | ||
| where each frame contains the descriptors for all atoms. | ||
|
|
||
| Raises | ||
| ------ | ||
| RuntimeError | ||
| if no valid system was found | ||
| """ | ||
| if datafile is not None: | ||
| with open(datafile) as datalist: | ||
| all_sys = datalist.read().splitlines() | ||
| else: | ||
| all_sys = expand_sys_str(system) | ||
|
|
||
| if len(all_sys) == 0: | ||
| raise RuntimeError("Did not find valid system") | ||
|
|
||
| # init model | ||
| dp = DeepEval(model, head=head) | ||
|
|
||
| # create output directory | ||
| output_dir = Path(output) | ||
| output_dir.mkdir(parents=True, exist_ok=True) | ||
|
|
||
| for cc, system_path in enumerate(all_sys): | ||
| log.info("# -------output of dp eval_desc------- ") | ||
| log.info(f"# processing system : {system_path}") | ||
|
|
||
| # create data class | ||
| tmap = dp.get_type_map() | ||
| data = DeepmdData( | ||
| system_path, | ||
| set_prefix="set", | ||
| shuffle_test=False, | ||
| type_map=tmap, | ||
| sort_atoms=False, | ||
| ) | ||
|
|
||
| # get test data | ||
| test_data = data.get_test() | ||
| mixed_type = data.mixed_type | ||
| natoms = len(test_data["type"][0]) | ||
| nframes = test_data["box"].shape[0] | ||
|
|
||
| # prepare input data | ||
| coord = test_data["coord"].reshape([nframes, -1]) | ||
| box = test_data["box"] | ||
| if not data.pbc: | ||
| box = None | ||
| if mixed_type: | ||
| atype = test_data["type"].reshape([nframes, -1]) | ||
| else: | ||
| atype = test_data["type"][0] | ||
|
|
||
| # handle optional parameters | ||
| fparam = None | ||
| if dp.get_dim_fparam() > 0: | ||
| if "fparam" in test_data: | ||
| fparam = test_data["fparam"] | ||
|
|
||
| aparam = None | ||
| if dp.get_dim_aparam() > 0: | ||
| if "aparam" in test_data: | ||
| aparam = test_data["aparam"] | ||
|
|
||
| # evaluate descriptors | ||
| log.info(f"# evaluating descriptors for {nframes} frames") | ||
| descriptors = dp.eval_descriptor( | ||
| coord, | ||
| box, | ||
| atype, | ||
| fparam=fparam, | ||
| aparam=aparam, | ||
| ) | ||
|
|
||
| # descriptors are kept in 3D format (nframes, natoms, ndesc) | ||
|
|
||
| # save descriptors | ||
| system_name = os.path.basename(system_path.rstrip("/")) | ||
| desc_file = output_dir / f"{system_name}.npy" | ||
| np.save(desc_file, descriptors) | ||
|
|
||
| log.info(f"# descriptors saved to {desc_file}") | ||
| log.info(f"# descriptor shape: {descriptors.shape}") | ||
| log.info("# ----------------------------------- ") | ||
|
|
||
| log.info("# eval_desc completed successfully") | ||
| Original file line number | Diff line number | Diff line change | |||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,101 @@ | |||||||||||||||||||||||||||||
| # SPDX-License-Identifier: LGPL-3.0-or-later | |||||||||||||||||||||||||||||
| import json | |||||||||||||||||||||||||||||
| import os | |||||||||||||||||||||||||||||
| import shutil | |||||||||||||||||||||||||||||
| import tempfile | |||||||||||||||||||||||||||||
| import unittest | |||||||||||||||||||||||||||||
| from copy import ( | |||||||||||||||||||||||||||||
| deepcopy, | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
| from pathlib import ( | |||||||||||||||||||||||||||||
| Path, | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| import numpy as np | |||||||||||||||||||||||||||||
| import torch | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| from deepmd.entrypoints.eval_desc import ( | |||||||||||||||||||||||||||||
| eval_desc, | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
| from deepmd.pt.entrypoints.main import ( | |||||||||||||||||||||||||||||
| get_trainer, | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| from .model.test_permutation import ( | |||||||||||||||||||||||||||||
| model_se_e2_a, | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| class DPEvalDesc: | |||||||||||||||||||||||||||||
| def test_dp_eval_desc_1_frame(self) -> None: | |||||||||||||||||||||||||||||
| trainer = get_trainer(deepcopy(self.config)) | |||||||||||||||||||||||||||||
| with torch.device("cpu"): | |||||||||||||||||||||||||||||
| input_dict, label_dict, _ = trainer.get_data(is_train=False) | |||||||||||||||||||||||||||||
| has_spin = getattr(trainer.model, "has_spin", False) | |||||||||||||||||||||||||||||
| if callable(has_spin): | |||||||||||||||||||||||||||||
| has_spin = has_spin() | |||||||||||||||||||||||||||||
| if not has_spin: | |||||||||||||||||||||||||||||
| input_dict.pop("spin", None) | |||||||||||||||||||||||||||||
| input_dict["do_atomic_virial"] = True | |||||||||||||||||||||||||||||
| result = trainer.model(**input_dict) | |||||||||||||||||||||||||||||
Check noticeCode scanning / CodeQL Unused local variable Note test
Variable result is not used.
Copilot AutofixAI 8 months ago To fix the problem, we should remove the assignment to the unused variable
Suggested changeset
1
source/tests/pt/test_eval_desc.py
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
|
|||||||||||||||||||||||||||||
| model = torch.jit.script(trainer.model) | |||||||||||||||||||||||||||||
| tmp_model = tempfile.NamedTemporaryFile(delete=False, suffix=".pth") | |||||||||||||||||||||||||||||
| torch.jit.save(model, tmp_model.name) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| # Test eval_desc | |||||||||||||||||||||||||||||
| eval_desc( | |||||||||||||||||||||||||||||
| model=tmp_model.name, | |||||||||||||||||||||||||||||
| system=self.config["training"]["validation_data"]["systems"][0], | |||||||||||||||||||||||||||||
| datafile=None, | |||||||||||||||||||||||||||||
| output=self.output_dir, | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
| os.unlink(tmp_model.name) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| # Check that descriptor file was created | |||||||||||||||||||||||||||||
| system_name = os.path.basename( | |||||||||||||||||||||||||||||
| self.config["training"]["validation_data"]["systems"][0].rstrip("/") | |||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||
| desc_file = os.path.join(self.output_dir, f"{system_name}.npy") | |||||||||||||||||||||||||||||
| self.assertTrue(os.path.exists(desc_file)) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| # Load and validate descriptor | |||||||||||||||||||||||||||||
| descriptors = np.load(desc_file) | |||||||||||||||||||||||||||||
| self.assertIsInstance(descriptors, np.ndarray) | |||||||||||||||||||||||||||||
| # Descriptors should be 3D: (nframes, natoms, ndesc) | |||||||||||||||||||||||||||||
| self.assertEqual(len(descriptors.shape), 3) # Should be 3D array | |||||||||||||||||||||||||||||
| self.assertGreater(descriptors.shape[0], 0) # Should have frames | |||||||||||||||||||||||||||||
| self.assertGreater(descriptors.shape[1], 0) # Should have atoms | |||||||||||||||||||||||||||||
| self.assertGreater(descriptors.shape[2], 0) # Should have descriptor dimensions | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| def tearDown(self) -> None: | |||||||||||||||||||||||||||||
| for f in os.listdir("."): | |||||||||||||||||||||||||||||
| if f.startswith("model") and f.endswith(".pt"): | |||||||||||||||||||||||||||||
| os.remove(f) | |||||||||||||||||||||||||||||
| if f in ["lcurve.out", self.input_json]: | |||||||||||||||||||||||||||||
| os.remove(f) | |||||||||||||||||||||||||||||
| if f in ["stat_files"]: | |||||||||||||||||||||||||||||
| shutil.rmtree(f) | |||||||||||||||||||||||||||||
| # Clean up output directory | |||||||||||||||||||||||||||||
| if hasattr(self, "output_dir") and os.path.exists(self.output_dir): | |||||||||||||||||||||||||||||
| shutil.rmtree(self.output_dir) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| class TestDPEvalDescSeA(DPEvalDesc, unittest.TestCase): | |||||||||||||||||||||||||||||
| def setUp(self) -> None: | |||||||||||||||||||||||||||||
| self.output_dir = "test_eval_desc_output" | |||||||||||||||||||||||||||||
| input_json = str(Path(__file__).parent / "water" / "se_atten.json") | |||||||||||||||||||||||||||||
| with open(input_json) as f: | |||||||||||||||||||||||||||||
| self.config = json.load(f) | |||||||||||||||||||||||||||||
| self.config["training"]["numb_steps"] = 1 | |||||||||||||||||||||||||||||
| self.config["training"]["save_freq"] = 1 | |||||||||||||||||||||||||||||
| data_file = [str(Path(__file__).parent / "water" / "data" / "single")] | |||||||||||||||||||||||||||||
| self.config["training"]["training_data"]["systems"] = data_file | |||||||||||||||||||||||||||||
| self.config["training"]["validation_data"]["systems"] = data_file | |||||||||||||||||||||||||||||
| self.config["model"] = deepcopy(model_se_e2_a) | |||||||||||||||||||||||||||||
| self.input_json = "test_eval_desc.json" | |||||||||||||||||||||||||||||
| with open(self.input_json, "w") as fp: | |||||||||||||||||||||||||||||
| json.dump(self.config, fp, indent=4) | |||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||
| if __name__ == "__main__": | |||||||||||||||||||||||||||||
| unittest.main() | |||||||||||||||||||||||||||||
Check notice
Code scanning / CodeQL
Unused local variable Note
Copilot Autofix
AI 8 months ago
To fix the issue, the line assigning to
natomsshould be deleted, since the value is not used. Removing this line will clean up the code without affecting any downstream logic, as all necessary information comes fromtest_dataand related computations. Ensure that no references tonatomsremain, and that deleting this line does not affect any required initialization or program side effects.