Download model/nn/norm.py from OneScience-Group/NequIP: direct link, hf CLI and curl.
- Browser
- Download file 2.93 kB
-
https://huggingface.co/OneScience-Group/NequIP/resolve/main/model/nn/norm.py
- Command line
-
hf download hf://OneScience-Group/NequIP/model/nn/norm.py
-
curl -L -o norm.py https://huggingface.co/OneScience-Group/NequIP/resolve/main/model/nn/norm.py
2.93 kB
| import torch | |
| from typing import Union, Sequence, Dict | |
| from math import sqrt | |
| from onescience.datapipes.materials.nequip import AtomicDataDict | |
| class AvgNumNeighborsNorm(torch.nn.Module): | |
| def __init__( | |
| self, | |
| type_names: Sequence[str], | |
| avg_num_neighbors: Union[float, Dict[str, float]], | |
| ) -> None: | |
| """ | |
| Module to normalize features during training using per type edge sum normalization. | |
| Args: | |
| type_names (Sequence[str]): list of atom type names | |
| avg_num_neighbors (float/Dict[str, float]): used to normalize edge sums for better numerics | |
| """ | |
| super().__init__() | |
| assert avg_num_neighbors is not None, "avg_num_neighbors must be specified" | |
| self.in_field = self.out_field = AtomicDataDict.NODE_FEATURES_KEY | |
| self.norm_key = AtomicDataDict.FEATURE_NORM_FACTOR_KEY | |
| # Put avg_num_neighbors in a list (global or per type) | |
| if isinstance(avg_num_neighbors, (float, int)): | |
| avg_num_neighbors = [avg_num_neighbors] | |
| elif isinstance(avg_num_neighbors, dict): | |
| assert set(type_names) == set(avg_num_neighbors.keys()) | |
| avg_num_neighbors = [avg_num_neighbors[k] for k in type_names] | |
| else: | |
| raise RuntimeError( | |
| "Unrecognized format for `avg_num_neighbors`, only floats or dicts allowed." | |
| ) | |
| assert isinstance(avg_num_neighbors, list) | |
| # Tensorize avg_num_neighbors and register as buffer | |
| norm_const = torch.tensor([(1.0 / sqrt(N)) for N in avg_num_neighbors]) | |
| norm_const = norm_const.reshape(-1, 1) | |
| # Persistent=False to ensure backwards compatibility of FMs. | |
| # TODO remove this once we're sure FMs are not using this anymore | |
| self.register_buffer("norm_const", norm_const, persistent=False) | |
| # If global avg_num_neighbors or only one type, no need to do embedding lookup in forward | |
| self.norm_shortcut = self.norm_const.numel() == 1 | |
| def forward(self, data: AtomicDataDict.Type) -> AtomicDataDict.Type: | |
| features = data[self.in_field] | |
| norm_size = features.size(0) | |
| if self.norm_key in data and data[self.norm_key].size(0) == norm_size: | |
| norm_factor = data[self.norm_key] | |
| else: | |
| # Compute norm factor for the first time | |
| if self.norm_shortcut: | |
| # No need to do embedding lookup in forward | |
| norm_factor = self.norm_const.expand(norm_size, -1) | |
| else: | |
| # Embed each avg_num_neighbors value per type | |
| norm_factor = torch.nn.functional.embedding( | |
| data[AtomicDataDict.ATOM_TYPE_KEY][:norm_size], | |
| self.norm_const, | |
| ) | |
| data[self.norm_key] = norm_factor # shape: (num_local_nodes, 1) | |
| data[self.out_field] = norm_factor * features | |
| return data | |