D4CMPP2 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. D4CMPP2/_Data/AGENTS.md +24 -0
  2. D4CMPP2/_Data/Aqsoldb.csv +9291 -0
  3. D4CMPP2/_Data/BradleyMP.csv +3042 -0
  4. D4CMPP2/_Data/Lipophilicity.csv +1131 -0
  5. D4CMPP2/_Data/README.md +8 -0
  6. D4CMPP2/_Data/__init__.py +26 -0
  7. D4CMPP2/_Data/optical.csv +20237 -0
  8. D4CMPP2/_Data/test.csv +190 -0
  9. D4CMPP2/__init__.py +16 -0
  10. D4CMPP2/__main__.py +5 -0
  11. D4CMPP2/_main.py +500 -0
  12. D4CMPP2/cli.py +7 -0
  13. D4CMPP2/exceptions.py +53 -0
  14. D4CMPP2/grid_search.py +259 -0
  15. D4CMPP2/network_refer.yaml +160 -0
  16. D4CMPP2/networks/AFP_model.py +72 -0
  17. D4CMPP2/networks/AFPwithSolv_model.py +72 -0
  18. D4CMPP2/networks/DMPNN_model.py +90 -0
  19. D4CMPP2/networks/DMPNNwithSolv_model.py +89 -0
  20. D4CMPP2/networks/GAT_model.py +48 -0
  21. D4CMPP2/networks/GATwithSolv_model.py +63 -0
  22. D4CMPP2/networks/GCN_model.py +113 -0
  23. D4CMPP2/networks/GCNwithSolv_model.py +103 -0
  24. D4CMPP2/networks/GC_model.py +122 -0
  25. D4CMPP2/networks/ISATPM_model.py +14 -0
  26. D4CMPP2/networks/ISATPN_model.py +199 -0
  27. D4CMPP2/networks/ISAT_model.py +90 -0
  28. D4CMPP2/networks/MPNN_model.py +56 -0
  29. D4CMPP2/networks/MPNNwithSolv_model.py +72 -0
  30. D4CMPP2/networks/__init__.py +25 -0
  31. D4CMPP2/networks/base.py +250 -0
  32. D4CMPP2/networks/registry.py +187 -0
  33. D4CMPP2/networks/src/AFP.py +118 -0
  34. D4CMPP2/networks/src/BiDropout.py +29 -0
  35. D4CMPP2/networks/src/DMPNN.py +35 -0
  36. D4CMPP2/networks/src/GAT.py +69 -0
  37. D4CMPP2/networks/src/GC.py +85 -0
  38. D4CMPP2/networks/src/GCN.py +71 -0
  39. D4CMPP2/networks/src/ISAT.py +153 -0
  40. D4CMPP2/networks/src/Linear.py +49 -0
  41. D4CMPP2/networks/src/MPNN.py +56 -0
  42. D4CMPP2/networks/src/SolventLayer.py +62 -0
  43. D4CMPP2/networks/src/__init__.py +0 -0
  44. D4CMPP2/networks/src/distGCN.py +21 -0
  45. D4CMPP2/networks/src/pyg_hetero.py +24 -0
  46. D4CMPP2/optimize.py +472 -0
  47. D4CMPP2/src/Analyzer/ISAAnalyzer.py +458 -0
  48. D4CMPP2/src/Analyzer/ISAPNAnalyzer.py +366 -0
  49. D4CMPP2/src/Analyzer/ISAwSAnalyzer.py +117 -0
  50. D4CMPP2/src/Analyzer/MolAnalyzer.py +319 -0
  51. D4CMPP2/src/Analyzer/__init__.py +54 -0
  52. D4CMPP2/src/Analyzer/core.py +480 -0
  53. D4CMPP2/src/Analyzer/factory.py +166 -0
  54. D4CMPP2/src/Analyzer/interpretation.py +232 -0
  55. D4CMPP2/src/Analyzer/results.py +101 -0
  56. D4CMPP2/src/DataManager/Dataset/GraphDataset.py +314 -0
  57. D4CMPP2/src/DataManager/Dataset/ISAGraphDataset.py +384 -0
  58. D4CMPP2/src/DataManager/Dataset/__init__.py +0 -0
  59. D4CMPP2/src/DataManager/GraphGenerator/ISAGraphGenerator.py +223 -0
  60. D4CMPP2/src/DataManager/GraphGenerator/MolGraphGenerator.py +73 -0
  61. D4CMPP2/src/DataManager/GraphGenerator/__init__.py +14 -0
  62. D4CMPP2/src/DataManager/ISADataManager.py +67 -0
  63. D4CMPP2/src/DataManager/MolDataManager.py +735 -0
  64. D4CMPP2/src/DataManager/__init__.py +14 -0
  65. D4CMPP2/src/DataManager/contracts.py +179 -0
  66. D4CMPP2/src/NetworkManager/ISANetworkManager.py +12 -0
  67. D4CMPP2/src/NetworkManager/NetworkManager.py +520 -0
  68. D4CMPP2/src/NetworkManager/__init__.py +14 -0
  69. D4CMPP2/src/PostProcessor.py +160 -0
  70. D4CMPP2/src/TrainManager/ISATrainManager.py +26 -0
  71. D4CMPP2/src/TrainManager/TrainManager.py +254 -0
  72. D4CMPP2/src/TrainManager/__init__.py +14 -0
  73. D4CMPP2/src/TrainManager/callbacks.py +119 -0
  74. D4CMPP2/src/__init__.py +0 -0
  75. D4CMPP2/src/utils/PATH.py +246 -0
  76. D4CMPP2/src/utils/__init__.py +0 -0
  77. D4CMPP2/src/utils/argparser.py +56 -0
  78. D4CMPP2/src/utils/checkpointing.py +90 -0
  79. D4CMPP2/src/utils/config_resolution.py +123 -0
  80. D4CMPP2/src/utils/config_validation.py +370 -0
  81. D4CMPP2/src/utils/csv_validation.py +105 -0
  82. D4CMPP2/src/utils/data_quality.py +181 -0
  83. D4CMPP2/src/utils/featureizer.py +202 -0
  84. D4CMPP2/src/utils/functional_group.csv +169 -0
  85. D4CMPP2/src/utils/graph_cache.py +213 -0
  86. D4CMPP2/src/utils/leaderboard.py +212 -0
  87. D4CMPP2/src/utils/metrics.py +31 -0
  88. D4CMPP2/src/utils/module_loader.py +147 -0
  89. D4CMPP2/src/utils/output.py +80 -0
  90. D4CMPP2/src/utils/reproducibility.py +70 -0
  91. D4CMPP2/src/utils/run_manifest.py +175 -0
  92. D4CMPP2/src/utils/scaler.py +40 -0
  93. D4CMPP2/src/utils/sculptor.py +713 -0
  94. D4CMPP2/src/utils/splitting.py +250 -0
  95. D4CMPP2/src/utils/supportfile_saver.py +94 -0
  96. D4CMPP2/src/utils/tools.py +156 -0
  97. D4CMPP2/src/utils/transfer_learning.py +111 -0
  98. d4cmpp2-0.4.0.dist-info/METADATA +420 -0
  99. d4cmpp2-0.4.0.dist-info/RECORD +103 -0
  100. d4cmpp2-0.4.0.dist-info/WHEEL +5 -0
  101. d4cmpp2-0.4.0.dist-info/entry_points.txt +2 -0
  102. d4cmpp2-0.4.0.dist-info/licenses/LICENSE +21 -0
  103. d4cmpp2-0.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,366 @@
