Download UniPath/remote/DiffCSP-official/diffcsp/pl_modules/cspnet.py from BAAI/AIDD: direct link, hf CLI and curl.
- Browser
- Download file 10.8 kB
-
https://huggingface.co/BAAI/AIDD/resolve/main/UniPath/remote/DiffCSP-official/diffcsp/pl_modules/cspnet.py
- Command line
-
hf download hf://BAAI/AIDD/UniPath/remote/DiffCSP-official/diffcsp/pl_modules/cspnet.py
-
curl -L -o cspnet.py https://huggingface.co/BAAI/AIDD/resolve/main/UniPath/remote/DiffCSP-official/diffcsp/pl_modules/cspnet.py
10.8 kB
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| import math | |
| from torch_scatter import scatter | |
| from torch_scatter.composite import scatter_softmax | |
| from torch_geometric.utils import to_dense_adj, dense_to_sparse | |
| from einops import rearrange, repeat | |
| from diffcsp.common.data_utils import lattice_params_to_matrix_torch, get_pbc_distances, radius_graph_pbc, frac_to_cart_coords, repeat_blocks | |
| MAX_ATOMIC_NUM=100 | |
| class SinusoidsEmbedding(nn.Module): | |
| def __init__(self, n_frequencies = 10, n_space = 3): | |
| super().__init__() | |
| self.n_frequencies = n_frequencies | |
| self.n_space = n_space | |
| self.frequencies = 2 * math.pi * torch.arange(self.n_frequencies) | |
| self.dim = self.n_frequencies * 2 * self.n_space | |
| def forward(self, x): | |
| emb = x.unsqueeze(-1) * self.frequencies[None, None, :].to(x.device) | |
| emb = emb.reshape(-1, self.n_frequencies * self.n_space) | |
| emb = torch.cat((emb.sin(), emb.cos()), dim=-1) | |
| return emb.detach() | |
| class CSPLayer(nn.Module): | |
| """ Message passing layer for cspnet.""" | |
| def __init__( | |
| self, | |
| hidden_dim=128, | |
| act_fn=nn.SiLU(), | |
| dis_emb=None, | |
| ln=False, | |
| ip=True | |
| ): | |
| super(CSPLayer, self).__init__() | |
| self.dis_dim = 3 | |
| self.dis_emb = dis_emb | |
| self.ip = True | |
| if dis_emb is not None: | |
| self.dis_dim = dis_emb.dim | |
| self.edge_mlp = nn.Sequential( | |
| nn.Linear(hidden_dim * 2 + 9 + self.dis_dim, hidden_dim), | |
| act_fn, | |
| nn.Linear(hidden_dim, hidden_dim), | |
| act_fn) | |
| self.node_mlp = nn.Sequential( | |
| nn.Linear(hidden_dim * 2, hidden_dim), | |
| act_fn, | |
| nn.Linear(hidden_dim, hidden_dim), | |
| act_fn) | |
| self.ln = ln | |
| if self.ln: | |
| self.layer_norm = nn.LayerNorm(hidden_dim) | |
| def edge_model(self, node_features, frac_coords, lattices, edge_index, edge2graph, frac_diff = None): | |
| hi, hj = node_features[edge_index[0]], node_features[edge_index[1]] | |
| if frac_diff is None: | |
| xi, xj = frac_coords[edge_index[0]], frac_coords[edge_index[1]] | |
| frac_diff = (xj - xi) % 1. | |
| if self.dis_emb is not None: | |
| frac_diff = self.dis_emb(frac_diff) | |
| if self.ip: | |
| lattice_ips = lattices @ lattices.transpose(-1,-2) | |
| else: | |
| lattice_ips = lattices | |
| lattice_ips_flatten = lattice_ips.view(-1, 9) | |
| lattice_ips_flatten_edges = lattice_ips_flatten[edge2graph] | |
| edges_input = torch.cat([hi, hj, lattice_ips_flatten_edges, frac_diff], dim=1) | |
| edge_features = self.edge_mlp(edges_input) | |
| return edge_features | |
| def node_model(self, node_features, edge_features, edge_index): | |
| agg = scatter(edge_features, edge_index[0], dim = 0, reduce='mean', dim_size=node_features.shape[0]) | |
| agg = torch.cat([node_features, agg], dim = 1) | |
| out = self.node_mlp(agg) | |
| return out | |
| def forward(self, node_features, frac_coords, lattices, edge_index, edge2graph, frac_diff = None): | |
| node_input = node_features | |
| if self.ln: | |
| node_features = self.layer_norm(node_input) | |
| edge_features = self.edge_model(node_features, frac_coords, lattices, edge_index, edge2graph, frac_diff) | |
| node_output = self.node_model(node_features, edge_features, edge_index) | |
| return node_input + node_output | |
| class CSPNet(nn.Module): | |
| def __init__( | |
| self, | |
| hidden_dim = 128, | |
| latent_dim = 256, | |
| num_layers = 4, | |
| max_atoms = 100, | |
| act_fn = 'silu', | |
| dis_emb = 'sin', | |
| num_freqs = 10, | |
| edge_style = 'fc', | |
| cutoff = 6.0, | |
| max_neighbors = 20, | |
| ln = False, | |
| ip = True, | |
| smooth = False, | |
| pred_type = False | |
| ): | |
| super(CSPNet, self).__init__() | |
| self.ip = ip | |
| self.smooth = smooth | |
| if self.smooth: | |
| self.node_embedding = nn.Linear(max_atoms, hidden_dim) | |
| else: | |
| self.node_embedding = nn.Embedding(max_atoms, hidden_dim) | |
| self.atom_latent_emb = nn.Linear(hidden_dim + latent_dim, hidden_dim) | |
| if act_fn == 'silu': | |
| self.act_fn = nn.SiLU() | |
| if dis_emb == 'sin': | |
| self.dis_emb = SinusoidsEmbedding(n_frequencies = num_freqs) | |
| elif dis_emb == 'none': | |
| self.dis_emb = None | |
| for i in range(0, num_layers): | |
| self.add_module( | |
| "csp_layer_%d" % i, CSPLayer(hidden_dim, self.act_fn, self.dis_emb, ln=ln, ip=ip) | |
| ) | |
| self.num_layers = num_layers | |
| self.coord_out = nn.Linear(hidden_dim, 3, bias = False) | |
| self.lattice_out = nn.Linear(hidden_dim, 9, bias = False) | |
| self.cutoff = cutoff | |
| self.max_neighbors = max_neighbors | |
| self.pred_type = pred_type | |
| self.ln = ln | |
| self.edge_style = edge_style | |
| if self.ln: | |
| self.final_layer_norm = nn.LayerNorm(hidden_dim) | |
| if self.pred_type: | |
| self.type_out = nn.Linear(hidden_dim, MAX_ATOMIC_NUM) | |
| def select_symmetric_edges(self, tensor, mask, reorder_idx, inverse_neg): | |
| # Mask out counter-edges | |
| tensor_directed = tensor[mask] | |
| # Concatenate counter-edges after normal edges | |
| sign = 1 - 2 * inverse_neg | |
| tensor_cat = torch.cat([tensor_directed, sign * tensor_directed]) | |
| # Reorder everything so the edges of every image are consecutive | |
| tensor_ordered = tensor_cat[reorder_idx] | |
| return tensor_ordered | |
| def reorder_symmetric_edges( | |
| self, edge_index, cell_offsets, neighbors, edge_vector | |
| ): | |
| """ | |
| Reorder edges to make finding counter-directional edges easier. | |
| Some edges are only present in one direction in the data, | |
| since every atom has a maximum number of neighbors. Since we only use i->j | |
| edges here, we lose some j->i edges and add others by | |
| making it symmetric. | |
| We could fix this by merging edge_index with its counter-edges, | |
| including the cell_offsets, and then running torch.unique. | |
| But this does not seem worth it. | |
| """ | |
| # Generate mask | |
| mask_sep_atoms = edge_index[0] < edge_index[1] | |
| # Distinguish edges between the same (periodic) atom by ordering the cells | |
| cell_earlier = ( | |
| (cell_offsets[:, 0] < 0) | |
| | ((cell_offsets[:, 0] == 0) & (cell_offsets[:, 1] < 0)) | |
| | ( | |
| (cell_offsets[:, 0] == 0) | |
| & (cell_offsets[:, 1] == 0) | |
| & (cell_offsets[:, 2] < 0) | |
| ) | |
| ) | |
| mask_same_atoms = edge_index[0] == edge_index[1] | |
| mask_same_atoms &= cell_earlier | |
| mask = mask_sep_atoms | mask_same_atoms | |
| # Mask out counter-edges | |
| edge_index_new = edge_index[mask[None, :].expand(2, -1)].view(2, -1) | |
| # Concatenate counter-edges after normal edges | |
| edge_index_cat = torch.cat( | |
| [ | |
| edge_index_new, | |
| torch.stack([edge_index_new[1], edge_index_new[0]], dim=0), | |
| ], | |
| dim=1, | |
| ) | |
| # Count remaining edges per image | |
| batch_edge = torch.repeat_interleave( | |
| torch.arange(neighbors.size(0), device=edge_index.device), | |
| neighbors, | |
| ) | |
| batch_edge = batch_edge[mask] | |
| neighbors_new = 2 * torch.bincount( | |
| batch_edge, minlength=neighbors.size(0) | |
| ) | |
| # Create indexing array | |
| edge_reorder_idx = repeat_blocks( | |
| neighbors_new // 2, | |
| repeats=2, | |
| continuous_indexing=True, | |
| repeat_inc=edge_index_new.size(1), | |
| ) | |
| # Reorder everything so the edges of every image are consecutive | |
| edge_index_new = edge_index_cat[:, edge_reorder_idx] | |
| cell_offsets_new = self.select_symmetric_edges( | |
| cell_offsets, mask, edge_reorder_idx, True | |
| ) | |
| edge_vector_new = self.select_symmetric_edges( | |
| edge_vector, mask, edge_reorder_idx, True | |
| ) | |
| return ( | |
| edge_index_new, | |
| cell_offsets_new, | |
| neighbors_new, | |
| edge_vector_new, | |
| ) | |
| def gen_edges(self, num_atoms, frac_coords, lattices, node2graph): | |
| if self.edge_style == 'fc': | |
| lis = [torch.ones(n,n, device=num_atoms.device) for n in num_atoms] | |
| fc_graph = torch.block_diag(*lis) | |
| fc_edges, _ = dense_to_sparse(fc_graph) | |
| return fc_edges, (frac_coords[fc_edges[1]] - frac_coords[fc_edges[0]]) % 1. | |
| elif self.edge_style == 'knn': | |
| lattice_nodes = lattices[node2graph] | |
| cart_coords = torch.einsum('bi,bij->bj', frac_coords, lattice_nodes) | |
| edge_index, to_jimages, num_bonds = radius_graph_pbc( | |
| cart_coords, None, None, num_atoms, self.cutoff, self.max_neighbors, | |
| device=num_atoms.device, lattices=lattices) | |
| j_index, i_index = edge_index | |
| distance_vectors = frac_coords[j_index] - frac_coords[i_index] | |
| distance_vectors += to_jimages.float() | |
| edge_index_new, _, _, edge_vector_new = self.reorder_symmetric_edges(edge_index, to_jimages, num_bonds, distance_vectors) | |
| return edge_index_new, -edge_vector_new | |
| def forward(self, t, atom_types, frac_coords, lattices, num_atoms, node2graph): | |
| edges, frac_diff = self.gen_edges(num_atoms, frac_coords, lattices, node2graph) | |
| edge2graph = node2graph[edges[0]] | |
| if self.smooth: | |
| node_features = self.node_embedding(atom_types) | |
| else: | |
| node_features = self.node_embedding(atom_types - 1) | |
| t_per_atom = t.repeat_interleave(num_atoms, dim=0) | |
| node_features = torch.cat([node_features, t_per_atom], dim=1) | |
| node_features = self.atom_latent_emb(node_features) | |
| for i in range(0, self.num_layers): | |
| node_features = self._modules["csp_layer_%d" % i](node_features, frac_coords, lattices, edges, edge2graph, frac_diff = frac_diff) | |
| if self.ln: | |
| node_features = self.final_layer_norm(node_features) | |
| coord_out = self.coord_out(node_features) | |
| graph_features = scatter(node_features, node2graph, dim = 0, reduce = 'mean') | |
| lattice_out = self.lattice_out(graph_features) | |
| lattice_out = lattice_out.view(-1, 3, 3) | |
| if self.ip: | |
| lattice_out = torch.einsum('bij,bjk->bik', lattice_out, lattices) | |
| if self.pred_type: | |
| type_out = self.type_out(node_features) | |
| return lattice_out, coord_out, type_out | |
| return lattice_out, coord_out | |