Repository navigation
feat(pt): add charge density prediction support #5999
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
Open
YuzhiLiu-ai
wants to merge
36
commits into
deepmodeling:master
Choose a base branch
from
YuzhiLiu-ai:density-for-pr
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+4,631
−18
Open
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
9ad31f7
feat(pt): add charge density prediction support
YuzhiLiu-ai ba7ce74
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 1172f9b
Merge branch 'master' into density-for-pr
YuzhiLiu-ai a2c294e
ci: rerun checks
YuzhiLiu-ai f6a514f
fix(infer): remove type-incoherent grid branch from DeepPot.eval
YuzhiLiu-ai 4ddc097
fix(pt): harden density model evaluation and inputs
YuzhiLiu-ai 83a753c
refactor(pt): derive density model from make_model and assert shuffle…
YuzhiLiu-ai e894ce8
ci: rerun checks
YuzhiLiu-ai d01f459
fix(pt): address review on density model interfaces, loss, data, and …
YuzhiLiu-ai 3aef44c
docs: add density fitting guide and align argcheck and example configs
YuzhiLiu-ai 9d98f65
fix(pt): address review on density signatures, statistics cache, and …
YuzhiLiu-ai dec47b2
Merge remote-tracking branch 'upstream/master' into density-for-pr
YuzhiLiu-ai aa566fb
fix(pt): address review round on density dispatch, loss, exclusions, …
YuzhiLiu-ai b75d383
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] ff7fc05
Merge branch 'master' into density-for-pr
YuzhiLiu-ai 6145ae1
fix(pt): close review round on density eval, statistics, and loader c…
YuzhiLiu-ai 284f62e
Merge remote-tracking branch 'density/density-for-pr' into density-fo…
YuzhiLiu-ai e23e953
test: avoid subclass __init__ bypass flagged by CodeQL in data test d…
YuzhiLiu-ai bbbcec8
test: avoid subclass __init__ bypass flagged by CodeQL in data test d…
YuzhiLiu-ai 4f83b59
fix(data): require exact ndof for 3D frame_major arrays
YuzhiLiu-ai 3e7c259
fix(pt): align density data layout, forward charge_spin, and loader c…
YuzhiLiu-ai 7a4c466
fix(data): guard frame-major extents across sets and align the densit…
YuzhiLiu-ai 99c2b81
Merge remote-tracking branch 'upstream/master' into density-for-pr
YuzhiLiu-ai 094a19f
fix(pt): address density model review on serialization, charge_spin, …
YuzhiLiu-ai 26c0eed
Merge remote-tracking branch 'upstream/master' into density-for-pr
YuzhiLiu-ai 5cf0090
Merge remote-tracking branch 'refs/remotes/upstream/master' into dens…
YuzhiLiu-ai 873b182
Merge remote-tracking branch 'upstream/master' into density-for-pr
e9fb537
fix(pt): address density model review feedback
7bf3e6d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 9449d6e
chore(docs): retrigger Read the Docs build
94e0a1b
test(pt): avoid ambiguous parent stat call
a03b05a
fix(pt): cover density training edge cases
c007ab4
fix(pt): honor default fparam in density tests
4c36ab1
fix(pt): resolve density CodeQL findings
5ec6aa0
fix(pt): accept density bias presets
d762590
test(pt): align density stat samples with device
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| # SPDX-License-Identifier: LGPL-3.0-or-later | ||
| from typing import ( | ||
| Any, | ||
| ) | ||
|
|
||
| import numpy as np | ||
|
|
||
| from deepmd.dpmodel.output_def import ( | ||
| FittingOutputDef, | ||
| ModelOutputDef, | ||
| OutputVariableDef, | ||
| ) | ||
|
|
||
| from .deep_eval import ( | ||
| DeepEval, | ||
| ) | ||
|
|
||
|
|
||
| class DeepDensity(DeepEval): | ||
| """Charge density evaluated on grid points. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| model_file : Path | ||
| The name of the frozen model file. | ||
| *args : list | ||
| Positional arguments. | ||
| auto_batch_size : bool or int or AutoBatchSize, default: True | ||
| If True, automatic batch size will be used. If int, it will be used | ||
| as the initial batch size. | ||
| neighbor_list : ase.neighborlist.NewPrimitiveNeighborList, optional | ||
| The ASE neighbor list class to produce the neighbor list. If None, the | ||
| neighbor list will be built natively in the model. | ||
| **kwargs : dict | ||
| Keyword arguments. | ||
| """ | ||
|
|
||
| @property | ||
| def output_def(self) -> ModelOutputDef: | ||
| """Get the output definition of this model. | ||
|
|
||
| The output definition is identical to that of the density | ||
| fitting net of the model: the density is predicted on grid | ||
| points, and is neither reducible nor differentiable. | ||
| """ | ||
| return ModelOutputDef( | ||
| FittingOutputDef( | ||
| [ | ||
| OutputVariableDef( | ||
| "density", | ||
| [1], | ||
| reducible=False, | ||
| r_differentiable=False, | ||
| c_differentiable=False, | ||
| ), | ||
| ] | ||
| ) | ||
| ) | ||
|
|
||
| def eval( | ||
| self, | ||
| coords: np.ndarray, | ||
| cells: np.ndarray | None, | ||
| atom_types: list[int] | np.ndarray, | ||
| grid: np.ndarray, | ||
| fparam: np.ndarray | None = None, | ||
| aparam: np.ndarray | None = None, | ||
| mixed_type: bool = False, | ||
| grid_batch_size: int | None = 4096, | ||
| **kwargs: Any, | ||
| ) -> np.ndarray: | ||
| """Evaluate the density on grid points. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| coords : np.ndarray | ||
| The coordinates of the atoms, in shape (nframes, natoms, 3). | ||
| cells : np.ndarray | ||
| The cell vectors of the system, in shape (nframes, 9). If the system | ||
| is not periodic, set it to None. | ||
| atom_types : list[int] or np.ndarray | ||
| The types of the atoms. If mixed_type is False, the shape is (natoms,); | ||
| otherwise, the shape is (nframes, natoms). | ||
| grid : np.ndarray | ||
| The coordinates of the grid points, in shape (nframes, ngrid, 3). | ||
| fparam : np.ndarray, optional | ||
| The frame parameters, by default None. | ||
| aparam : np.ndarray, optional | ||
| The atomic parameters, by default None. | ||
| mixed_type : bool, optional | ||
| Whether the atom_types is mixed type, by default False. | ||
| grid_batch_size : int or None, optional | ||
| Maximum number of grid points evaluated in one model call. Grid | ||
| chunks are concatenated in their original order. The default is | ||
| 4096; set to None to evaluate the full grid in one call. | ||
| **kwargs : dict[str, Any] | ||
| Keyword arguments. ``charge_spin`` (nframes, 2) is forwarded to | ||
| models with charge spin embedding; when omitted, such models use | ||
| their ``default_chg_spin`` if set. | ||
|
|
||
| Returns | ||
| ------- | ||
| density | ||
| The density on the grid points, in shape (nframes, ngrid). | ||
| """ | ||
| ( | ||
| coords, | ||
| cells, | ||
| atom_types, | ||
| fparam, | ||
| aparam, | ||
| nframes, | ||
| _, | ||
| ) = self._standard_input(coords, cells, atom_types, fparam, aparam, mixed_type) | ||
| grid = self._standard_grid(grid, nframes) | ||
| if grid_batch_size is not None and grid_batch_size <= 0: | ||
| raise ValueError("grid_batch_size must be positive or None") | ||
| if grid_batch_size is None or grid.shape[1] <= grid_batch_size: | ||
| grid_slices = (grid,) | ||
| else: | ||
| grid_slices = tuple( | ||
| grid[:, begin : begin + grid_batch_size] | ||
| for begin in range(0, grid.shape[1], grid_batch_size) | ||
| ) | ||
| density_chunks = [] | ||
| for grid_chunk in grid_slices: | ||
| results = self.deep_eval.eval( | ||
| coords, | ||
| cells, | ||
| atom_types, | ||
| False, | ||
| fparam=fparam, | ||
| aparam=aparam, | ||
| grid=grid_chunk, | ||
| **kwargs, | ||
| ) | ||
| density_chunks.append(results["density"].reshape(nframes, -1)) | ||
| return np.concatenate(density_chunks, axis=1) | ||
|
|
||
| @staticmethod | ||
| def _standard_grid(grid: np.ndarray, nframes: int) -> np.ndarray: | ||
| """Normalise the grid coordinates to ``(nframes, ngrid, 3)``. | ||
|
|
||
| The auto batcher slices every argument with ``ndim > 1`` along axis | ||
| 0, so a natural single-frame ``(ngrid, 3)`` input would silently be | ||
| truncated to one grid point; carry the frame dimension explicitly. | ||
| """ | ||
| arr = np.asarray(grid) | ||
| if arr.ndim == 2: | ||
|
YuzhiLiu-ai marked this conversation as resolved.
|
||
| if nframes != 1: | ||
| raise ValueError( | ||
| f"grid of shape {arr.shape} is ambiguous for {nframes} " | ||
|
iProzd marked this conversation as resolved.
|
||
| "frames; pass grid with shape (nframes, ngrid, 3)" | ||
| ) | ||
| arr = arr[None, ...] | ||
| if arr.ndim != 3 or arr.shape[-1] != 3: | ||
| raise ValueError( | ||
| f"grid must have shape (nframes, ngrid, 3), got {arr.shape}" | ||
| ) | ||
| if arr.shape[0] != nframes: | ||
| raise ValueError(f"grid has {arr.shape[0]} frames but coord has {nframes}") | ||
| if arr.shape[1] == 0: | ||
| raise ValueError("grid must contain at least one point per frame") | ||
| return arr | ||
|
|
||
|
|
||
| __all__ = ["DeepDensity"] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| # SPDX-License-Identifier: LGPL-3.0-or-later | ||
| """Testing of models predicting charge density on grid points.""" | ||
|
|
||
| from deepmd.infer.model_test.base import ( | ||
| ChunkContext, | ||
| ModelTester, | ||
| _write_per_frame_details, | ||
| ) | ||
| from deepmd.utils.data import ( | ||
| DeepmdData, | ||
| ) | ||
| from deepmd.utils.eval_metrics import ( | ||
| mae, | ||
| rmse, | ||
| ) | ||
|
|
||
| __all__ = ["DensityTester"] | ||
|
|
||
|
|
||
| class DensityTester(ModelTester): | ||
| """Test a model of charge density on grid points.""" | ||
|
|
||
| report = ( | ||
| ("mae_density", "DENSITY MAE : {} units"), | ||
| ("rmse_density", "DENSITY RMSE : {} units"), | ||
| ) | ||
|
|
||
| def add_data_requirements(self, data: DeepmdData) -> None: | ||
| """Declare the labels a density test consumes.""" | ||
| dp = self.dp | ||
| # The grid and the density are defined on grid points rather than on | ||
| # atoms, and their extent (ngrid) is not known until the data is | ||
| # loaded. They are declared frame-major so the loader keeps the | ||
| # (nframes, ngrid, ndof) layout without reshaping to natoms. | ||
| data.add( | ||
| "grid", | ||
| 3, | ||
| atomic=False, | ||
| must=True, | ||
| high_prec=True, | ||
| special_shape="frame_major", | ||
| ) | ||
| data.add( | ||
| "density", | ||
| 1, | ||
| atomic=False, | ||
| must=True, | ||
| high_prec=True, | ||
| special_shape="frame_major", | ||
| ) | ||
| if dp.get_dim_fparam() > 0: | ||
|
iProzd marked this conversation as resolved.
|
||
| data.add( | ||
| "fparam", | ||
| dp.get_dim_fparam(), | ||
| atomic=False, | ||
| must=not dp.has_default_fparam(), | ||
| high_prec=False, | ||
| ) | ||
| if dp.get_dim_aparam() > 0: | ||
| data.add( | ||
| "aparam", dp.get_dim_aparam(), atomic=True, must=True, high_prec=False | ||
| ) | ||
| if dp.has_chg_spin_ebd(): | ||
| data.add( | ||
| "charge_spin", | ||
| 2, | ||
| atomic=False, | ||
| must=not dp.has_default_chg_spin(), | ||
| high_prec=False, | ||
| ) | ||
|
|
||
| def evaluate_chunk( | ||
|
YuzhiLiu-ai marked this conversation as resolved.
|
||
| self, | ||
| data: DeepmdData, | ||
| test_data: dict, | ||
| context: ChunkContext, | ||
| ) -> dict[str, tuple[float, float]]: | ||
| """Evaluate one chunk of a density test.""" | ||
| dp = self.dp | ||
| mixed_type = data.mixed_type | ||
| nframes = test_data["box"].shape[0] | ||
|
|
||
| coord = test_data["coord"].reshape([nframes, -1]) | ||
| box = test_data["box"] if data.pbc else None | ||
|
YuzhiLiu-ai marked this conversation as resolved.
|
||
| if mixed_type: | ||
| atype = test_data["type"].reshape([nframes, -1]) | ||
| else: | ||
| atype = test_data["type"][0] | ||
| fparam = ( | ||
| test_data["fparam"] | ||
| if dp.get_dim_fparam() > 0 and test_data["find_fparam"] != 0.0 | ||
| else None | ||
| ) | ||
| aparam = test_data["aparam"] if dp.get_dim_aparam() > 0 else None | ||
| charge_spin = ( | ||
| test_data["charge_spin"] | ||
| if dp.has_chg_spin_ebd() and test_data.get("find_charge_spin", 0.0) != 0.0 | ||
| else None | ||
| ) | ||
| # the loader accepts both (nframes, ngrid, 3) and the flattened | ||
| # (nframes, ngrid*3) layout; DeepDensity.eval only accepts the | ||
| # former, so normalize here and keep one contract at the eval call | ||
| grid = test_data["grid"].reshape(nframes, -1, 3) | ||
|
|
||
| prediction = dp.eval( | ||
| coord, | ||
| box, | ||
| atype, | ||
| grid, | ||
| fparam=fparam, | ||
| aparam=aparam, | ||
| charge_spin=charge_spin, | ||
| mixed_type=mixed_type, | ||
| ).reshape(nframes, -1) | ||
| label = test_data["density"].reshape(nframes, -1) | ||
|
|
||
| diff = prediction - label | ||
| errors: dict[str, tuple[float, float]] = { | ||
| "mae_density": (mae(diff), diff.size), | ||
| "rmse_density": (rmse(diff), diff.size), | ||
| } | ||
|
|
||
| if context.detail_path is not None: | ||
|
YuzhiLiu-ai marked this conversation as resolved.
|
||
| _write_per_frame_details( | ||
| context, | ||
| suffix="density", | ||
| reference=label, | ||
| prediction=prediction, | ||
| ) | ||
|
|
||
| return errors | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.