1
+ """Positive-negative ISA Analyzer implementations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import warnings
6
+
7
+ import io
8
+
9
+ import matplotlib.pyplot as plt
10
+ import numpy as np
11
+ from matplotlib.colors import LinearSegmentedColormap
12
+ from matplotlib.gridspec import GridSpec
13
+ from PIL import Image
14
+ import rdkit.Chem as Chem
15
+
16
+ from .ISAAnalyzer import ISAAnalyzer, ISAAnalyzer_v2, showAtomHighlight
17
+ from .interpretation import ISAInterpreter
18
+
19
+
20
+ class _ISAPNAnalyzerMixin:
21
+ """ISATPN-only positive/negative score and hidden-feature behavior."""
22
+
23
+ def _configure_isapn_interpreter(self):
24
+ self.data_keys = [
25
+ "prediction",
26
+ "positive",
27
+ "negative",
28
+ "feature_P",
29
+ "feature_N",
30
+ "fragments",
31
+ ]
32
+ self._interpreter = ISAInterpreter(
33
+ self._core,
34
+ score_keys=("positive", "negative"),
35
+ feature_keys=("feature_P", "feature_N"),
36
+ score_mode="fragment",
37
+ )
38
+
39
+ def analyze_rows(self, *args, include_features=True, **kwargs):
40
+ """Return ISATPN positive/negative scores and optional P/N features."""
41
+
42
+ return self._interpreter.analyze(
43
+ *args, include_features=include_features, **kwargs
44
+ )
45
+
46
+ def get_score(self, *args, **kwargs):
47
+ """Return row-aligned positive and negative ISATPN scores."""
48
+
49
+ analysis = self.analyze_rows(*args, include_features=False, **kwargs)
50
+ values = [dict(row.scores) for row in analysis]
51
+ return values[0] if len(values) == 1 else values
52
+
53
+ def get_scores(self, smiles_list):
54
+ """Return positive and negative scores keyed by SMILES."""
55
+
56
+ analysis = self.analyze_rows(smiles_list, include_features=False)
57
+ molecule_column = self.molecule_columns[0]
58
+ return {
59
+ row.inputs[molecule_column]: dict(row.scores)
60
+ for row in analysis
61
+ }
62
+
63
+ def plot_analysis(self, analysis_row, **kwargs):
64
+ """Plot both score streams from one immutable ISATPN analysis row."""
65
+
66
+ smiles = analysis_row.inputs[self.molecule_columns[0]]
67
+ return self._plot_score(
68
+ smiles,
69
+ {
70
+ "positive": analysis_row.atom_scores("positive"),
71
+ "negative": analysis_row.atom_scores("negative"),
72
+ },
73
+ **kwargs,
74
+ )
75
+
76
+ def plot_score(self, *args, draw_mol_columns=None, **kwargs):
77
+ """Calculate and plot both ISATPN score streams."""
78
+
79
+ if draw_mol_columns is None:
80
+ draw_mol_columns = self.molecule_columns[0]
81
+ if draw_mol_columns not in self.molecule_columns:
82
+ raise ValueError(
83
+ f"{draw_mol_columns} is not a valid molecule column. "
84
+ f"Choose from {self.molecule_columns}."
85
+ )
86
+ inputs, plot_kwargs = self.handle_positional_args(args, kwargs)
87
+ smiles = inputs.get(draw_mol_columns, [None])[0]
88
+ if smiles is None:
89
+ raise ValueError(f"{draw_mol_columns} must be specified.")
90
+ scores = self.get_score(**inputs)
91
+ return self._plot_score(smiles, scores, **plot_kwargs)
92
+
93
+ def get_feature(self, *args, feature=None, **kwargs):
94
+ """Return row-aligned ISATPN positive/negative hidden features."""
95
+
96
+ analysis = self.analyze_rows(*args, include_features=True, **kwargs)
97
+ rows = []
98
+ for row in analysis:
99
+ values = dict(row.features)
100
+ if feature is not None:
101
+ if feature not in values:
102
+ raise ValueError(
103
+ f"Feature {feature!r} is unavailable. "
104
+ f"Available features: {list(values)}."
105
+ )
106
+ rows.append(values[feature])
107
+ else:
108
+ rows.append(values)
109
+ return rows[0] if len(rows) == 1 else rows
110
+
111
+ def _plot_score(
112
+ self,
113
+ smiles,
114
+ score,
115
+ *,
116
+ atom_with_index=False,
117
+ score_scaler=lambda value: value,
118
+ ticks=(0, 0.25, 0.5, 0.75, 1),
119
+ tick_type="manual",
120
+ rot=0,
121
+ line_width=2.0,
122
+ locate="right",
123
+ figsize=1,
124
+ only_total=False,
125
+ with_colorbar=True,
126
+ **kwargs,
127
+ ):
128
+ """Plot ISATPN positive, negative, and combined attribution scores."""
129
+
130
+ molecule = Chem.MolFromSmiles(smiles)
131
+ positive = self.get_atom_score(
132
+ smiles,
133
+ np.asarray(score_scaler(score["positive"])),
134
+ )
135
+ negative = self.get_atom_score(
136
+ smiles,
137
+ np.asarray(score_scaler(score["negative"])),
138
+ )
139
+ raw_total = (1 + positive - negative) / 2
140
+ total = np.asarray(score_scaler(raw_total))
141
+ color_map = LinearSegmentedColormap.from_list(
142
+ "isapn_score",
143
+ [(0, "orangered"), (0.5, "white"), (1, "royalblue")],
144
+ )
145
+
146
+ def draw(axis, values, title, scale=1):
147
+ png = showAtomHighlight(
148
+ molecule,
149
+ values,
150
+ color_map,
151
+ atom_with_index,
152
+ rot,
153
+ line_width,
154
+ figsize=scale,
155
+ )
156
+ axis.imshow(Image.open(io.BytesIO(png)))
157
+ axis.axis("off")
158
+ axis.set_title(title, fontsize=20)
159
+
160
+ if only_total:
161
+ figure, axis = plt.subplots(
162
+ 1,
163
+ 1,
164
+ figsize=(16 * figsize, 11 * figsize),
165
+ )
166
+ draw(axis, total, " ", figsize)
167
+ else:
168
+ if locate == "right":
169
+ grid = GridSpec(2, 3)
170
+ positions = (grid[0:2, 0:2], grid[0, 2], grid[1, 2])
171
+ figure = plt.figure(figsize=(16 * figsize, 10 * figsize))
172
+ elif locate == "bottom":
173
+ grid = GridSpec(3, 2)
174
+ positions = (grid[0:2, 0:2], grid[2, 0], grid[2, 1])
175
+ figure = plt.figure(figsize=(12 * figsize, 12 * figsize))
176
+ else:
177
+ raise ValueError("locate must be either 'right' or 'bottom'.")
178
+ axes = [figure.add_subplot(position) for position in positions]
179
+ draw(axes[0], total, " ", figsize)
180
+ draw(axes[1], positive, "Positive", figsize * 0.5)
181
+ draw(axes[2], negative, "Negative", figsize * 0.5)
182
+ axis = axes[0]
183
+ if with_colorbar:
184
+ colorbar = plt.colorbar(
185
+ plt.cm.ScalarMappable(cmap=color_map),
186
+ ax=axis,
187
+ shrink=0.5,
188
+ pad=0,
189
+ )
190
+ colorbar.set_ticks([0, 0.25, 0.5, 0.75, 1])
191
+ labels = ticks
192
+ if tick_type == "minmax":
193
+ labels = np.round(
194
+ np.linspace(np.min(raw_total), np.max(raw_total), 5),
195
+ 3,
196
+ )
197
+ colorbar.set_ticklabels(labels)
198
+ plt.show()
199
+ return {
200
+ "positive": positive,
201
+ "negative": negative,
202
+ "total": total,
203
+ }
204
+
205
+ def get_subgroup_score_bin(self, smiles_list):
206
+ """Group both ISATPN score streams by fragment SMILES."""
207
+
208
+ positive_fragments = {}
209
+ negative_fragments = {}
210
+ for smiles, result in self.get_scores(smiles_list).items():
211
+ positive = np.asarray(result["positive"]).reshape(-1, 1)
212
+ negative = np.asarray(result["negative"]).reshape(-1, 1)
213
+ for index, fragment in enumerate(self.get_fragment(smiles)):
214
+ for destination, values in (
215
+ (positive_fragments, positive),
216
+ (negative_fragments, negative),
217
+ ):
218
+ current = values[index]
219
+ if fragment.smiles in destination:
220
+ current = np.concatenate(
221
+ [destination[fragment.smiles], current],
222
+ axis=0,
223
+ )
224
+ destination[fragment.smiles] = current
225
+ return positive_fragments, negative_fragments
226
+
227
+ def plot_score_hist(self, smiles_list, xlim=(0, 1), bins=40):
228
+ """Plot and return both ISATPN score streams."""
229
+
230
+ positive = []
231
+ negative = []
232
+ for result in self.get_scores(smiles_list).values():
233
+ positive.extend(set(np.asarray(result["positive"]).flatten()))
234
+ negative.extend(set(np.asarray(result["negative"]).flatten()))
235
+ edges = np.arange(xlim[0], xlim[1], 1.0 / bins)
236
+ plt.hist(positive, bins=edges, alpha=0.5, label="positive")
237
+ plt.hist(negative, bins=edges, alpha=0.5, label="negative")
238
+ plt.legend()
239
+ plt.xlim(*xlim)
240
+ plt.show()
241
+ return positive, negative
242
+
243
+ def plot_subgroup_score_histogram(
244
+ self,
245
+ smiles_list,
246
+ nums=10,
247
+ bins=40,
248
+ xlim=(0, 1),
249
+ ):
250
+ """Plot fragment histograms separately for both ISATPN streams."""
251
+
252
+ positive, negative = self.get_subgroup_score_bin(smiles_list)
253
+ positive = dict(
254
+ sorted(positive.items(), key=lambda item: len(item[1]), reverse=True)[
255
+ :nums
256
+ ]
257
+ )
258
+ negative = dict(
259
+ sorted(negative.items(), key=lambda item: len(item[1]), reverse=True)[
260
+ :nums
261
+ ]
262
+ )
263
+ edges = np.arange(xlim[0], xlim[1], 1.0 / bins)
264
+ for title, values in (("Positive", positive), ("Negative", negative)):
265
+ for fragment, scores in values.items():
266
+ plt.hist(scores, bins=edges, alpha=0.5, label=fragment)
267
+ plt.xlim(*xlim)
268
+ plt.legend()
269
+ plt.title(title)
270
+ plt.show()
271
+ return positive, negative
272
+
273
+ def plot_subgroup_score_histogram_byone(
274
+ self,
275
+ smiles_list,
276
+ nums=10,
277
+ bins=40,
278
+ xlim=(0, 1),
279
+ target_subgroups=None,
280
+ ):
281
+ """Plot both ISATPN streams together for each selected fragment."""
282
+
283
+ positive, negative = self.get_subgroup_score_bin(smiles_list)
284
+ positive = dict(
285
+ sorted(positive.items(), key=lambda item: len(item[1]), reverse=True)[
286
+ :nums
287
+ ]
288
+ )
289
+ negative = dict(
290
+ sorted(negative.items(), key=lambda item: len(item[1]), reverse=True)[
291
+ :nums
292
+ ]
293
+ )
294
+ if target_subgroups:
295
+ positive = {
296
+ key: value
297
+ for key, value in positive.items()
298
+ if key in target_subgroups
299
+ }
300
+ negative = {
301
+ key: value
302
+ for key, value in negative.items()
303
+ if key in target_subgroups
304
+ }
305
+ figure, axes = plt.subplots(
306
+ max(len(positive), 1),
307
+ 1,
308
+ figsize=(5, 2 * max(len(positive), 1)),
309
+ squeeze=False,
310
+ )
311
+ edges = np.arange(xlim[0], xlim[1], 1.0 / bins)
312
+ for axis, (fragment, values) in zip(axes[:, 0], positive.items()):
313
+ axis.hist(values, bins=edges, alpha=0.5, label="positive")
314
+ if fragment in negative:
315
+ axis.hist(
316
+ negative[fragment],
317
+ bins=edges,
318
+ alpha=0.5,
319
+ label="negative",
320
+ )
321
+ axis.set_ylabel(fragment)
322
+ axis.set_xlim(*xlim)
323
+ axis.legend()
324
+ plt.show()
325
+ return positive, negative
326
+
327
+
328
+ class ISAPNAnalyzer(_ISAPNAnalyzerMixin, ISAAnalyzer):
329
+ """Legacy-compatible Analyzer for ISATPN positive/negative models."""
330
+
331
+ def __init__(self, model_path, save_result=False, **kwargs):
332
+ super().__init__(model_path, save_result, **kwargs)
333
+ self._configure_isapn_interpreter()
334
+
335
+
336
+ class ISAPNAnalyzer_v2(_ISAPNAnalyzerMixin, ISAAnalyzer_v2):
337
+ """Version 2 Analyzer for ISATPN positive/negative models."""
338
+
339
+ def __init__(self, model_path, save_result=False, **kwargs):
340
+ super().__init__(model_path, save_result, **kwargs)
341
+ self._configure_isapn_interpreter()
342
+
343
+
344
+ class ISAPNAnalyzer_v1p3(ISAPNAnalyzer_v2):
345
+ """Deprecated compatibility name for :class:`ISAPNAnalyzer_v2`."""
346
+
347
+ def __init__(self, *args, **kwargs):
348
+ warnings.warn(
349
+ "ISAPNAnalyzer_v1p3 is deprecated; use ISAPNAnalyzer_v2 or "
350
+ "Analyzer(...) instead.",
351
+ FutureWarning,
352
+ stacklevel=2,
353
+ )
354
+ super().__init__(*args, **kwargs)
355
+
356
+
357
+ ISATPNAnalyzer = ISAPNAnalyzer
358
+ ISATPNAnalyzer_v2 = ISAPNAnalyzer_v2
359
+
360
+ __all__ = [
361
+ "ISAPNAnalyzer",
362
+ "ISAPNAnalyzer_v2",
363
+ "ISAPNAnalyzer_v1p3",
364
+ "ISATPNAnalyzer",
365
+ "ISATPNAnalyzer_v2",
366
+ ]
@@ -0,0 +1,117 @@
1
+
2
+ import numpy as np
3
+ import torch
4
+ import io
5
+ import rdkit.Chem as Chem
6
+
7
+
8
+ from .ISAAnalyzer import ISAAnalyzer
9
+
10
+ class ISAwSAnalyzer(ISAAnalyzer):
11
+ def check_score_by_group(self):
12
+ if getattr(self.dm.gg,'sculptor',None) is None:
13
+ self.is_score_by_group = True
14
+ else:
15
+ temp_smiles='CC(C)(C)OC(=O)C1=CC=CC=C1C(=O)O'
16
+ temp_solvent = "CS(=O)C"
17
+
18
+ test_loader,_ = self.prepare_temp_data([temp_smiles],solvents=[temp_solvent])
19
+ temp_score = self.tm.get_score(self.nm, test_loader)
20
+
21
+ if 'positive' in temp_score:
22
+ temp_score['positive'] = temp_score['positive'].detach().cpu().numpy()
23
+ if temp_score['positive'].shape[0] == len(self.get_fragment(temp_smiles)):
24
+ self.is_score_by_group = True
25
+ elif temp_score['positive'].shape[0] == Chem.MolFromSmiles(temp_smiles).GetNumAtoms():
26
+ self.is_score_by_group = False
27
+ else:
28
+ raise ValueError('The length of the score and the number of fragments do not match.')
29
+ else:
30
+ raise ValueError('The score is not calculated properly.')
31
+
32
+
33
+ # get the attention score of the given smiles
34
+ def get_score(self, smiles, solvent):
35
+ "get the attention score of the given smiles by its subgroups"
36
+ test_loader,_ = self.prepare_temp_data([smiles],[solvent])
37
+ result = self.tm.get_score(self.nm, test_loader)
38
+ for k in result.keys():
39
+ if type(result[k]) is torch.Tensor:
40
+ result[k] = result[k].detach().cpu().numpy()
41
+
42
+ result['positive'] = self.get_group_score(smiles, result['positive'])
43
+ if 'negative' in result:
44
+ result['negative'] = self.get_group_score(smiles, result['negative'])
45
+ self.save_data(smiles, result)
46
+ return result
47
+
48
+ def _get_feature_single_legacy(self, smiles, solvent):
49
+ """Legacy single-input solvent feature helper."""
50
+ """
51
+ This function gets the feature of the given smiles and solvent.
52
+
53
+ Args:
54
+ smiles (str): The smiles of the molecule.
55
+ solvent (str): The solvent of the molecule.
56
+
57
+ Returns:
58
+ dict: The feature of the given smiles and solvent.
59
+ """
60
+ test_loader, _ = self.prepare_temp_data([smiles], [solvent])
61
+ return self.tm.get_feature(self.nm, test_loader)
62
+
63
+ def get_feature(self, smiles_list, solvent_list, feature=None):
64
+ results = {}
65
+
66
+ if len(smiles_list) > 0:
67
+ valid_smiles, new_results = self._get_all_features(smiles_list, solvent_list, feature=feature)
68
+ for smiles, result in zip(valid_smiles, new_results):
69
+ results[smiles] = result
70
+ return results
71
+
72
+
73
+ def _get_all_features(self, smiles_list, solvent_list, feature=None):
74
+ temp_loader,valid_smiles = self.prepare_temp_data(smiles_list, solvent_list)
75
+ features = self.tm.get_feature(self.nm, temp_loader)
76
+ features = {k:features[k].detach().cpu().numpy() for k in features.keys()}
77
+ results = []
78
+ count=0
79
+ for i, smiles in enumerate(valid_smiles['compound']):
80
+ result = {}
81
+ if feature is not None:
82
+ result[feature] = features[feature][i]
83
+ else:
84
+ frag = self.get_fragment(smiles)
85
+ result['feature_P'] = features['feature_P'][count:count+len(frag)]
86
+ if 'feature_N' in features:
87
+ result['feature_N'] = features['feature_N'][count:count+len(frag)]
88
+ count+=len(frag)
89
+ results.append(result)
90
+ self.save_data(smiles, result)
91
+ return valid_smiles['compound'], results
92
+
93
+
94
+
95
+ def plot_score(self, smiles, solvent ,**kwargs):
96
+ """
97
+ This function plots the attention score of the given smiles by its subgroups.
98
+
99
+ Args:
100
+ smiles (str): The smiles of the molecule.
101
+ atom_with_index (bool): Whether to show the atom index or not.
102
+ score_scaler (function): The function to scale the score. Default is lambda x:x.
103
+ ticks (list): The ticks of the colorbar. Default is [0,0.25,0.5,0.75,1].
104
+ rot (int): The rotation of the molecule. Default is 0.
105
+ locate (str): The location of the subplots. Default is 'right'. It can be 'right' or 'bottom'.
106
+ figsize (float): The size of the figure. Default is 1.
107
+ only_total (bool): Whether to plot only the total score or plot PAS and NAS as well. Default is False.
108
+ with_colorbar (bool): Whether to show the colorbar or not. Default is True.
109
+
110
+ Returns:
111
+ dict: The attention score of the given smiles.
112
+ Each value in the dictionary is the attention score of corresponding atom with the same index.
113
+
114
+ """
115
+ score = self.get_score(smiles, solvent)
116
+ return self._plot_score(smiles, score, **kwargs)
117
+