| from args import get_args |
| import torch.nn as nn |
| import torch |
|
|
|
|
| class BondWeightLayer(nn.Module): |
| def __init__(self, bond_types=4, hidden_dim=64): |
| super().__init__() |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
| self.bond_embedding = nn.Embedding(bond_types, hidden_dim) |
| self.edge_mlp = nn.Sequential( |
| nn.Linear(hidden_dim, 1), |
| ) |
|
|
| self.edge_mlp = self.edge_mlp.to(device) |
|
|
| nn.init.xavier_uniform_(self.bond_embedding.weight) |
| for layer in self.edge_mlp: |
| if isinstance(layer, nn.Linear): |
| nn.init.xavier_uniform_(layer.weight) |
| nn.init.zeros_(layer.bias) |
|
|
| def forward(self, edge_types): |
| bond_feats = self.bond_embedding(edge_types) |
| edge_weight = self.edge_mlp(bond_feats).squeeze() |
| edge_weight = edge_weight.squeeze() |
| return edge_weight |
|
|
| def init_weights(m): |
| if isinstance(m, nn.Linear): |
| nn.init.kaiming_uniform_(m.weight, nonlinearity='leaky_relu') |
| if m.bias is not None: |
| nn.init.zeros_(m.bias) |
|
|
|
|
| import torch.nn.functional as F |
| from torch_geometric.nn import GINEConv |
|
|
| import torch.nn as nn |
| from vq import VectorQuantize |
|
|
|
|
| class BondWeightLayer(nn.Module): |
| def __init__(self, bond_types=4, hidden_dim=64): |
| import torch |
| super().__init__() |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
| self.bond_embedding = nn.Embedding(bond_types, hidden_dim) |
| self.edge_mlp = nn.Sequential( |
| nn.Linear(hidden_dim, 1), |
| nn.Sigmoid() |
| ) |
| self.edge_mlp = self.edge_mlp.to(device) |
|
|
| def forward(self, edge_types): |
| bond_feats = self.bond_embedding(edge_types) |
| edge_weight = self.edge_mlp(bond_feats).squeeze() |
| return edge_weight |
|
|
| import torch |
|
|
| def _zeros_like(x, device=None, dtype=None): |
| return torch.zeros((), device=device or getattr(x, "device", None), dtype=dtype or getattr(x, "dtype", None)) |
|
|
| import torch |
| def _normalize_quantize_output(qo, logger, device=None, dtype=None): |
| """ |
| Normalize quantizer outputs to 7-tuple: |
| (loss, embed, commit_loss, cb_loss, sil_loss, repel_loss, cb_repel_loss) |
| |
| Assumption (ALWAYS): |
| qo == (total_loss, (commit_loss, codebook_loss, repel_loss, cb_repel_loss)) |
| |
| Rules: |
| - Never detach. |
| - Never wrap existing tensors into new tensors. |
| - Only convert python/np scalars/None to tensors. |
| """ |
|
|
| def _infer_device_dtype(*xs): |
| nonlocal device, dtype |
| if device is None or dtype is None: |
| for x in xs: |
| if torch.is_tensor(x): |
| if device is None: |
| device = x.device |
| if dtype is None: |
| dtype = x.dtype |
| return |
| if device is None: |
| device = "cpu" |
| if dtype is None: |
| dtype = torch.float32 |
|
|
| def _scalar_to_tensor(x): |
| if torch.is_tensor(x): |
| return x |
| if x is None: |
| _infer_device_dtype() |
| return torch.zeros((), device=device, dtype=dtype) |
| _infer_device_dtype() |
| return torch.as_tensor(x, device=device, dtype=dtype) |
|
|
| |
| |
| |
| |
| if not (isinstance(qo, (tuple, list)) and len(qo) == 2 and isinstance(qo[1], (tuple, list))): |
| raise TypeError(f"Expected (loss, (commit, cb, rep, cb_rep)) but got {type(qo)}: {qo}") |
|
|
| loss, inner = qo |
| if len(inner) != 5: |
| raise TypeError(f"Expected inner tuple length 5: (commit, cb, rep, cb_rep, ent), got {len(inner)}: {inner}") |
|
|
| commit, cb, rep, cb_rep, ent = inner[0], inner[1], inner[2], inner[3], inner[4] |
|
|
| |
| logger.info(f"end of model forward, loss {loss}, commit {commit}, ent {ent}") |
| _infer_device_dtype(loss, commit, cb, rep, cb_rep, ent) |
|
|
| embed = None |
| sil = None |
|
|
| return ( |
| _scalar_to_tensor(loss), |
| embed, |
| _scalar_to_tensor(commit), |
| _scalar_to_tensor(cb), |
| _scalar_to_tensor(sil), |
| _scalar_to_tensor(rep), |
| _scalar_to_tensor(cb_rep), |
| _scalar_to_tensor(ent), |
| ) |
|
|
| import torch.nn as nn |
| import torch |
| import torch.nn as nn |
| from utils import CORE_ELEMENTS |
|
|
| class AtomEmbedding(nn.Module): |
| def __init__(self): |
| super(AtomEmbedding, self).__init__() |
|
|
| |
| self.degree_embed = nn.Embedding(num_embeddings=7, embedding_dim=4) |
| self.ring_embed = nn.Embedding(num_embeddings=2, embedding_dim=4) |
| self.charge_embed = nn.Embedding(num_embeddings=8, embedding_dim=4) |
| self.aromatic_embed = nn.Embedding(num_embeddings=2, embedding_dim=4) |
| self.hybrid_embed = nn.Embedding(num_embeddings=6, embedding_dim=4) |
| self.hydrogen_embed = nn.Embedding(num_embeddings=5, embedding_dim=4) |
|
|
| ELEMENTS = [5, 6, 7, 8, 14, 15, 16] |
| self.register_buffer( |
| "element_lut", |
| self._build_element_lut(ELEMENTS) |
| ) |
| self.element_embed = nn.Embedding(num_embeddings=len(ELEMENTS), embedding_dim=4) |
|
|
| |
| def flag_emb(): |
| return nn.Embedding(num_embeddings=2, embedding_dim=2) |
|
|
| |
| self.func_embed_0 = flag_emb() |
| self.func_embed_1 = flag_emb() |
| self.func_embed_2 = flag_emb() |
| self.func_embed_3 = flag_emb() |
| self.func_embed_4 = flag_emb() |
| self.func_embed_5 = flag_emb() |
| self.func_embed_6 = flag_emb() |
| self.func_embed_7 = flag_emb() |
| self.func_embed_8 = flag_emb() |
| self.func_embed_9 = flag_emb() |
| self.func_embed_10 = flag_emb() |
| self.func_embed_11 = flag_emb() |
| self.func_embed_12 = flag_emb() |
| self.func_embed_13 = flag_emb() |
| self.func_embed_14 = flag_emb() |
| self.func_embed_15 = flag_emb() |
| self.func_embed_16 = flag_emb() |
| self.func_embed_17 = flag_emb() |
|
|
| |
| self.h_don_embed = flag_emb() |
| self.h_acc_embed = flag_emb() |
|
|
| |
| |
| self.ringsize_embed = nn.Embedding(num_embeddings=7, embedding_dim=4) |
| self.aroma_num_embed = nn.Embedding(num_embeddings=5, embedding_dim=4) |
| self.fused_if_embed = nn.Embedding(num_embeddings=8, embedding_dim=4) |
|
|
| |
| self.het27_embed = nn.Embedding(num_embeddings=27, embedding_dim=4) |
|
|
| |
| self.func_reduce = nn.Linear(18 * 2, 4) |
|
|
| |
| uniq = ['0', '3', '4', '5', '6', '7', '8'] |
| uniq_int = sorted(int(x) for x in uniq) |
| mapping = {v: i for i, v in enumerate(uniq_int)} |
| self.register_buffer( |
| "ring_values_tensor", |
| torch.tensor(uniq_int, dtype=torch.long) |
| ) |
| self.ring_value_to_index = mapping |
|
|
| |
| |
| self.bond_env_proj = nn.Linear(48, 16) |
|
|
| |
| |
| |
| |
| |
| |
| self.disc_proj = nn.Linear(52, 48) |
|
|
| |
| |
| |
| |
| self.out_dim = 48 + 16 |
|
|
| @staticmethod |
| def _build_element_lut(ELEMENTS): |
| max_z = max(ELEMENTS) |
| lut = torch.zeros(max_z + 1, dtype=torch.long) |
| for i, z in enumerate(ELEMENTS): |
| lut[z] = i |
| return lut |
|
|
| def forward(self, atom_inputs: torch.Tensor) -> torch.Tensor: |
| """ |
| Encode per-atom features into a dense embedding. |
| |
| Parameters |
| ---------- |
| atom_inputs : torch.Tensor |
| Shape [N, 79]. |
| |
| 0 : Z |
| 1 : degree |
| 2 : charge |
| 3 : hyb |
| 4 : arom |
| 5 : ring |
| 6 : hcount |
| 7-24 : func_flags (18) |
| 25 : H-donor flag |
| 26 : H-acceptor flag |
| 27 : ringSize (0,3,4,5,6,7,8,...) |
| 28 : #aromatic neighbors |
| 29 : fused ring id (0..7) |
| 30 : het27 (0..26) |
| 31-78 : bond_env_raw (48) float |
| Returns |
| ------- |
| out : torch.Tensor |
| Shape [N, 64] = disc_emb(48) + bond_env_emb(16) |
| """ |
| device = next(self.parameters()).device |
| x = atom_inputs.to(device=device, non_blocking=True) |
|
|
| if x.ndim != 2 or x.size(1) != 79: |
| raise ValueError(f"atom_inputs must be [N,79], got {tuple(x.shape)}") |
|
|
| |
| |
| |
| def clamp_idx(col: int, n_embed: int, lo: int = 0) -> torch.Tensor: |
| """Read integer column and clamp to valid embedding range.""" |
| return x[:, col].long().clamp(lo, n_embed - 1) |
|
|
| def flag01(col: int) -> torch.Tensor: |
| """Read 0/1 flag column.""" |
| return x[:, col].long().clamp(0, 1) |
|
|
| |
| |
| |
|
|
| |
| z_raw = x[:, 0].long() |
| max_z = int(self.element_lut.shape[0] - 1) |
| z_safe = torch.where((0 <= z_raw) & (z_raw <= max_z), z_raw, torch.zeros_like(z_raw)) |
| idx0 = self.element_lut[z_safe].clamp(0, self.element_embed.num_embeddings - 1) |
| x0 = self.element_embed(idx0) |
|
|
| |
| x1 = self.degree_embed(clamp_idx(1, self.degree_embed.num_embeddings)) |
|
|
| |
| ring_plus1 = (x[:, 5].long() + 1).clamp(0, self.ring_embed.num_embeddings - 1) |
| x2 = self.ring_embed(ring_plus1) |
|
|
| |
| x3 = self.charge_embed(clamp_idx(2, self.charge_embed.num_embeddings)) |
|
|
| |
| x4 = self.aromatic_embed(clamp_idx(4, self.aromatic_embed.num_embeddings)) |
|
|
| |
| x5 = self.hybrid_embed(clamp_idx(3, self.hybrid_embed.num_embeddings)) |
|
|
| |
| x6 = self.hydrogen_embed(clamp_idx(6, self.hydrogen_embed.num_embeddings)) |
|
|
| |
| |
| |
| |
| func_embeds = [ |
| self.func_embed_0, self.func_embed_1, self.func_embed_2, self.func_embed_3, |
| self.func_embed_4, self.func_embed_5, self.func_embed_6, self.func_embed_7, |
| self.func_embed_8, self.func_embed_9, self.func_embed_10, self.func_embed_11, |
| self.func_embed_12, self.func_embed_13, self.func_embed_14, self.func_embed_15, |
| self.func_embed_16, self.func_embed_17, |
| ] |
| flags = torch.cat([emb(flag01(7 + i)) for i, emb in enumerate(func_embeds)], dim=-1) |
| flags4 = self.func_reduce(flags) |
|
|
| |
| x25 = self.h_don_embed(flag01(25)) |
| x26 = self.h_acc_embed(flag01(26)) |
|
|
| |
| |
| |
| raw27 = x[:, 27].long() |
|
|
| |
| mapped27 = torch.full_like(raw27, fill_value=self.ringsize_embed.num_embeddings - 1) |
|
|
| |
| |
| for v, idx in self.ring_value_to_index.items(): |
| mapped27[raw27 == int(v)] = int(idx) |
|
|
| mapped27 = mapped27.clamp(0, self.ringsize_embed.num_embeddings - 1) |
| x27 = self.ringsize_embed(mapped27) |
|
|
| |
| x28 = self.aroma_num_embed(clamp_idx(28, self.aroma_num_embed.num_embeddings)) |
|
|
| |
| x29 = self.fused_if_embed(clamp_idx(29, self.fused_if_embed.num_embeddings)) |
|
|
| |
| x30 = self.het27_embed(clamp_idx(30, self.het27_embed.num_embeddings)) |
|
|
| |
| |
| |
| bond_env = x[:, 31:].to(dtype=torch.float32) |
| if bond_env.size(1) != 48: |
| raise ValueError(f"bond_env_raw must be 48 dims, got {bond_env.size(1)}") |
| bond_env_emb = self.bond_env_proj(bond_env) |
|
|
| |
| |
| |
| disc_cat = torch.cat( |
| [ |
| x0, x1, x2, x3, x4, x5, x6, |
| flags4, |
| x25, x26, |
| x27, x28, x29, x30, |
| ], |
| dim=-1, |
| ) |
|
|
| disc_emb = self.disc_proj(disc_cat) |
| out = torch.cat([disc_emb, bond_env_emb], dim=-1) |
| return out |
|
|
| class EquivariantThreeHopGINE(nn.Module): |
| def __init__(self, in_feats, hidden_feats, out_feats, args): |
| super().__init__() |
| import torch |
| if args is None: |
| args = get_args() |
|
|
| self.feat_embed = AtomEmbedding() |
| |
| self.linear_0 = nn.Linear(64, args.hidden_dim) |
|
|
| edge_emb_dim = getattr(args, "edge_emb_dim", 32) |
| self.bond_emb = nn.Embedding(5, edge_emb_dim, padding_idx=0) |
|
|
| def mlp(in_f, out_f): |
| return nn.Sequential( |
| nn.Linear(in_f, out_f), nn.ReLU(), |
| nn.Linear(out_f, out_f), nn.ReLU() |
| ) |
|
|
| nn1 = mlp(args.hidden_dim, hidden_feats) |
| nn2 = mlp(hidden_feats, hidden_feats) |
| nn3 = mlp(hidden_feats, hidden_feats) |
|
|
| self.gine1 = GINEConv(nn1, edge_dim=edge_emb_dim) |
| self.gine2 = GINEConv(nn2, edge_dim=edge_emb_dim) |
| self.gine3 = GINEConv(nn3, edge_dim=edge_emb_dim) |
|
|
| self.ln_in = nn.LayerNorm(args.hidden_dim) |
| self.ln1 = nn.LayerNorm(hidden_feats) |
| self.ln2 = nn.LayerNorm(hidden_feats) |
| self.ln3 = nn.LayerNorm(hidden_feats) |
|
|
| |
| self.res1 = nn.Parameter(torch.tensor(0.5)) |
| self.res2 = nn.Parameter(torch.tensor(0.5)) |
| self.res3 = nn.Parameter(torch.tensor(0.5)) |
|
|
| |
| self.skip0 = None |
| if args.hidden_dim != hidden_feats: |
| self.skip0 = nn.Linear(args.hidden_dim, hidden_feats, bias=False) |
|
|
| |
| jk_dim = args.hidden_dim + 3 * hidden_feats |
| self.mix = nn.Sequential( |
| nn.Linear(jk_dim, 2 * hidden_feats), nn.ReLU(), |
| nn.Linear(2 * hidden_feats, hidden_feats), nn.ReLU(), |
| ) |
|
|
| |
| self.out_proj = nn.Linear(hidden_feats, args.hidden_dim) |
| self.pre_vq_ln = nn.LayerNorm(args.hidden_dim) |
|
|
| self.vq = VectorQuantize( |
| dim=args.hidden_dim, |
| codebook_size=args.codebook_size, |
| decay=getattr(args, "ema_decay", 0.8), |
| threshold_ema_dead_code=2, |
| ) |
|
|
| def reset_kmeans(self): |
| self.vq._codebook.reset_kmeans() |
|
|
| def forward(self, data, features, chunk_i, mask_dict=None, logger=None, epoch=None, |
| batched_graph_base=None, mode=None, attr_list=None): |
| import torch |
| dev = next(self.parameters()).device |
| if mode == "init_kmeans_final": |
| if hasattr(data, "to"): |
| data = data.to(dev) |
| if torch.is_tensor(features): |
| features = features.to(dev, non_blocking=True) |
| self.vq(data, attr_list, mask_dict, logger, chunk_i, epoch, mode) |
| return 0 |
|
|
| |
| s1, d1 = data.edges() |
| s1 = s1.to(dev, non_blocking=True); d1 = d1.to(dev, non_blocking=True) |
| src = torch.cat([s1, d1], 0); dst = torch.cat([d1, s1], 0) |
| edge_index = torch.stack([src, dst], 0) |
|
|
| |
| eb = data.edata.get("weight", torch.zeros(data.num_edges(), dtype=torch.long, device=s1.device)) |
| eb = eb.to(dev, non_blocking=True) |
| e = torch.cat([eb, eb], 0) |
| e = torch.where((e >= 1) & (e <= 4), e, torch.zeros_like(e)) |
| edge_attr = self.bond_emb(e.long()) |
|
|
| |
| features = features.to(dev, non_blocking=True) |
| h0_raw = self.feat_embed(features) |
| h0 = self.ln_in(self.linear_0(h0_raw)) |
|
|
| |
| h0_for1 = self.skip0(h0) if self.skip0 is not None else h0 |
| h1 = self.gine1(h0, edge_index, edge_attr) |
| h1 = self.ln1(h1 * self.res1 + h0_for1) |
|
|
| |
| h2 = self.gine2(h1, edge_index, edge_attr) |
| h2 = self.ln2(h2 * self.res2 + h1) |
| |
| h3 = self.gine3(h2, edge_index, edge_attr) |
| h3 = self.ln3(h3 * self.res3 + h2) |
|
|
| |
| h_cat = torch.cat([h0, h1, h2, h3], dim=-1) |
| h_mid = self.mix(h_cat) |
| h_out = self.out_proj(h_mid) |
|
|
| |
| if mode == "init_kmeans_loop": |
| return h_out |
| import torch.nn.functional as F |
| h_vq = self.pre_vq_ln(h_out) |
|
|
| |
| h_vq = F.normalize(h_vq, p=2, dim=-1, eps=1e-12) |
|
|
| quantize_output = self.vq( |
| h_vq, |
| attr_list, |
| mask_dict, |
| logger, |
| chunk_i, |
| epoch, |
| mode, |
| ) |
| if mode == "infer": |
| |
| if isinstance(quantize_output, (tuple, list)) and len(quantize_output) == 4: |
| kid_full, cid_full, gid_full, id2safe = quantize_output |
| |
| |
| ids = (kid_full, cid_full, gid_full, id2safe) |
| |
| |
| return None, ids, None |
|
|
| |
| if isinstance(quantize_output, (tuple, list)) and len(quantize_output) == 3: |
| gid_full, cid_full, id2safe = quantize_output |
| kid_full = gid_full |
| ids = (kid_full, cid_full, gid_full, id2safe) |
| return None, ids, None |
|
|
| raise TypeError(f"[model.forward] infer expected vq to return 4-tuple, got {type(quantize_output)}") |
| |
| |
| |
| if mode == "infer": |
| |
| |
| if not (isinstance(quantize_output, (tuple, list)) and len(quantize_output) == 3): |
| raise TypeError( |
| f"[model.forward] mode='infer' expects vq() -> (key_id_full, cluster_id_full, id2safe), " |
| f"got {type(quantize_output)} with len={len(quantize_output) if isinstance(quantize_output,(tuple,list)) else 'NA'}" |
| ) |
|
|
| key_id_full, cluster_id_full, id2safe = quantize_output |
|
|
| |
| key_id_full = key_id_full.reshape(-1).long() |
| cluster_id_full = cluster_id_full.reshape(-1).long() |
|
|
| return key_id_full, cluster_id_full, id2safe |
|
|
| |
| loss, _embed_ignored, commit_loss, cb_loss, sil_loss, repel_loss, cb_repel_loss, ent_loss = _normalize_quantize_output( |
| quantize_output, logger, |
| device=h_vq.device if torch.is_tensor(h_vq) else None, |
| dtype=getattr(h_vq, "dtype", None) |
| ) |
|
|
| |
| embed = h_vq |
|
|
| |
| |
| |
| return loss, embed, [commit_loss, cb_repel_loss, repel_loss, cb_loss, sil_loss, ent_loss] |
|
|
|
|
| class Model(nn.Module): |
| """ |
| Wrapper of different models |
| """ |
|
|
| def __init__(self, conf): |
| super(Model, self).__init__() |
| self.model_name = conf["model_name"] |
| if "MLP" in conf["model_name"]: |
| self.encoder = MLP( |
| num_layers=conf["num_layers"], |
| input_dim=conf["feat_dim"], |
| hidden_dim=conf["hidden_dim"], |
| output_dim=conf["label_dim"], |
| dropout_ratio=conf["dropout_ratio"], |
| norm_type=conf["norm_type"], |
| ).to(conf["device"]) |
| elif "SAGE" in conf["model_name"]: |
| self.encoder = SAGE( |
| num_layers=conf["num_layers"], |
| input_dim=conf["feat_dim"], |
| hidden_dim=conf["hidden_dim"], |
| output_dim=conf["label_dim"], |
| dropout_ratio=conf["dropout_ratio"], |
| activation=F.relu, |
| norm_type=conf["norm_type"], |
| codebook_size=conf["codebook_size"], |
| lamb_edge=conf["lamb_edge"], |
| lamb_node=conf["lamb_node"], |
| lamb_div_ele=conf["lamb_div_ele"] |
| ).to(conf["device"]) |
| elif "GCN" in conf["model_name"]: |
| self.encoder = GCN( |
| num_layers=conf["num_layers"], |
| input_dim=conf["feat_dim"], |
| hidden_dim=conf["hidden_dim"], |
| output_dim=conf["label_dim"], |
| dropout_ratio=conf["dropout_ratio"], |
| activation=F.relu, |
| norm_type=conf["norm_type"], |
| codebook_size=conf["codebook_size"], |
| lamb_edge=conf["lamb_edge"], |
| lamb_node=conf["lamb_node"] |
| ).to(conf["device"]) |
| elif "GAT" in conf["model_name"]: |
| self.encoder = GAT( |
| num_layers=conf["num_layers"], |
| input_dim=conf["feat_dim"], |
| hidden_dim=conf["hidden_dim"], |
| output_dim=conf["label_dim"], |
| dropout_ratio=conf["dropout_ratio"], |
| activation=F.relu, |
| attn_drop=conf["attn_dropout_ratio"], |
| ).to(conf["device"]) |
| elif "APPNP" in conf["model_name"]: |
| self.encoder = APPNP( |
| num_layers=conf["num_layers"], |
| input_dim=conf["feat_dim"], |
| hidden_dim=conf["hidden_dim"], |
| output_dim=conf["label_dim"], |
| dropout_ratio=conf["dropout_ratio"], |
| activation=F.relu, |
| norm_type=conf["norm_type"], |
| ).to(conf["device"]) |
|
|
| def forward(self, data, feats, epoch, logger): |
| """ |
| data: a graph `g` or a `dataloader` of blocks |
| """ |
| if "MLP" in self.model_name: |
| return self.encoder(feats) |
| else: |
| return self.encoder(data, feats, epoch, logger) |
|
|
| def forward_fitnet(self, data, feats): |
| """ |
| Return a tuple (h_list, h) |
| h_list: intermediate hidden representation |
| h: final output |
| """ |
| if "MLP" in self.model_name: |
| return self.encoder(feats) |
| else: |
| return self.encoder(data, feats) |
|
|
| def inference(self, data, feats): |
| if "SAGE" in self.model_name: |
| |
|
|
| return self.encoder.inference(data, feats) |
| else: |
| return self.forward(data, feats) |
|
|