Download models/egnn.py from OneScience-Group/TargetDiff: direct link, hf CLI and curl.
- Browser
- Download file 5.58 kB
-
https://huggingface.co/OneScience-Group/TargetDiff/resolve/main/models/egnn.py
- Command line
-
hf download hf://OneScience-Group/TargetDiff/models/egnn.py
-
curl -L -o egnn.py https://huggingface.co/OneScience-Group/TargetDiff/resolve/main/models/egnn.py
5.58 kB
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from torch_scatter import scatter_sum | |
| from torch_geometric.nn import radius_graph, knn_graph | |
| from .common import GaussianSmearing, MLP, batch_hybrid_edge_connection, NONLINEARITIES | |
| class EnBaseLayer(nn.Module): | |
| def __init__(self, hidden_dim, edge_feat_dim, num_r_gaussian, update_x=True, act_fn='silu', norm=False): | |
| super().__init__() | |
| self.r_min = 0. | |
| self.r_max = 10. | |
| self.hidden_dim = hidden_dim | |
| self.num_r_gaussian = num_r_gaussian | |
| self.edge_feat_dim = edge_feat_dim | |
| self.update_x = update_x | |
| self.act_fn = act_fn | |
| self.norm = norm | |
| if num_r_gaussian > 1: | |
| self.distance_expansion = GaussianSmearing(self.r_min, self.r_max, num_gaussians=num_r_gaussian) | |
| self.edge_mlp = MLP(2 * hidden_dim + edge_feat_dim + num_r_gaussian, hidden_dim, hidden_dim, | |
| num_layer=2, norm=norm, act_fn=act_fn, act_last=True) | |
| self.edge_inf = nn.Sequential(nn.Linear(hidden_dim, 1), nn.Sigmoid()) | |
| if self.update_x: | |
| # self.x_mlp = MLP(hidden_dim, 1, hidden_dim, num_layer=2, norm=norm, act_fn=act_fn) | |
| x_mlp = [nn.Linear(hidden_dim, hidden_dim), NONLINEARITIES[act_fn]] | |
| layer = nn.Linear(hidden_dim, 1, bias=False) | |
| torch.nn.init.xavier_uniform_(layer.weight, gain=0.001) | |
| x_mlp.append(layer) | |
| x_mlp.append(nn.Tanh()) | |
| self.x_mlp = nn.Sequential(*x_mlp) | |
| self.node_mlp = MLP(2 * hidden_dim, hidden_dim, hidden_dim, num_layer=2, norm=norm, act_fn=act_fn) | |
| def forward(self, h, x, edge_index, mask_ligand, edge_attr=None): | |
| src, dst = edge_index | |
| hi, hj = h[dst], h[src] | |
| # \phi_e in Eq(3) | |
| rel_x = x[dst] - x[src] | |
| d_sq = torch.sum(rel_x ** 2, -1, keepdim=True) | |
| if self.num_r_gaussian > 1: | |
| d_feat = self.distance_expansion(torch.sqrt(d_sq + 1e-8)) | |
| else: | |
| d_feat = d_sq | |
| if edge_attr is not None: | |
| edge_feat = torch.cat([d_feat, edge_attr], -1) | |
| else: | |
| edge_feat = d_sq | |
| mij = self.edge_mlp(torch.cat([hi, hj, edge_feat], -1)) | |
| eij = self.edge_inf(mij) | |
| mi = scatter_sum(mij * eij, dst, dim=0, dim_size=h.shape[0]) | |
| # h update in Eq(6) | |
| h = h + self.node_mlp(torch.cat([mi, h], -1)) | |
| if self.update_x: | |
| # x update in Eq(4) | |
| xi, xj = x[dst], x[src] | |
| # (xi - xj) / (\|xi - xj\| + C) to make it more stable | |
| delta_x = scatter_sum((xi - xj) / (torch.sqrt(d_sq + 1e-8) + 1) * self.x_mlp(mij), dst, dim=0) | |
| x = x + delta_x * mask_ligand[:, None] # only ligand positions will be updated | |
| return h, x | |
| class EGNN(nn.Module): | |
| def __init__(self, num_layers, hidden_dim, edge_feat_dim, num_r_gaussian, k=32, cutoff=10.0, cutoff_mode='knn', | |
| update_x=True, act_fn='silu', norm=False): | |
| super().__init__() | |
| # Build the network | |
| self.num_layers = num_layers | |
| self.hidden_dim = hidden_dim | |
| self.edge_feat_dim = edge_feat_dim | |
| self.num_r_gaussian = num_r_gaussian | |
| self.update_x = update_x | |
| self.act_fn = act_fn | |
| self.norm = norm | |
| self.k = k | |
| self.cutoff = cutoff | |
| self.cutoff_mode = cutoff_mode | |
| self.distance_expansion = GaussianSmearing(stop=cutoff, num_gaussians=num_r_gaussian) | |
| self.net = self._build_network() | |
| def _build_network(self): | |
| # Equivariant layers | |
| layers = [] | |
| for l_idx in range(self.num_layers): | |
| layer = EnBaseLayer(self.hidden_dim, self.edge_feat_dim, self.num_r_gaussian, | |
| update_x=self.update_x, act_fn=self.act_fn, norm=self.norm) | |
| layers.append(layer) | |
| return nn.ModuleList(layers) | |
| # todo: refactor | |
| def _connect_edge(self, x, mask_ligand, batch): | |
| # if self.cutoff_mode == 'radius': | |
| # edge_index = radius_graph(x, r=self.r, batch=batch, flow='source_to_target') | |
| if self.cutoff_mode == 'knn': | |
| edge_index = knn_graph(x, k=self.k, batch=batch, flow='source_to_target') | |
| elif self.cutoff_mode == 'hybrid': | |
| edge_index = batch_hybrid_edge_connection( | |
| x, k=self.k, mask_ligand=mask_ligand, batch=batch, add_p_index=True) | |
| else: | |
| raise ValueError(f'Not supported cutoff mode: {self.cutoff_mode}') | |
| return edge_index | |
| # todo: refactor | |
| def _build_edge_type(edge_index, mask_ligand): | |
| src, dst = edge_index | |
| edge_type = torch.zeros(len(src)).to(edge_index) | |
| n_src = mask_ligand[src] == 1 | |
| n_dst = mask_ligand[dst] == 1 | |
| edge_type[n_src & n_dst] = 0 | |
| edge_type[n_src & ~n_dst] = 1 | |
| edge_type[~n_src & n_dst] = 2 | |
| edge_type[~n_src & ~n_dst] = 3 | |
| edge_type = F.one_hot(edge_type, num_classes=4) | |
| return edge_type | |
| def forward(self, h, x, mask_ligand, batch, return_all=False): | |
| all_x = [x] | |
| all_h = [h] | |
| for l_idx, layer in enumerate(self.net): | |
| edge_index = self._connect_edge(x, mask_ligand, batch) | |
| edge_type = self._build_edge_type(edge_index, mask_ligand) | |
| h, x = layer(h, x, edge_index, mask_ligand, edge_attr=edge_type) | |
| all_x.append(x) | |
| all_h.append(h) | |
| outputs = {'x': x, 'h': h} | |
| if return_all: | |
| outputs.update({'all_x': all_x, 'all_h': all_h}) | |
| return outputs | |