PFASGroups 3.2.2__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.
- HalogenGroups/__init__.py +246 -0
- PFASGroups/ComponentsSolverModel.py +977 -0
- PFASGroups/HalogenGroupModel.py +810 -0
- PFASGroups/PFASDefinitionModel.py +393 -0
- PFASGroups/PFASEmbeddings.py +3315 -0
- PFASGroups/__init__.py +21 -0
- PFASGroups/cli.py +618 -0
- PFASGroups/core.py +415 -0
- PFASGroups/data/Halogen_groups_smarts.json +9024 -0
- PFASGroups/data/PFAS_definitions_smarts.json +170 -0
- PFASGroups/data/component_smarts.json +4 -0
- PFASGroups/data/component_smarts_halogens.json +142 -0
- PFASGroups/data/diatomic_bonds_dict.json +12802 -0
- PFASGroups/draw_mols.py +374 -0
- PFASGroups/embeddings.py +150 -0
- PFASGroups/fragmentation.py +548 -0
- PFASGroups/generate_homologues.py +256 -0
- PFASGroups/generate_mol.py +656 -0
- PFASGroups/generate_paper_figures.py +266 -0
- PFASGroups/getter.py +111 -0
- PFASGroups/homologue_series.py +473 -0
- PFASGroups/parser.py +942 -0
- PFASGroups/prioritise.py +439 -0
- pfasgroups-3.2.2.dist-info/METADATA +724 -0
- pfasgroups-3.2.2.dist-info/RECORD +28 -0
- pfasgroups-3.2.2.dist-info/WHEEL +5 -0
- pfasgroups-3.2.2.dist-info/entry_points.txt +3 -0
- pfasgroups-3.2.2.dist-info/top_level.txt +2 -0
PFASGroups/prioritise.py
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PFAS Molecule Prioritization Module
|
|
3
|
+
====================================
|
|
4
|
+
|
|
5
|
+
This module provides functions to prioritize PFAS molecules based on:
|
|
6
|
+
1. Distributional similarity to a reference list (using fingerprint KL divergence)
|
|
7
|
+
2. Fluorinated component characteristics (total fluorine content and component size distribution)
|
|
8
|
+
|
|
9
|
+
Author: HalogenGroups
|
|
10
|
+
Version: 2.2.4
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Union, List, Tuple, Optional, Dict
|
|
14
|
+
import numpy as np
|
|
15
|
+
from rdkit import Chem
|
|
16
|
+
|
|
17
|
+
from .parser import parse_smiles, parse_mols
|
|
18
|
+
from .PFASEmbeddings import PFASEmbeddingSet
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def prioritise_molecules(
|
|
22
|
+
molecules: Union[List[str], List[Chem.Mol], PFASEmbeddingSet],
|
|
23
|
+
reference: Optional[Union[List[str], List[Chem.Mol], PFASEmbeddingSet]] = None,
|
|
24
|
+
group_selection: str = 'all',
|
|
25
|
+
count_mode: str = 'max_component',
|
|
26
|
+
halogens: Union[str, List[str]] = 'F',
|
|
27
|
+
saturation: Optional[str] = None,
|
|
28
|
+
a: float = 1.0,
|
|
29
|
+
b: float = 1.0,
|
|
30
|
+
percentile: float = 90.0,
|
|
31
|
+
return_scores: bool = True,
|
|
32
|
+
ascending: bool = False,
|
|
33
|
+
progress: bool = False,
|
|
34
|
+
) -> Union[PFASEmbeddingSet, Tuple[PFASEmbeddingSet, np.ndarray]]:
|
|
35
|
+
"""
|
|
36
|
+
Prioritize PFAS molecules based on similarity to a reference or intrinsic properties.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
molecules : list of str, list of rdkit.Chem.Mol, or PFASEmbeddingSet
|
|
41
|
+
Molecules to prioritize. Can be:
|
|
42
|
+
- List of SMILES strings
|
|
43
|
+
- List of RDKit molecule objects
|
|
44
|
+
- PFASEmbeddingSet object (pre-computed results)
|
|
45
|
+
|
|
46
|
+
reference : list of str, list of rdkit.Chem.Mol, PFASEmbeddingSet, or None
|
|
47
|
+
Reference molecules for similarity comparison. If provided, molecules are
|
|
48
|
+
prioritized by distributional similarity (lower KL divergence = higher priority).
|
|
49
|
+
If None, prioritization is based on intrinsic fluorinated component properties.
|
|
50
|
+
|
|
51
|
+
group_selection : str, default 'all'
|
|
52
|
+
PFAS group selection for fingerprint generation when using reference:
|
|
53
|
+
- 'all': All 115 groups (OECD + generic)
|
|
54
|
+
- 'oecd': OECD-defined groups (1-28)
|
|
55
|
+
- 'generic': Generic functional groups (29-115)
|
|
56
|
+
- 'telomers': Telomer-related groups
|
|
57
|
+
- 'generic+telomers': Combined selection
|
|
58
|
+
|
|
59
|
+
count_mode : str, default 'binary'
|
|
60
|
+
Fingerprint encoding mode when using reference:
|
|
61
|
+
- 'binary': 1 if present, 0 if absent
|
|
62
|
+
- 'count': Number of matches
|
|
63
|
+
- 'max_component': Maximum component size
|
|
64
|
+
|
|
65
|
+
halogens : str or list of str, default 'F'
|
|
66
|
+
Which halogen(s) to include when generating fingerprints for reference
|
|
67
|
+
comparison. Passed directly to ``PFASEmbeddingSet.to_fingerprint``.
|
|
68
|
+
|
|
69
|
+
saturation : str or None, default None
|
|
70
|
+
Saturation filter applied to component SMARTS when generating
|
|
71
|
+
fingerprints. ``None`` (default) includes both per- and
|
|
72
|
+
polyfluorinated / polyhalogenated components, which gives the broadest
|
|
73
|
+
coverage and avoids zero scores for candidates that only contain
|
|
74
|
+
polyfluorinated chains. Pass ``'per'`` or ``'poly'`` to restrict.
|
|
75
|
+
|
|
76
|
+
a : float, default 1.0
|
|
77
|
+
Weight for total fluorinated component size (sum of all component sizes).
|
|
78
|
+
Used when reference is None. Higher values prioritize molecules with more
|
|
79
|
+
total fluorination.
|
|
80
|
+
|
|
81
|
+
b : float, default 1.0
|
|
82
|
+
Weight for component size percentile. Used when reference is None.
|
|
83
|
+
Higher values prioritize molecules with larger individual fluorinated components.
|
|
84
|
+
|
|
85
|
+
percentile : float, default 90.0
|
|
86
|
+
Percentile value (0-100) for component size distribution. Used when reference
|
|
87
|
+
is None. Common values:
|
|
88
|
+
- 90.0: Focus on largest 10% of components
|
|
89
|
+
- 75.0: Focus on largest 25% of components
|
|
90
|
+
- 50.0: Median component size
|
|
91
|
+
|
|
92
|
+
return_scores : bool, default True
|
|
93
|
+
If True, returns tuple of (prioritized_results, scores).
|
|
94
|
+
If False, returns only prioritized_results.
|
|
95
|
+
|
|
96
|
+
ascending : bool, default False
|
|
97
|
+
Sort order. If False (default), highest priority first.
|
|
98
|
+
If True, lowest priority first.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
PFASEmbeddingSet or tuple
|
|
103
|
+
If return_scores=True: (prioritized_results, scores)
|
|
104
|
+
If return_scores=False: prioritized_results only
|
|
105
|
+
|
|
106
|
+
prioritized_results : PFASEmbeddingSet
|
|
107
|
+
Molecules sorted by priority
|
|
108
|
+
scores : np.ndarray
|
|
109
|
+
Priority scores for each molecule
|
|
110
|
+
|
|
111
|
+
Examples
|
|
112
|
+
--------
|
|
113
|
+
# Priority by similarity to reference list
|
|
114
|
+
>>> from PFASGroups import prioritise_molecules
|
|
115
|
+
>>> inventory = ["FC(F)(F)C(F)(F)C(=O)O", "FC(F)(F)C(F)(F)C(F)(F)C(=O)O"]
|
|
116
|
+
>>> reference = ["FC(F)(F)C(F)(F)C(=O)O"] # Known priority compounds
|
|
117
|
+
>>> results, scores = prioritise_molecules(inventory, reference=reference)
|
|
118
|
+
>>> print(f"Most similar: {results[0]['smiles']}")
|
|
119
|
+
|
|
120
|
+
# Priority by fluorination characteristics
|
|
121
|
+
>>> results, scores = prioritise_molecules(
|
|
122
|
+
... inventory,
|
|
123
|
+
... a=1.0, # Weight for total fluorination
|
|
124
|
+
... b=2.0, # Weight for largest components
|
|
125
|
+
... percentile=90
|
|
126
|
+
... )
|
|
127
|
+
>>> print(f"Highest priority: {results[0]['smiles']}")
|
|
128
|
+
|
|
129
|
+
# Emphasize total fluorine content
|
|
130
|
+
>>> results = prioritise_molecules(inventory, a=2.0, b=0.5, return_scores=False)
|
|
131
|
+
|
|
132
|
+
# Focus on molecules with longest chains
|
|
133
|
+
>>> results = prioritise_molecules(inventory, a=0.5, b=2.0, percentile=95)
|
|
134
|
+
|
|
135
|
+
Notes
|
|
136
|
+
-----
|
|
137
|
+
**Reference-based prioritization:**
|
|
138
|
+
|
|
139
|
+
Uses cosine similarity between each candidate's fingerprint vector and the
|
|
140
|
+
mean fingerprint of the reference set:
|
|
141
|
+
|
|
142
|
+
score_i = (fp_i · mean_ref) / (||fp_i|| × ||mean_ref||)
|
|
143
|
+
|
|
144
|
+
- Higher cosine similarity = more similar group profile to reference = higher priority
|
|
145
|
+
- Molecules that activate the same PFAS groups as the reference rank highest
|
|
146
|
+
- Molecules with no group matches receive score 0
|
|
147
|
+
|
|
148
|
+
**Intrinsic prioritization (no reference):**
|
|
149
|
+
|
|
150
|
+
Score = a × Σ(component_sizes) + b × percentile(component_sizes, p)
|
|
151
|
+
|
|
152
|
+
Where:
|
|
153
|
+
- Σ(component_sizes): Total number of fluorinated carbons across all components
|
|
154
|
+
- percentile(component_sizes, p): Size of fluorinated components at pth percentile
|
|
155
|
+
|
|
156
|
+
This approach prioritizes molecules based on:
|
|
157
|
+
- Total fluorination burden (a parameter)
|
|
158
|
+
- Presence of long perfluorinated chains (b and percentile parameters)
|
|
159
|
+
|
|
160
|
+
**Tuning guidelines:**
|
|
161
|
+
|
|
162
|
+
For environmental persistence concerns:
|
|
163
|
+
- High b, high percentile (e.g., b=2.0, p=90): Long-chain compounds
|
|
164
|
+
|
|
165
|
+
For bioaccumulation potential:
|
|
166
|
+
- Balanced a and b (e.g., a=1.0, b=1.0): Both total and chain length
|
|
167
|
+
|
|
168
|
+
For screening priority:
|
|
169
|
+
- High a, moderate b (e.g., a=2.0, b=1.0, p=75): Total fluorine load
|
|
170
|
+
|
|
171
|
+
See Also
|
|
172
|
+
--------
|
|
173
|
+
PFASEmbeddingSet.to_array : Convert results to fingerprints
|
|
174
|
+
PFASEmbedding.compare_kld : Compare embedding distributions
|
|
175
|
+
"""
|
|
176
|
+
# Step 1: Convert input to PFASEmbeddingSet if needed
|
|
177
|
+
if isinstance(molecules, PFASEmbeddingSet):
|
|
178
|
+
results = molecules
|
|
179
|
+
elif isinstance(molecules, list):
|
|
180
|
+
if len(molecules) == 0:
|
|
181
|
+
raise ValueError("molecules list is empty")
|
|
182
|
+
|
|
183
|
+
# Check if list contains SMILES strings or Mol objects
|
|
184
|
+
if isinstance(molecules[0], str):
|
|
185
|
+
results = parse_smiles(molecules, progress=progress)
|
|
186
|
+
elif isinstance(molecules[0], Chem.Mol):
|
|
187
|
+
results = parse_mols(molecules, progress=progress)
|
|
188
|
+
else:
|
|
189
|
+
raise TypeError(
|
|
190
|
+
f"molecules must be list of SMILES strings or RDKit Mol objects, "
|
|
191
|
+
f"got {type(molecules[0])}"
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
raise TypeError(
|
|
195
|
+
f"molecules must be list of SMILES/Mol objects or PFASEmbeddingSet, "
|
|
196
|
+
f"got {type(molecules)}"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Step 2: Compute priority scores
|
|
200
|
+
if reference is not None:
|
|
201
|
+
# Reference-based: cosine similarity of embedding vectors
|
|
202
|
+
scores = _prioritise_by_reference(
|
|
203
|
+
results, reference, group_selection, count_mode, halogens, saturation,
|
|
204
|
+
progress=progress,
|
|
205
|
+
)
|
|
206
|
+
else:
|
|
207
|
+
# Intrinsic: fluorinated component characteristics
|
|
208
|
+
scores = _prioritise_by_components(results, a, b, percentile)
|
|
209
|
+
|
|
210
|
+
# Step 3: Sort by priority
|
|
211
|
+
sorted_indices = np.argsort(scores)
|
|
212
|
+
if not ascending:
|
|
213
|
+
sorted_indices = sorted_indices[::-1]
|
|
214
|
+
|
|
215
|
+
# Create prioritized results
|
|
216
|
+
prioritized_results = PFASEmbeddingSet([results[i] for i in sorted_indices])
|
|
217
|
+
|
|
218
|
+
if return_scores:
|
|
219
|
+
return prioritized_results, scores[sorted_indices]
|
|
220
|
+
else:
|
|
221
|
+
return prioritized_results
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _prioritise_by_reference(
|
|
225
|
+
results: PFASEmbeddingSet,
|
|
226
|
+
reference: Union[List[str], List[Chem.Mol], PFASEmbeddingSet],
|
|
227
|
+
group_selection: str,
|
|
228
|
+
count_mode: str,
|
|
229
|
+
halogens: Union[str, List[str]] = 'F',
|
|
230
|
+
saturation: Optional[str] = None,
|
|
231
|
+
progress: bool = False,
|
|
232
|
+
) -> np.ndarray:
|
|
233
|
+
"""Prioritize by cosine similarity to the mean embedding of a reference set.
|
|
234
|
+
|
|
235
|
+
Parameters
|
|
236
|
+
----------
|
|
237
|
+
results : PFASEmbeddingSet
|
|
238
|
+
Molecules to prioritize.
|
|
239
|
+
reference : list of str, list of rdkit.Chem.Mol, or PFASEmbeddingSet
|
|
240
|
+
Reference molecules.
|
|
241
|
+
group_selection : str
|
|
242
|
+
PFAS group selection ('all', 'oecd', 'generic', …).
|
|
243
|
+
count_mode : str
|
|
244
|
+
Count mode for the embedding ('binary', 'count', 'max_component', …).
|
|
245
|
+
halogens : str or list of str, default 'F'
|
|
246
|
+
Passed to parse_smiles / parse_mols for the reference.
|
|
247
|
+
saturation : str or None, default None
|
|
248
|
+
Passed to parse_smiles / parse_mols for the reference.
|
|
249
|
+
progress : bool, default False
|
|
250
|
+
|
|
251
|
+
Returns
|
|
252
|
+
-------
|
|
253
|
+
np.ndarray
|
|
254
|
+
Priority scores in [0, 1] (higher = more similar to reference).
|
|
255
|
+
"""
|
|
256
|
+
# Convert reference to PFASEmbeddingSet if needed
|
|
257
|
+
if isinstance(reference, PFASEmbeddingSet):
|
|
258
|
+
ref_results = reference
|
|
259
|
+
elif isinstance(reference, list):
|
|
260
|
+
if len(reference) == 0:
|
|
261
|
+
raise ValueError("reference list is empty")
|
|
262
|
+
if isinstance(reference[0], str):
|
|
263
|
+
ref_results = parse_smiles(reference, halogens=halogens,
|
|
264
|
+
saturation=saturation, progress=progress)
|
|
265
|
+
elif isinstance(reference[0], Chem.Mol):
|
|
266
|
+
ref_results = parse_mols(reference, halogens=halogens,
|
|
267
|
+
saturation=saturation, progress=progress)
|
|
268
|
+
else:
|
|
269
|
+
raise TypeError(
|
|
270
|
+
f"reference must be list of SMILES strings or RDKit Mol objects, "
|
|
271
|
+
f"got {type(reference[0])}"
|
|
272
|
+
)
|
|
273
|
+
else:
|
|
274
|
+
raise TypeError(
|
|
275
|
+
f"reference must be list of SMILES/Mol objects or PFASEmbeddingSet, "
|
|
276
|
+
f"got {type(reference)}"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Build mean reference vector: shape (n_cols,)
|
|
280
|
+
ref_mat = ref_results.to_array(
|
|
281
|
+
group_selection=group_selection,
|
|
282
|
+
component_metrics=[count_mode],
|
|
283
|
+
)
|
|
284
|
+
ref_vec = np.mean(np.asarray(ref_mat), axis=0).astype(float)
|
|
285
|
+
ref_norm = float(np.linalg.norm(ref_vec))
|
|
286
|
+
|
|
287
|
+
if ref_norm == 0.0:
|
|
288
|
+
import warnings
|
|
289
|
+
warnings.warn(
|
|
290
|
+
"Reference embedding is all-zero: no molecules in the reference "
|
|
291
|
+
"matched any group for the given group_selection / count_mode settings. "
|
|
292
|
+
"Returning zero scores.",
|
|
293
|
+
UserWarning,
|
|
294
|
+
stacklevel=3,
|
|
295
|
+
)
|
|
296
|
+
return np.zeros(len(results), dtype=float)
|
|
297
|
+
|
|
298
|
+
# Build candidate matrix: shape (n_candidates, n_cols)
|
|
299
|
+
cand_mat = np.asarray(results.to_array(
|
|
300
|
+
group_selection=group_selection,
|
|
301
|
+
component_metrics=[count_mode],
|
|
302
|
+
)).astype(float)
|
|
303
|
+
|
|
304
|
+
# Cosine similarity: score_i = (cand_i · ref_vec) / (||cand_i|| × ||ref_vec||)
|
|
305
|
+
dot_products = cand_mat @ ref_vec # (n_candidates,)
|
|
306
|
+
cand_norms = np.linalg.norm(cand_mat, axis=1) # (n_candidates,)
|
|
307
|
+
|
|
308
|
+
scores = np.zeros(len(dot_products), dtype=float)
|
|
309
|
+
nonzero = cand_norms > 0
|
|
310
|
+
scores[nonzero] = dot_products[nonzero] / (cand_norms[nonzero] * ref_norm)
|
|
311
|
+
|
|
312
|
+
return np.clip(scores, 0.0, 1.0)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _prioritise_by_components(
|
|
316
|
+
results: PFASEmbeddingSet,
|
|
317
|
+
a: float,
|
|
318
|
+
b: float,
|
|
319
|
+
percentile: float
|
|
320
|
+
) -> np.ndarray:
|
|
321
|
+
"""
|
|
322
|
+
Prioritize by fluorinated component characteristics.
|
|
323
|
+
|
|
324
|
+
Score = a × sum(component_sizes) + b × percentile(component_sizes, p)
|
|
325
|
+
|
|
326
|
+
Parameters
|
|
327
|
+
----------
|
|
328
|
+
results : PFASEmbeddingSet
|
|
329
|
+
Molecules to prioritize
|
|
330
|
+
a : float
|
|
331
|
+
Weight for total component size
|
|
332
|
+
b : float
|
|
333
|
+
Weight for component size percentile
|
|
334
|
+
percentile : float
|
|
335
|
+
Percentile value (0-100)
|
|
336
|
+
|
|
337
|
+
Returns
|
|
338
|
+
-------
|
|
339
|
+
np.ndarray
|
|
340
|
+
Priority scores (higher = higher priority)
|
|
341
|
+
"""
|
|
342
|
+
scores = []
|
|
343
|
+
|
|
344
|
+
for mol_result in results:
|
|
345
|
+
# Extract all fluorinated component sizes from all matched groups
|
|
346
|
+
all_component_sizes = []
|
|
347
|
+
|
|
348
|
+
for match in mol_result['matches']:
|
|
349
|
+
if match.get('type') == 'HalogenGroup' and 'components_sizes' in match:
|
|
350
|
+
component_sizes = match['components_sizes']
|
|
351
|
+
if isinstance(component_sizes, list) and len(component_sizes) > 0:
|
|
352
|
+
all_component_sizes.extend(component_sizes)
|
|
353
|
+
|
|
354
|
+
if len(all_component_sizes) == 0:
|
|
355
|
+
# No fluorinated components found
|
|
356
|
+
score = 0.0
|
|
357
|
+
else:
|
|
358
|
+
all_component_sizes = np.array(all_component_sizes)
|
|
359
|
+
|
|
360
|
+
# Compute score components
|
|
361
|
+
total_size = float(np.sum(all_component_sizes))
|
|
362
|
+
percentile_size = float(np.percentile(all_component_sizes, percentile))
|
|
363
|
+
|
|
364
|
+
# Combined score
|
|
365
|
+
score = a * total_size + b * percentile_size
|
|
366
|
+
|
|
367
|
+
scores.append(score)
|
|
368
|
+
|
|
369
|
+
return np.array(scores)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def get_priority_statistics(
|
|
373
|
+
results: PFASEmbeddingSet,
|
|
374
|
+
scores: np.ndarray,
|
|
375
|
+
top_n: int = 10
|
|
376
|
+
) -> Dict:
|
|
377
|
+
"""
|
|
378
|
+
Get statistics about prioritization results.
|
|
379
|
+
|
|
380
|
+
Parameters
|
|
381
|
+
----------
|
|
382
|
+
results : PFASEmbeddingSet
|
|
383
|
+
Prioritized molecules
|
|
384
|
+
scores : np.ndarray
|
|
385
|
+
Priority scores
|
|
386
|
+
top_n : int, default 10
|
|
387
|
+
Number of top molecules to analyze
|
|
388
|
+
|
|
389
|
+
Returns
|
|
390
|
+
-------
|
|
391
|
+
dict
|
|
392
|
+
Statistics including:
|
|
393
|
+
- 'n_molecules': Total number of molecules
|
|
394
|
+
- 'score_mean': Mean priority score
|
|
395
|
+
- 'score_std': Standard deviation of scores
|
|
396
|
+
- 'score_min': Minimum score
|
|
397
|
+
- 'score_max': Maximum score
|
|
398
|
+
- 'top_n_smiles': SMILES of top N molecules
|
|
399
|
+
- 'top_n_scores': Scores of top N molecules
|
|
400
|
+
- 'top_n_groups': Most common groups in top N molecules
|
|
401
|
+
|
|
402
|
+
Examples
|
|
403
|
+
--------
|
|
404
|
+
>>> results, scores = prioritise_molecules(smiles_list, reference=ref_list)
|
|
405
|
+
>>> stats = get_priority_statistics(results, scores, top_n=10)
|
|
406
|
+
>>> print(f"Mean score: {stats['score_mean']:.3f}")
|
|
407
|
+
>>> print(f"Top molecule: {stats['top_n_smiles'][0]}")
|
|
408
|
+
"""
|
|
409
|
+
stats = {
|
|
410
|
+
'n_molecules': len(results),
|
|
411
|
+
'score_mean': float(np.mean(scores)),
|
|
412
|
+
'score_std': float(np.std(scores)),
|
|
413
|
+
'score_min': float(np.min(scores)),
|
|
414
|
+
'score_max': float(np.max(scores)),
|
|
415
|
+
'score_median': float(np.median(scores)),
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
# Top N molecules
|
|
419
|
+
n = min(top_n, len(results))
|
|
420
|
+
stats['top_n_smiles'] = [results[i]['smiles'] for i in range(n)]
|
|
421
|
+
stats['top_n_scores'] = scores[:n].tolist()
|
|
422
|
+
|
|
423
|
+
# Most common groups in top N
|
|
424
|
+
group_counts = {}
|
|
425
|
+
for i in range(n):
|
|
426
|
+
for match in results[i]['matches']:
|
|
427
|
+
if match.get('type') == 'HalogenGroup':
|
|
428
|
+
group_name = match.get('group_name', 'Unknown')
|
|
429
|
+
group_counts[group_name] = group_counts.get(group_name, 0) + 1
|
|
430
|
+
|
|
431
|
+
# Sort by frequency
|
|
432
|
+
sorted_groups = sorted(group_counts.items(), key=lambda x: x[1], reverse=True)
|
|
433
|
+
stats['top_n_groups'] = sorted_groups[:10] # Top 10 most common groups
|
|
434
|
+
|
|
435
|
+
return stats
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
# Alias for American spelling
|
|
439
|
+
prioritize_molecules = prioritise_molecules
|