File size: 5,348 Bytes
111d270
 
 
848dcb6
0b12001
 
 
 
 
111d270
 
a7ff6bf
111d270
 
e2dbbbe
ad553a5
e2dbbbe
 
0b12001
 
 
 
c4c625d
0b12001
 
 
 
 
 
 
c4c625d
0b12001
 
38688e0
c4c625d
 
 
 
 
 
e2dbbbe
 
 
 
d1e7bbb
 
e2dbbbe
 
c4c625d
111d270
 
 
 
 
 
 
 
 
 
0b12001
38688e0
62fd606
111d270
 
0b12001
 
111d270
 
 
 
 
 
 
 
 
 
0b12001
111d270
 
 
 
 
dacb5e3
111d270
 
 
 
 
 
e2dbbbe
111d270
 
 
 
dacb5e3
111d270
 
 
dacb5e3
111d270
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454364c
111d270
 
 
 
 
 
 
 
 
 
e2dbbbe
2753942
4c450f8
2753942
111d270
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
from transformers import Pipeline
import torch
import torch.nn.functional as F
import numpy as np
from transformers import Pipeline
import torch
import torch.nn.functional as F
import re

class SmilesDiffusionPipe(Pipeline):
    def __init__(self, model, voc, **kwargs):
        super().__init__(model=model, **kwargs)
        self.voc = voc

    def restore_halogen(self, smiles):
        """Restores halogens back to their proper form after decoding."""
        return smiles.replace('L', 'Cl').replace('R', 'Br')
    
    def replace_halogen(self, smiles):
        return smiles.replace('Cl', 'L').replace('Br', 'R')

    def tokenize_smiles(self, smiles):
        regex = r'(\[[^\[\]]{1,6}\])'
        smiles = self.replace_halogen(smiles)
        char_list = re.split(regex, smiles)
        tokenized = []
        for char in char_list:
            if char.startswith('['):
                tokenized.append(char)
            else:
                tokenized.extend(list(char))
        return tokenized

    def encode(self, char_list):
        smiles_matrix = np.zeros(len(char_list), dtype=np.float32)
        for i, char in enumerate(char_list):
            if char not in self.voc.vocab:
                raise ValueError(f"Token '{char}' not in vocabulary.")
            smiles_matrix[i] = self.voc.vocab[char]
        return smiles_matrix
        
    def decode(self, matrix):
        """Takes an array of indices (can be list of tensors or ints) and returns the corresponding SMILES"""
        matrix = [int(i) for i in matrix]  # This handles tensors from PyTorch
        # Using tokenizer's decode method to convert token IDs to text
        smiles = self.voc.decode(matrix)
        smiles = self.restore_halogen(smiles)  # Restore halogens after decoding
        return smiles

    def _sanitize_parameters(self, **kwargs):
        preprocess_kwargs = {}
        postprocess_kwargs = {}
        if "steps" in kwargs:
            preprocess_kwargs["steps"] = kwargs["steps"]
        if "k" in kwargs:
            preprocess_kwargs["k"] = kwargs["k"]
        return preprocess_kwargs, {}, postprocess_kwargs

    def preprocess(self, smiles_str, steps=30, k=1):
        tokenized = self.tokenize_smiles(smiles_str)
        encoded = self.encode(tokenized)
        return (encoded, steps, k)

    def _forward(self, inputs):
        encoded, steps, k = inputs
        return self.unmask_partial_smiles(encoded, self.model, self.voc, steps, k)

    def postprocess(self, result):
        return result

    def topk_sample(self, probs, k=5):
        topk_probs, topk_indices = torch.topk(probs, k)
        topk_probs = topk_probs / topk_probs.sum(dim=-1, keepdim=True)
        sampled = torch.multinomial(topk_probs, 1)
        return topk_indices.gather(-1, sampled)

    def unmask_partial_smiles(self, input_ids, model, voc, steps=30, k=1):
        model.eval()
        with torch.no_grad():
            sequences = torch.tensor(input_ids, dtype=torch.long, device=self.device).unsqueeze(0)

            total_tokens = sequences.size(1)
            original_num_masked = (sequences == voc.vocab['[MASK]']).sum().item()
            print(f"Original number of masked tokens: {original_num_masked}")

            if total_tokens == 0:
                raise ValueError("Encoded SMILES has no tokens.")
            if original_num_masked == 0:
                print("No masked tokens found. Returning original SMILES.")
                return self.decode(input_ids)

            filled_tokens_confidence = {}

            for step in range(steps):
                t = torch.tensor([(sequences == voc.vocab['[MASK]']).sum().item() / total_tokens], device=self.device)
                logits = model(sequences, t=t)
                probs = F.softmax(logits, dim=-1)

                mask_indices = (sequences == voc.vocab['[MASK]']).nonzero(as_tuple=True)[1]
                if len(mask_indices) == 0:
                    print("All tokens filled.")
                    break

                confidences = []
                filled_indices = []

                for idx in mask_indices:
                    token_probs = probs[0, idx]
                    topk_token = self.topk_sample(token_probs, k)
                    confidence = token_probs[topk_token].item()
                    sequences[0, idx] = topk_token
                    confidences.append(confidence)
                    filled_indices.append(idx)
                    filled_tokens_confidence[idx] = confidence

                avg_conf = sum(confidences) / len(confidences)

                for idx, conf in zip(filled_indices, confidences):
                    if conf < avg_conf:
                        sequences[0, idx] = voc.vocab['<MASK>']
                        filled_tokens_confidence[idx] = None

            sorted_filled_tokens = sorted(
                ((idx, conf) for idx, conf in filled_tokens_confidence.items() if conf is not None),
                key=lambda x: x[0]
            )

            for idx, confidence in sorted_filled_tokens:
                print(f"Final confidence for token at index {idx}: {confidence:.3f}")

            decoded = self.decode([
                token for token in sequences[0].tolist()
                if token not in (voc.vocab.get('<s>'), voc.vocab.get('</s>'))
            ]).replace(" ", "")
            return decoded