PFASGroups 3.4.2__tar.gz → 3.4.4__tar.gz
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.
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/PFASDefinitionModel.py +114 -42
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/data/PFAS_definitions_smarts.json +4 -4
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/parser.py +30 -8
- {pfasgroups-3.4.2 → pfasgroups-3.4.4/PFASGroups.egg-info}/PKG-INFO +5 -1
- {pfasgroups-3.4.2/PFASGroups.egg-info → pfasgroups-3.4.4}/PKG-INFO +5 -1
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/README.md +4 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/workers.py +7 -2
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/pyproject.toml +1 -1
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/setup.cfg +1 -1
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/HalogenGroups/__init__.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/ComponentsSolverModel.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/HalogenGroupModel.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/PFASEmbeddings.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/__init__.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/cli.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/core.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/data/Halogen_groups_smarts.json +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/data/component_smarts.json +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/data/component_smarts_halogens.json +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/data/diatomic_bonds_dict.json +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/draw_mols.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/embeddings.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/fragmentation.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/generate_homologues.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/generate_mol.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/generate_paper_figures.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/getter.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/group_features.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/homologue_series.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups/prioritise.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups.egg-info/SOURCES.txt +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups.egg-info/dependency_links.txt +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups.egg-info/entry_points.txt +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups.egg-info/requires.txt +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/PFASGroups.egg-info/top_level.txt +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/__init__.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/__main__.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/app.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/data/help.html +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/style.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/__init__.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/chemspace_tab.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/classify_tab.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/definition_tester_tab.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/help_tab.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/modelling_tab.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/prioritise_tab.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/tabs/results_tab.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/utils/__init__.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/utils/chemspace.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/utils/export_dialog.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/utils/fingerprints.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/utils/io_readers.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/utils/modelling.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/gui/utils/mol_renderer.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_bde_resistance.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_component_fractions.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_component_ratios.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_comprehensive_metrics.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_database_integration.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_definition_comparison.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_fingerprints_vs_txppfas.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_fraction_analysis.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_generate_homologues.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_halogen_groups_smarts.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_linker_smarts.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_metrics.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_metrics_detailed.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_n_spacer_ring_size.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_parser_wildcard.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_pfasstructv5.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_prioritise.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_readme_and_docs_examples.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_results_fingerprint.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_results_model.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_results_sql.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_run_groups_definitions_tests.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_smarts_atom_count.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_smarts_matching.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_smarts_precompute.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_summary.py +0 -0
- {pfasgroups-3.4.2 → pfasgroups-3.4.4}/tests/test_telomer_validation.py +0 -0
|
@@ -64,21 +64,21 @@ class PFASDefinition:
|
|
|
64
64
|
def __str__(self):
|
|
65
65
|
return self.name
|
|
66
66
|
|
|
67
|
-
def applies_to_molecule(self,
|
|
68
|
-
mol_or_smiles: Union[Chem.Mol, str],
|
|
67
|
+
def applies_to_molecule(self,
|
|
68
|
+
mol_or_smiles: Union[Chem.Mol, str],
|
|
69
69
|
formula: Optional[Dict[str, int]] = None,
|
|
70
70
|
**kwargs) -> bool:
|
|
71
71
|
"""Check if this PFAS definition applies to a given molecule.
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
This method evaluates whether a molecule meets the structural and/or compositional
|
|
74
74
|
criteria defined by this PFASDefinition. The evaluation logic depends on the
|
|
75
75
|
requireBoth flag:
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
- If requireBoth=False (default): Returns True if EITHER SMARTS matches OR
|
|
78
78
|
fluorine ratio is met (logical OR)
|
|
79
79
|
- If requireBoth=True: Returns True only if BOTH SMARTS matches AND fluorine
|
|
80
80
|
ratio are met (logical AND)
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
Parameters
|
|
83
83
|
----------
|
|
84
84
|
mol_or_smiles : Union[Chem.Mol, str]
|
|
@@ -88,16 +88,16 @@ class PFASDefinition:
|
|
|
88
88
|
If None, will be computed from the molecule.
|
|
89
89
|
**kwargs : dict
|
|
90
90
|
Additional parameters:
|
|
91
|
-
|
|
91
|
+
|
|
92
92
|
- include_hydrogen (bool): Whether to include H in fluorine ratio calculation.
|
|
93
93
|
Defaults to self.includeHydrogen
|
|
94
94
|
- require_both (bool): Override the instance's requireBoth setting
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
Returns
|
|
97
97
|
-------
|
|
98
98
|
bool
|
|
99
99
|
True if the molecule meets the definition criteria, False otherwise
|
|
100
|
-
|
|
100
|
+
|
|
101
101
|
Examples
|
|
102
102
|
--------
|
|
103
103
|
>>> pfas_def = PFASDefinition(
|
|
@@ -108,45 +108,101 @@ class PFASDefinition:
|
|
|
108
108
|
True
|
|
109
109
|
>>> pfas_def.applies_to_molecule("CCCCCC") # No fluorine
|
|
110
110
|
False
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
Notes
|
|
113
113
|
-----
|
|
114
114
|
- SMARTS patterns are checked using substructure matching (HasSubstructMatch)
|
|
115
|
-
- Fluorine ratio is
|
|
115
|
+
- Fluorine ratio is mass-weighted: F mass / total mass (see `match_details`
|
|
116
|
+
for the breakdown of which criterion -- SMARTS, ratio, or both -- was met,
|
|
117
|
+
and the actual computed ratio value)
|
|
116
118
|
- Invalid SMILES strings return False
|
|
117
119
|
"""
|
|
120
|
+
return self.match_details(mol_or_smiles, formula=formula, **kwargs)['applies']
|
|
121
|
+
|
|
122
|
+
def match_details(self,
|
|
123
|
+
mol_or_smiles: Union[Chem.Mol, str],
|
|
124
|
+
formula: Optional[Dict[str, int]] = None,
|
|
125
|
+
**kwargs) -> Dict:
|
|
126
|
+
"""Evaluate a molecule against this definition and report each criterion separately.
|
|
127
|
+
|
|
128
|
+
Unlike `applies_to_molecule`, which collapses everything to a single bool,
|
|
129
|
+
this returns the SMARTS-match result, the fluorine-ratio result, and the
|
|
130
|
+
actual computed ratio value individually -- useful for explaining *why* a
|
|
131
|
+
molecule matched (e.g. distinguishing a genuine structural PFAS from one
|
|
132
|
+
that only clears a fluorine-content threshold).
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
mol_or_smiles : Union[Chem.Mol, str]
|
|
137
|
+
Input molecule as RDKit Mol object or SMILES string
|
|
138
|
+
formula : Optional[Dict[str, int]], default=None
|
|
139
|
+
Pre-computed molecular formula as {element: count} dictionary.
|
|
140
|
+
If None, will be computed from the molecule.
|
|
141
|
+
**kwargs : dict
|
|
142
|
+
Same as `applies_to_molecule` (`include_hydrogen`, `require_both`).
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
dict
|
|
147
|
+
- 'valid' (bool): False if the input SMILES could not be parsed
|
|
148
|
+
- 'smarts_match' (bool): whether any SMARTS pattern matched
|
|
149
|
+
- 'fluorine_ratio' (float or None): the computed ratio, or None if this
|
|
150
|
+
definition has no fluorineRatio threshold
|
|
151
|
+
- 'fluorine_ratio_threshold' (float or None): this definition's threshold
|
|
152
|
+
- 'ratio_match' (bool): whether the ratio met the threshold (True if no
|
|
153
|
+
threshold is defined, matching `applies_to_molecule`'s OR semantics)
|
|
154
|
+
- 'applies' (bool): the same overall result `applies_to_molecule` returns
|
|
155
|
+
"""
|
|
118
156
|
# Convert SMILES to Mol if needed
|
|
119
157
|
if isinstance(mol_or_smiles, str):
|
|
120
158
|
mol = Chem.MolFromSmiles(mol_or_smiles)
|
|
121
159
|
if mol is None:
|
|
122
|
-
return
|
|
160
|
+
return {
|
|
161
|
+
'valid': False,
|
|
162
|
+
'smarts_match': False,
|
|
163
|
+
'fluorine_ratio': None,
|
|
164
|
+
'fluorine_ratio_threshold': self.fluorineRatio,
|
|
165
|
+
'ratio_match': False,
|
|
166
|
+
'applies': False,
|
|
167
|
+
}
|
|
123
168
|
else:
|
|
124
169
|
mol = mol_or_smiles
|
|
125
|
-
|
|
170
|
+
|
|
126
171
|
# Check SMARTS matches
|
|
127
172
|
smarts_match = False
|
|
128
173
|
for pattern in self.smarts_patterns:
|
|
129
174
|
if mol.HasSubstructMatch(pattern):
|
|
130
175
|
smarts_match = True
|
|
131
176
|
break
|
|
132
|
-
|
|
177
|
+
|
|
133
178
|
# Check fluorine ratio if defined
|
|
134
179
|
ratio_match = True # Default to True if no ratio requirement
|
|
180
|
+
fluorine_ratio_value = None
|
|
135
181
|
if self.fluorineRatio is not None:
|
|
136
182
|
if formula is None:
|
|
137
183
|
formula = self._compute_formula(mol, kwargs.get("include_hydrogen", self.includeHydrogen))
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
184
|
+
|
|
185
|
+
fluorine_ratio_value = self._compute_fluorine_ratio(formula, kwargs.get("include_hydrogen", self.includeHydrogen))
|
|
186
|
+
ratio_match = fluorine_ratio_value >= self.fluorineRatio
|
|
187
|
+
|
|
141
188
|
# Apply logic based on require_both
|
|
142
189
|
if kwargs.get("require_both", self.requireBoth):
|
|
143
|
-
|
|
144
|
-
|
|
190
|
+
applies = smarts_match and ratio_match
|
|
191
|
+
elif self.fluorineRatio is None:
|
|
145
192
|
# If no fluorine ratio is defined, only check SMARTS
|
|
146
|
-
|
|
147
|
-
|
|
193
|
+
applies = smarts_match
|
|
194
|
+
else:
|
|
148
195
|
# Otherwise, SMARTS OR ratio
|
|
149
|
-
|
|
196
|
+
applies = smarts_match or ratio_match
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
'valid': True,
|
|
200
|
+
'smarts_match': smarts_match,
|
|
201
|
+
'fluorine_ratio': round(fluorine_ratio_value, 4) if fluorine_ratio_value is not None else None,
|
|
202
|
+
'fluorine_ratio_threshold': self.fluorineRatio,
|
|
203
|
+
'ratio_match': ratio_match,
|
|
204
|
+
'applies': applies,
|
|
205
|
+
}
|
|
150
206
|
|
|
151
207
|
def _compute_formula(self, mol: Chem.Mol, include_hydrogen: bool) -> Dict[str, int]:
|
|
152
208
|
"""Compute molecular formula as element count dictionary.
|
|
@@ -175,38 +231,54 @@ class PFASDefinition:
|
|
|
175
231
|
|
|
176
232
|
return formula
|
|
177
233
|
|
|
178
|
-
def
|
|
179
|
-
"""
|
|
180
|
-
|
|
234
|
+
def _compute_fluorine_ratio(self, formula: Dict[str, int], include_hydrogen: bool) -> float:
|
|
235
|
+
"""Compute the mass-weighted fluorine ratio of a molecular formula.
|
|
236
|
+
|
|
237
|
+
Mass-weighted (F mass / total mass):
|
|
238
|
+
`fluorine_ratio = F_mass / (molecular_weight - H_mass)`.
|
|
239
|
+
|
|
181
240
|
Parameters
|
|
182
241
|
----------
|
|
183
242
|
formula : Dict[str, int]
|
|
184
243
|
Molecular formula as {element: count} dictionary
|
|
185
244
|
include_hydrogen : bool
|
|
186
|
-
If True, includes hydrogen
|
|
187
|
-
If False, excludes hydrogen from total (heavy atoms only)
|
|
188
|
-
|
|
245
|
+
If True, includes hydrogen mass in the total mass.
|
|
246
|
+
If False, excludes hydrogen mass from total (heavy atoms only) --
|
|
247
|
+
this is the "heavy atoms only" mode PFASSTRUCTv5 uses.
|
|
248
|
+
|
|
189
249
|
Returns
|
|
190
250
|
-------
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
Notes
|
|
195
|
-
-----
|
|
196
|
-
- Returns False if total_atoms is 0
|
|
197
|
-
- Formula with no fluorine (F_count=0) will fail unless fluorineRatio=0
|
|
251
|
+
float
|
|
252
|
+
F_mass / total_mass, or 0.0 if total_mass is 0
|
|
198
253
|
"""
|
|
254
|
+
from rdkit.Chem import GetPeriodicTable
|
|
255
|
+
pt = GetPeriodicTable()
|
|
256
|
+
|
|
199
257
|
f_count = formula.get('F', 0)
|
|
200
|
-
|
|
258
|
+
f_mass = f_count * pt.GetAtomicWeight('F')
|
|
259
|
+
|
|
201
260
|
if include_hydrogen:
|
|
202
|
-
|
|
261
|
+
total_mass = sum(count * pt.GetAtomicWeight(sym) for sym, count in formula.items())
|
|
203
262
|
else:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if
|
|
207
|
-
return
|
|
208
|
-
|
|
209
|
-
|
|
263
|
+
total_mass = sum(count * pt.GetAtomicWeight(sym) for sym, count in formula.items() if sym != 'H')
|
|
264
|
+
|
|
265
|
+
if total_mass == 0:
|
|
266
|
+
return 0.0
|
|
267
|
+
|
|
268
|
+
return f_mass / total_mass
|
|
269
|
+
|
|
270
|
+
def _check_fluorine_ratio(self, formula: Dict[str, int], include_hydrogen: bool) -> bool:
|
|
271
|
+
"""Check if the fluorine ratio in a molecular formula meets the threshold.
|
|
272
|
+
|
|
273
|
+
Thin boolean wrapper around `_compute_fluorine_ratio`, kept for backward
|
|
274
|
+
compatibility with any external callers of this (nominally private) method.
|
|
275
|
+
|
|
276
|
+
Returns
|
|
277
|
+
-------
|
|
278
|
+
bool
|
|
279
|
+
True if (F_mass / total_mass) >= self.fluorineRatio, False otherwise
|
|
280
|
+
"""
|
|
281
|
+
ratio = self._compute_fluorine_ratio(formula, include_hydrogen)
|
|
210
282
|
return ratio >= self.fluorineRatio
|
|
211
283
|
|
|
212
284
|
def test(self, test_data=None):
|
|
@@ -134,11 +134,13 @@
|
|
|
134
134
|
],
|
|
135
135
|
"fluorineRatio": 0.3,
|
|
136
136
|
"includeHydrogen": false,
|
|
137
|
-
"description": "Custom structural patterns or F ratio
|
|
137
|
+
"description": "Custom structural patterns or F ratio >= 0.3, mass-weighted (F mass / heavy-atom mass).",
|
|
138
138
|
"test": {
|
|
139
139
|
"category": "definition",
|
|
140
140
|
"examples": {
|
|
141
141
|
"positives": [
|
|
142
|
+
"C1=COC(=C1)C=C(S(=O)(=O)C(F)(F)F)S(=O)(=O)C(F)(F)F",
|
|
143
|
+
"COC(=C(F)Cl)F",
|
|
142
144
|
"C(=O)(C(F)(F)Cl)C(F)(F)Cl",
|
|
143
145
|
"COC(C(OC(C(=O)C(F)(F)F)(F)F)F)(F)F",
|
|
144
146
|
"C(C(=O)CC(C(=O)C(F)(F)F)(F)F)C(C(=O)C(F)(F)F)(F)F",
|
|
@@ -160,9 +162,7 @@
|
|
|
160
162
|
],
|
|
161
163
|
"negatives": [
|
|
162
164
|
"CCCCC(CC(C(F)(F)Br)(F)Cl)Br",
|
|
163
|
-
"C(C(OC(Cl)(Cl)Cl)(F)F)(F)Cl"
|
|
164
|
-
"C1=COC(=C1)C=C(S(=O)(=O)C(F)(F)F)S(=O)(=O)C(F)(F)F",
|
|
165
|
-
"COC(=C(F)Cl)F"
|
|
165
|
+
"C(C(OC(Cl)(Cl)Cl)(F)F)(F)Cl"
|
|
166
166
|
]
|
|
167
167
|
}
|
|
168
168
|
}
|
|
@@ -179,12 +179,21 @@ def parse_definitions_in_mol(mol, **kwargs):
|
|
|
179
179
|
pfas_definitions = kwargs.get('pfas_definitions')
|
|
180
180
|
for pdef in pfas_definitions:
|
|
181
181
|
matched = False
|
|
182
|
+
best_details = None
|
|
182
183
|
for mol in frags:
|
|
183
|
-
|
|
184
|
+
details = pdef.match_details(mol_or_smiles=mol, **kwargs)
|
|
185
|
+
# Keep the most informative details across fragments: prefer the
|
|
186
|
+
# fragment that actually matched, and among matches prefer the one
|
|
187
|
+
# with the higher fluorine ratio (most representative of the PFAS
|
|
188
|
+
# fragment rather than an unrelated counterion/fragment).
|
|
189
|
+
if details['applies'] is True:
|
|
184
190
|
matched = True
|
|
185
|
-
|
|
191
|
+
if best_details is None or (details['fluorine_ratio'] or 0) > (best_details['fluorine_ratio'] or 0):
|
|
192
|
+
best_details = details
|
|
193
|
+
elif best_details is None:
|
|
194
|
+
best_details = details
|
|
186
195
|
if matched is True:
|
|
187
|
-
definition_matches.append(pdef)
|
|
196
|
+
definition_matches.append((pdef, best_details))
|
|
188
197
|
return definition_matches
|
|
189
198
|
|
|
190
199
|
|
|
@@ -696,9 +705,7 @@ def setup_halogen_groups_database(
|
|
|
696
705
|
):
|
|
697
706
|
"""Set up halogen groups metadata tables in a database.
|
|
698
707
|
|
|
699
|
-
This function creates tables to store halogen group definitions and SMARTS patterns
|
|
700
|
-
similar to the load_pfas_groups function in zeropmdb.
|
|
701
|
-
|
|
708
|
+
This function creates tables to store halogen group definitions and SMARTS patterns.
|
|
702
709
|
Parameters
|
|
703
710
|
----------
|
|
704
711
|
conn : str or sqlalchemy.engine.Engine
|
|
@@ -873,6 +880,12 @@ def parse_mols(mols, output_format='list', include_PFAS_definitions=True,
|
|
|
873
880
|
- ``'id'``: integer definition ID
|
|
874
881
|
- ``'definition_name'``: definition name
|
|
875
882
|
- ``'halogen'``: ``'F'`` (definitions only match when F is enabled)
|
|
883
|
+
- ``'smarts_match'``: bool, whether any of the definition's SMARTS patterns matched
|
|
884
|
+
- ``'fluorine_ratio'``: float or None, the computed mass-weighted F ratio
|
|
885
|
+
(None if this definition has no fluorineRatio threshold)
|
|
886
|
+
- ``'fluorine_ratio_threshold'``: float or None, the definition's configured threshold
|
|
887
|
+
- ``'ratio_match'``: bool, whether the ratio met the threshold (True if no
|
|
888
|
+
threshold is defined)
|
|
876
889
|
"""
|
|
877
890
|
|
|
878
891
|
real_halogens, include_h_components, include_wildcards = _normalize_halogen_controls(halogens)
|
|
@@ -1130,7 +1143,12 @@ def parse_mols(mols, output_format='list', include_PFAS_definitions=True,
|
|
|
1130
1143
|
'id': definition.id,
|
|
1131
1144
|
'definition_name': definition.name,
|
|
1132
1145
|
'type':'PFASdefinition',
|
|
1133
|
-
'halogen': 'F'
|
|
1146
|
+
'halogen': 'F',
|
|
1147
|
+
'smarts_match': details['smarts_match'],
|
|
1148
|
+
'fluorine_ratio': details['fluorine_ratio'],
|
|
1149
|
+
'fluorine_ratio_threshold': details['fluorine_ratio_threshold'],
|
|
1150
|
+
'ratio_match': details['ratio_match'],
|
|
1151
|
+
} for definition, details in definitions])
|
|
1134
1152
|
# Convert results to list format (one entry per molecule)
|
|
1135
1153
|
results_list = [r for r in results.values()]
|
|
1136
1154
|
# Format output based on requested format
|
|
@@ -1154,7 +1172,11 @@ def parse_mols(mols, output_format='list', include_PFAS_definitions=True,
|
|
|
1154
1172
|
'smiles': entry['smiles'],
|
|
1155
1173
|
'match_id': match['match_id'],
|
|
1156
1174
|
'match_name': match['definition_name'],
|
|
1157
|
-
'match_type': match['type']
|
|
1175
|
+
'match_type': match['type'],
|
|
1176
|
+
'smarts_match': match.get('smarts_match'),
|
|
1177
|
+
'fluorine_ratio': match.get('fluorine_ratio'),
|
|
1178
|
+
'fluorine_ratio_threshold': match.get('fluorine_ratio_threshold'),
|
|
1179
|
+
'ratio_match': match.get('ratio_match'),
|
|
1158
1180
|
})
|
|
1159
1181
|
df = pd.DataFrame(rows)
|
|
1160
1182
|
return df.to_csv(index=False) if output_format == 'csv' else df
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PFASGroups
|
|
3
|
-
Version: 3.4.
|
|
3
|
+
Version: 3.4.4
|
|
4
4
|
Summary: A comprehensive cheminformatics package for automated detection, classification, and analysis of halogenated substances, with a focus on per- and polyfluoroalkyl substances (PFAS). Combines SMARTS pattern matching, molecular formula constraints, and graph-based pathfinding to identify 119 groups. Supports creations of embeddings with graph metrics for Machine Learning workflows.
|
|
5
5
|
Author: Luc T. Miaz
|
|
6
6
|
Author-email: "Luc T. Miaz" <luc@miaz.ch>
|
|
@@ -846,6 +846,10 @@ See [USER_GUIDE.md](USER_GUIDE.md) for comprehensive examples including:
|
|
|
846
846
|
- Integration with pandas and scikit-learn
|
|
847
847
|
|
|
848
848
|
## Summary of changes by version
|
|
849
|
+
- **Version 3.4.4**: Add fluorine ratio and structural match to returned results (for debugging or diplay).
|
|
850
|
+
|
|
851
|
+
- **Version 3.4.3**: Fixed ratio error, specific to PFASSTRUCTv5
|
|
852
|
+
|
|
849
853
|
- **Version 3.4.2**: Fixed ambiguity with OPPT 2023.
|
|
850
854
|
|
|
851
855
|
- **Version 3.4.1**: Added generic embedding for non-halogenated specific compounds which includes wildcard-components functional groups and halogen component size. Fixed H components and added cyclic and aryl H components.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PFASGroups
|
|
3
|
-
Version: 3.4.
|
|
3
|
+
Version: 3.4.4
|
|
4
4
|
Summary: A comprehensive cheminformatics package for automated detection, classification, and analysis of halogenated substances, with a focus on per- and polyfluoroalkyl substances (PFAS). Combines SMARTS pattern matching, molecular formula constraints, and graph-based pathfinding to identify 119 groups. Supports creations of embeddings with graph metrics for Machine Learning workflows.
|
|
5
5
|
Author: Luc T. Miaz
|
|
6
6
|
Author-email: "Luc T. Miaz" <luc@miaz.ch>
|
|
@@ -846,6 +846,10 @@ See [USER_GUIDE.md](USER_GUIDE.md) for comprehensive examples including:
|
|
|
846
846
|
- Integration with pandas and scikit-learn
|
|
847
847
|
|
|
848
848
|
## Summary of changes by version
|
|
849
|
+
- **Version 3.4.4**: Add fluorine ratio and structural match to returned results (for debugging or diplay).
|
|
850
|
+
|
|
851
|
+
- **Version 3.4.3**: Fixed ratio error, specific to PFASSTRUCTv5
|
|
852
|
+
|
|
849
853
|
- **Version 3.4.2**: Fixed ambiguity with OPPT 2023.
|
|
850
854
|
|
|
851
855
|
- **Version 3.4.1**: Added generic embedding for non-halogenated specific compounds which includes wildcard-components functional groups and halogen component size. Fixed H components and added cyclic and aryl H components.
|
|
@@ -798,6 +798,10 @@ See [USER_GUIDE.md](USER_GUIDE.md) for comprehensive examples including:
|
|
|
798
798
|
- Integration with pandas and scikit-learn
|
|
799
799
|
|
|
800
800
|
## Summary of changes by version
|
|
801
|
+
- **Version 3.4.4**: Add fluorine ratio and structural match to returned results (for debugging or diplay).
|
|
802
|
+
|
|
803
|
+
- **Version 3.4.3**: Fixed ratio error, specific to PFASSTRUCTv5
|
|
804
|
+
|
|
801
805
|
- **Version 3.4.2**: Fixed ambiguity with OPPT 2023.
|
|
802
806
|
|
|
803
807
|
- **Version 3.4.1**: Added generic embedding for non-halogenated specific compounds which includes wildcard-components functional groups and halogen component size. Fixed H components and added cyclic and aryl H components.
|
|
@@ -146,15 +146,20 @@ class DefinitionTestWorker(Worker):
|
|
|
146
146
|
self.progress.emit(50)
|
|
147
147
|
|
|
148
148
|
matched = parse_definitions_in_mol(mol)
|
|
149
|
-
|
|
149
|
+
matched_details = {pdef.id: details for pdef, details in matched}
|
|
150
150
|
|
|
151
151
|
results = []
|
|
152
152
|
for d in definitions:
|
|
153
|
+
details = matched_details.get(d.id)
|
|
153
154
|
results.append({
|
|
154
155
|
"id": d.id,
|
|
155
156
|
"name": d.name,
|
|
156
157
|
"description": d.description,
|
|
157
|
-
"passed": d.id in
|
|
158
|
+
"passed": d.id in matched_details,
|
|
159
|
+
"smarts_match": details["smarts_match"] if details else False,
|
|
160
|
+
"fluorine_ratio": details["fluorine_ratio"] if details else None,
|
|
161
|
+
"fluorine_ratio_threshold": d.fluorineRatio,
|
|
162
|
+
"ratio_match": details["ratio_match"] if details else False,
|
|
158
163
|
})
|
|
159
164
|
|
|
160
165
|
self.progress.emit(100)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "PFASGroups"
|
|
7
|
-
version = "3.4.
|
|
7
|
+
version = "3.4.4"
|
|
8
8
|
description = "A comprehensive cheminformatics package for automated detection, classification, and analysis of halogenated substances, with a focus on per- and polyfluoroalkyl substances (PFAS). Combines SMARTS pattern matching, molecular formula constraints, and graph-based pathfinding to identify 119 groups. Supports creations of embeddings with graph metrics for Machine Learning workflows."
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Luc T. Miaz", email = "luc@miaz.ch" }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[metadata]
|
|
2
2
|
name = PFASGroups
|
|
3
|
-
version = 3.4.
|
|
3
|
+
version = 3.4.4
|
|
4
4
|
description = A comprehensive cheminformatics package for automated detection, classification, and analysis of halogenated substances, with a focus on per- and polyfluoroalkyl substances (PFAS). Supports multi-halogen fingerprinting (F, Cl, Br, I) with per/poly saturation filtering and stackable fingerprint vectors.
|
|
5
5
|
long_description = file: README.md
|
|
6
6
|
long_description_content_type = text/markdown
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|