PFASGroups 3.4.0__tar.gz → 3.4.2__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.0 → pfasgroups-3.4.2}/PFASGroups/HalogenGroupModel.py +14 -1
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/__init__.py +10 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/data/PFAS_definitions_smarts.json +29 -6
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/data/component_smarts_halogens.json +17 -2
- pfasgroups-3.4.2/PFASGroups/group_features.py +393 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2/PFASGroups.egg-info}/PKG-INFO +43 -2
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups.egg-info/SOURCES.txt +1 -0
- {pfasgroups-3.4.0/PFASGroups.egg-info → pfasgroups-3.4.2}/PKG-INFO +43 -2
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/README.md +42 -1
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/data/help.html +37 -37
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/pyproject.toml +1 -1
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/setup.cfg +2 -2
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/HalogenGroups/__init__.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/ComponentsSolverModel.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/PFASDefinitionModel.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/PFASEmbeddings.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/cli.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/core.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/data/Halogen_groups_smarts.json +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/data/component_smarts.json +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/data/diatomic_bonds_dict.json +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/draw_mols.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/embeddings.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/fragmentation.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/generate_homologues.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/generate_mol.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/generate_paper_figures.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/getter.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/homologue_series.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/parser.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups/prioritise.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups.egg-info/dependency_links.txt +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups.egg-info/entry_points.txt +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups.egg-info/requires.txt +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/PFASGroups.egg-info/top_level.txt +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/__init__.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/__main__.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/app.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/style.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/tabs/__init__.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/tabs/chemspace_tab.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/tabs/classify_tab.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/tabs/definition_tester_tab.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/tabs/help_tab.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/tabs/modelling_tab.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/tabs/prioritise_tab.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/tabs/results_tab.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/utils/__init__.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/utils/chemspace.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/utils/export_dialog.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/utils/fingerprints.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/utils/io_readers.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/utils/modelling.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/utils/mol_renderer.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/gui/workers.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_bde_resistance.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_component_fractions.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_component_ratios.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_comprehensive_metrics.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_database_integration.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_definition_comparison.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_fingerprints_vs_txppfas.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_fraction_analysis.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_generate_homologues.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_halogen_groups_smarts.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_linker_smarts.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_metrics.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_metrics_detailed.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_n_spacer_ring_size.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_parser_wildcard.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_pfasstructv5.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_prioritise.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_readme_and_docs_examples.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_results_fingerprint.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_results_model.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_results_sql.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_run_groups_definitions_tests.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_smarts_atom_count.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_smarts_matching.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_smarts_precompute.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_summary.py +0 -0
- {pfasgroups-3.4.0 → pfasgroups-3.4.2}/tests/test_telomer_validation.py +0 -0
|
@@ -156,8 +156,15 @@ class HalogenGroup():
|
|
|
156
156
|
# prepare halogens (accepts list, str or None)
|
|
157
157
|
if isinstance(self.componentHalogens,list) and len(set(self.componentHalogens).intersection(['F','Cl','Br','I','H'])) == 0:
|
|
158
158
|
raise ValueError(f"Invalid componentHalogens for HalogenGroup '{self.name}' (ID: {self.id})")
|
|
159
|
+
_halogens_inferred = False
|
|
159
160
|
if self.componentHalogens is None:
|
|
160
|
-
|
|
161
|
+
# Derive from whichever halogens have component SMARTS defined in the
|
|
162
|
+
# available componentSmartss. Fall back to real halogens only (no H)
|
|
163
|
+
# when the data provides no guidance.
|
|
164
|
+
_hal_order = ['F', 'Cl', 'Br', 'I', 'H']
|
|
165
|
+
_inferred = [h for h in _hal_order if h in componentSmarts_dict]
|
|
166
|
+
self.componentHalogens = _inferred if _inferred else ['F', 'Cl', 'Br', 'I']
|
|
167
|
+
_halogens_inferred = True
|
|
161
168
|
elif isinstance(self.componentHalogens, str) and self.componentHalogens in ['F','Cl','Br','I','H']:
|
|
162
169
|
self.componentHalogens = [self.componentHalogens]
|
|
163
170
|
if self.componentSaturation is None:
|
|
@@ -172,11 +179,17 @@ class HalogenGroup():
|
|
|
172
179
|
self.componentForm = 'alkyl'
|
|
173
180
|
for halogen in self.componentHalogens:
|
|
174
181
|
if halogen not in componentSmarts_dict:
|
|
182
|
+
if _halogens_inferred:
|
|
183
|
+
continue
|
|
175
184
|
raise ValueError(f"No component SMARTS available for halogen '{halogen}' in HalogenGroup '{self.name}' (ID: {self.id})")
|
|
176
185
|
if self.componentForm not in componentSmarts_dict[halogen]:
|
|
186
|
+
if _halogens_inferred:
|
|
187
|
+
continue
|
|
177
188
|
raise ValueError(f"No component SMARTS available for form '{self.componentForm}' and halogen '{halogen}' in HalogenGroup '{self.name}' (ID: {self.id})")
|
|
178
189
|
for saturation in self.componentSaturation:
|
|
179
190
|
if saturation not in componentSmarts_dict[halogen][self.componentForm]:
|
|
191
|
+
if _halogens_inferred:
|
|
192
|
+
continue
|
|
180
193
|
raise ValueError(f"No component SMARTS available for saturation '{saturation}', form '{self.componentForm}', halogen '{halogen}' in HalogenGroup '{self.name}' (ID: {self.id})")
|
|
181
194
|
new_CS.append(componentSmarts_dict[halogen][self.componentForm][saturation])
|
|
182
195
|
self.componentSmarts = new_CS
|
|
@@ -15,7 +15,17 @@ from .fragmentation import generate_degradation_products
|
|
|
15
15
|
# PFASEmbedding (dict subclass, primary) must be imported after embeddings to take precedence
|
|
16
16
|
from .PFASEmbeddings import PFASEmbedding, PFASEmbeddingSet, EmbeddingArray, ResultsModel, MoleculeResult, generate_fingerprint
|
|
17
17
|
from .prioritise import prioritise_molecules, prioritize_molecules, get_priority_statistics
|
|
18
|
+
from .group_features import (
|
|
19
|
+
extract_group_features, GroupFeatureResult,
|
|
20
|
+
PER_GROUP_IDS, POLY_GROUP_IDS, HALOGENS_ORDER,
|
|
21
|
+
GENERIC_GROUP_VOCAB, GENERIC_GROUP_NAMES,
|
|
22
|
+
)
|
|
18
23
|
__version__ = "3.2.0"
|
|
19
24
|
__all__ = ['HalogenGroup', 'PFASDefinition', 'parse_smiles', 'parse_mols','parse_mol', 'parse_groups_in_mol', 'parse_from_database', 'setup_halogen_groups_database', 'plot_HalogenGroups', 'plot_mol','plot_mols', 'FINGERPRINT_PRESETS', 'PFASFingerprint', 'generate_fingerprint', 'get_compiled_componentSMARTSs', 'get_componentSMARTSs', 'get_HalogenGroups', 'get_compiled_HalogenGroups', 'get_compiled_PFASGroups', 'get_PFASDefinitions' ,'ComponentsSolver', 'generate_homologues', 'generate_degradation_products',"rdkit_disable_log","load_HalogenGroups", "HALOGEN_GROUPS_FILE", "_match_wildcard_groups_in_mol"]
|
|
20
25
|
__all__.extend(['PFASEmbedding', 'PFASEmbeddingSet', 'EmbeddingArray', 'ResultsModel', 'MoleculeResult', 'prioritise_molecules', 'prioritize_molecules', 'get_priority_statistics'])
|
|
21
26
|
__all__.extend(['HomologueSeries', 'HomologueEntry'])
|
|
27
|
+
__all__.extend([
|
|
28
|
+
'extract_group_features', 'GroupFeatureResult',
|
|
29
|
+
'PER_GROUP_IDS', 'POLY_GROUP_IDS', 'HALOGENS_ORDER',
|
|
30
|
+
'GENERIC_GROUP_VOCAB', 'GENERIC_GROUP_NAMES',
|
|
31
|
+
])
|
|
@@ -63,12 +63,12 @@
|
|
|
63
63
|
"id": 3,
|
|
64
64
|
"name": "OPPT 2023",
|
|
65
65
|
"smarts": [
|
|
66
|
-
"[#
|
|
67
|
-
"[#6](F)(F)([F,#8,#
|
|
68
|
-
"[#6](F)(F)(F)[#6]([#6](F)(F)F)([F,#
|
|
66
|
+
"[#6X4](F)(F)[#6X4](F)",
|
|
67
|
+
"[#6](F)(F)([F,#8,#6X4])[#8][#6](F)(F)[F,#8,#6X4]",
|
|
68
|
+
"[#6](F)(F)(F)[#6]([#6](F)(F)F)([F,#6X4])[F,#6X4]"
|
|
69
69
|
],
|
|
70
70
|
"fluorineRatio": null,
|
|
71
|
-
"description": "Three inclusion criteria.",
|
|
71
|
+
"description": "Three inclusion criteria. R groups are unconstrained per the final 2023 rule (40 CFR 705.3) -- the 2021 proposal's \"none of the R groups can be hydrogen\" language was dropped when finalized, so R positions (and the non-defining valence on the backbone CF2/CF carbons) may be H. Validated against EPA's published PFAS8a7v3 CompTox list: 99.96% coverage (13,035/13,040) with H unconstrained, vs 91.50% with H0 enforced.",
|
|
72
72
|
"test": {
|
|
73
73
|
"category": "definition",
|
|
74
74
|
"examples": {
|
|
@@ -77,13 +77,13 @@
|
|
|
77
77
|
"C(F)(F)C(F)(F)C(F)(F)C",
|
|
78
78
|
"C(F)(F)(F)C(F)(F)OC(F)(F)C(F)(F)F",
|
|
79
79
|
"C(F)(F)(C(F)(F)F)C(F)(F)C(F)(F)F",
|
|
80
|
-
"C(F)(F)(F)C(OC)(F)C(F)(F)F"
|
|
80
|
+
"C(F)(F)(F)C(OC)(F)C(F)(F)F",
|
|
81
|
+
"FC(F)C(F)F"
|
|
81
82
|
],
|
|
82
83
|
"negatives": [
|
|
83
84
|
"CCCCCC",
|
|
84
85
|
"C(F)CCC",
|
|
85
86
|
"CC(F)(F)C",
|
|
86
|
-
"FC(F)C(F)F",
|
|
87
87
|
"C1=CC=CC=C1"
|
|
88
88
|
]
|
|
89
89
|
}
|
|
@@ -166,5 +166,28 @@
|
|
|
166
166
|
]
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"id": 6,
|
|
172
|
+
"name": "OPPT 2021",
|
|
173
|
+
"smarts": [
|
|
174
|
+
"[#6H0X4](F)(F)[#6H0X4](F)"
|
|
175
|
+
],
|
|
176
|
+
"fluorineRatio": null,
|
|
177
|
+
"description": "R-(CF2)-CF(R')R'', where the CF2 and CF moieties are saturated carbons and none of the R groups can be hydrogen (2021 proposed definition; superseded by OPPT 2023, which drops this H restriction).",
|
|
178
|
+
"test": {
|
|
179
|
+
"category": "definition",
|
|
180
|
+
"examples": {
|
|
181
|
+
"positives": [
|
|
182
|
+
"C(F)(F)(F)C(F)(F)C(F)(F)F"
|
|
183
|
+
],
|
|
184
|
+
"negatives": [
|
|
185
|
+
"CCCCCC",
|
|
186
|
+
"C(F)CCC",
|
|
187
|
+
"CC(F)(F)C",
|
|
188
|
+
"C1=CC=CC=C1"
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
}
|
|
169
192
|
}
|
|
170
193
|
]
|
|
@@ -143,8 +143,23 @@
|
|
|
143
143
|
"alkyl": {
|
|
144
144
|
"per": {
|
|
145
145
|
"name": "Alkyl",
|
|
146
|
-
"component": "[CH2]",
|
|
147
|
-
"end": "[CH3]"
|
|
146
|
+
"component": "[CH2!$([#6][F,Cl,Br,I]),CH3$([CH3][#6])!$([#6][F,Cl,Br,I])]",
|
|
147
|
+
"end": "[CH3]",
|
|
148
|
+
"constraints": {"gte": {"H": 2}}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
"cyclic": {
|
|
152
|
+
"per": {
|
|
153
|
+
"name": "Cyclic alkyl",
|
|
154
|
+
"component": "[#6aX4R$([#6aR]([#6aR!$([#6][F,Cl,Br,I])])([#6aR!$([#6][F,Cl,Br,I])])[!$([#6][F,Cl,Br,I])][!$([#6][F,Cl,Br,I])])]",
|
|
155
|
+
"constraints": {"gte": {"H": 2}}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"aryl": {
|
|
159
|
+
"per": {
|
|
160
|
+
"name": "Aryl",
|
|
161
|
+
"component": "[#6aX3R$([#6aR]([#6aR!$([#6][F,Cl,Br,I])])[#6aR!$([#6][F,Cl,Br,I])])!$([#6][F,Cl,Br,I])]",
|
|
162
|
+
"constraints": {"gte": {"H": 1}}
|
|
148
163
|
}
|
|
149
164
|
}
|
|
150
165
|
}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"""Structured feature extraction from PFASGroups halogen group detection.
|
|
2
|
+
|
|
3
|
+
This module provides :func:`extract_group_features`, a single public entry
|
|
4
|
+
point that runs the necessary :func:`~PFASGroups.parser.parse_mol` calls and
|
|
5
|
+
returns a :class:`GroupFeatureResult` with four named feature dictionaries
|
|
6
|
+
covering the following four group categories:
|
|
7
|
+
|
|
8
|
+
- **Polyhalogenated groups** (ids 35, 38, 45): aggregate match counts
|
|
9
|
+
- **Perhalogenated groups** (ids 34, 37, 44): per-halogen max component sizes
|
|
10
|
+
- **Alkyl chain (H pseudo-halogen)** aggregates: convenience view of the H
|
|
11
|
+
column of perhalogenated results
|
|
12
|
+
- **Generic functional groups** (ids 29-76): wildcard bag-of-groups counts
|
|
13
|
+
|
|
14
|
+
The six component groups (34/35/37/38/44/45) are excluded from wildcard
|
|
15
|
+
matching by design, so they never appear in the generic-group counts.
|
|
16
|
+
|
|
17
|
+
Example
|
|
18
|
+
-------
|
|
19
|
+
>>> from rdkit import Chem
|
|
20
|
+
>>> from PFASGroups import extract_group_features
|
|
21
|
+
>>> mol = Chem.MolFromSmiles("FC(F)(F)C(F)(F)C(F)(F)C(=O)O") # PFOA-like
|
|
22
|
+
>>> r = extract_group_features(mol)
|
|
23
|
+
>>> r.poly_counts
|
|
24
|
+
{'poly_alkyl': 1.0, 'poly_aryl': 0.0, 'poly_cyclic': 0.0}
|
|
25
|
+
>>> r.per_halogen_sizes['g34_F'] # longest perfluoroalkyl component (carbons)
|
|
26
|
+
4.0
|
|
27
|
+
>>> r.h_chain_sizes
|
|
28
|
+
{'alkyl_H': 0.0, 'aryl_H': 0.0, 'cyclic_H': 0.0}
|
|
29
|
+
>>> len(r.to_array()) # 3 + 15 + 48
|
|
30
|
+
66
|
|
31
|
+
"""
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
from dataclasses import dataclass, field
|
|
35
|
+
from typing import Union
|
|
36
|
+
|
|
37
|
+
import numpy as np
|
|
38
|
+
from rdkit import Chem
|
|
39
|
+
|
|
40
|
+
from .parser import parse_mol as _parse_mol # type: ignore[attr-defined]
|
|
41
|
+
from .getter import get_compiled_HalogenGroups as _get_compiled_HalogenGroups
|
|
42
|
+
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
# Public constants
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
#: Group IDs for perhalogenated groups (alkyl, aryl, cyclic).
|
|
48
|
+
#: These groups require *all* halogen-bearing carbons to carry only one halogen
|
|
49
|
+
#: type. Each match is attributed to a single halogen (or H for alkyl chains).
|
|
50
|
+
PER_GROUP_IDS: list[int] = [34, 37, 44]
|
|
51
|
+
|
|
52
|
+
#: Group IDs for polyhalogenated groups (alkyl, aryl, cyclic).
|
|
53
|
+
#: These groups allow a *mixture* of halogens and/or partial halogenation.
|
|
54
|
+
#: Match counts are aggregated across all halogens.
|
|
55
|
+
POLY_GROUP_IDS: list[int] = [35, 38, 45]
|
|
56
|
+
|
|
57
|
+
#: Halogen types recognised in :attr:`GroupFeatureResult.per_halogen_sizes`.
|
|
58
|
+
#: ``'H'`` is the pseudo-halogen used to detect un-substituted alkyl chains
|
|
59
|
+
#: (perhalogenated-alkyl, group 34) via :class:`~PFASGroups.ComponentsSolverModel.ComponentsSolver`.
|
|
60
|
+
HALOGENS_ORDER: list[str] = ["F", "Cl", "Br", "I", "H"]
|
|
61
|
+
|
|
62
|
+
#: Ordered list of group IDs covered by :attr:`GroupFeatureResult.generic_groups`.
|
|
63
|
+
#: Ids {34, 35, 37, 38, 44, 45} are excluded from wildcard matching and will
|
|
64
|
+
#: always be zero in this vector.
|
|
65
|
+
GENERIC_GROUP_VOCAB: list[int] = list(range(29, 77)) # 48 entries
|
|
66
|
+
|
|
67
|
+
#: Human-readable names for every group id in :data:`GENERIC_GROUP_VOCAB`.
|
|
68
|
+
GENERIC_GROUP_NAMES: dict[int, str] = {
|
|
69
|
+
29: "acrylate",
|
|
70
|
+
30: "acyl halide",
|
|
71
|
+
31: "alcohol",
|
|
72
|
+
32: "aldehyde",
|
|
73
|
+
33: "alkene",
|
|
74
|
+
34: "perhalogenated alkyl",
|
|
75
|
+
35: "polyhalogenated alkyl",
|
|
76
|
+
36: "alkyne",
|
|
77
|
+
37: "perhalogenated aryl compounds",
|
|
78
|
+
38: "polyhalogenated aryl compounds",
|
|
79
|
+
39: "benzodioxole",
|
|
80
|
+
40: "benzoyl peroxydes",
|
|
81
|
+
41: "bromide",
|
|
82
|
+
42: "carboxylic acid",
|
|
83
|
+
43: "chloride",
|
|
84
|
+
44: "perhalogenated cyclic compounds",
|
|
85
|
+
45: "polyhalogenated cyclic compounds",
|
|
86
|
+
46: "ester",
|
|
87
|
+
47: "ether",
|
|
88
|
+
48: "fluoride",
|
|
89
|
+
49: "glucuronate",
|
|
90
|
+
50: "iodide",
|
|
91
|
+
51: "ketone",
|
|
92
|
+
52: "methacrylate",
|
|
93
|
+
53: "peroxydes",
|
|
94
|
+
54: "side-chain aromatics",
|
|
95
|
+
55: "sulfenic acid",
|
|
96
|
+
56: "sulfenyl halide",
|
|
97
|
+
57: "sulfinic acid",
|
|
98
|
+
58: "sulfinyl amido sulfonic acid",
|
|
99
|
+
59: "sulfonamide",
|
|
100
|
+
60: "sulfonamidoethanol",
|
|
101
|
+
61: "sulfonic acid",
|
|
102
|
+
62: "sulfonyl halide",
|
|
103
|
+
63: "sulfonyl propanoic acid",
|
|
104
|
+
64: "sulfuric acid",
|
|
105
|
+
65: "thioester keto dicarboxylic acid",
|
|
106
|
+
66: "thiocyanic acid",
|
|
107
|
+
67: "phosphinic acid",
|
|
108
|
+
68: "phosphonic acid",
|
|
109
|
+
69: "amide",
|
|
110
|
+
70: "amine",
|
|
111
|
+
71: "heterocyclic azine",
|
|
112
|
+
72: "heterocyclic azole",
|
|
113
|
+
73: "betaine",
|
|
114
|
+
74: "glycine",
|
|
115
|
+
75: "trichlorosilane",
|
|
116
|
+
76: "silane",
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
# Internal state
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
_COMPONENT_GROUPS: list | None = None # cached list of HalogenGroups {34..45}
|
|
124
|
+
_GENERIC_GROUP_INDEX: dict[int, int] = {gid: i for i, gid in enumerate(GENERIC_GROUP_VOCAB)}
|
|
125
|
+
_HAL_TO_COL: dict[str, int] = {h: i for i, h in enumerate(HALOGENS_ORDER)}
|
|
126
|
+
_PER_GID_TO_ROW: dict[int, int] = {34: 0, 37: 1, 44: 2}
|
|
127
|
+
_POLY_GID_TO_IDX: dict[int, int] = {35: 0, 38: 1, 45: 2}
|
|
128
|
+
|
|
129
|
+
# Short labels used in h_chain_sizes
|
|
130
|
+
_H_CHAIN_KEYS: list[str] = ["alkyl_H", "aryl_H", "cyclic_H"]
|
|
131
|
+
# Corresponding per_halogen_sizes keys (H column of each per group)
|
|
132
|
+
_H_CHAIN_PHS_KEYS: list[str] = ["g34_H", "g37_H", "g44_H"]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _get_component_groups() -> list:
|
|
136
|
+
"""Return a cached list of :class:`~PFASGroups.HalogenGroup` objects with ids in ``{34,35,37,38,44,45}``."""
|
|
137
|
+
global _COMPONENT_GROUPS
|
|
138
|
+
if _COMPONENT_GROUPS is None:
|
|
139
|
+
_ids = {34, 35, 37, 38, 44, 45}
|
|
140
|
+
_COMPONENT_GROUPS = [g for g in _get_compiled_HalogenGroups() if g.id in _ids]
|
|
141
|
+
return _COMPONENT_GROUPS
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
# GroupFeatureResult
|
|
146
|
+
# ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
@dataclass
|
|
149
|
+
class GroupFeatureResult:
|
|
150
|
+
"""Structured feature extraction result for a single molecule.
|
|
151
|
+
|
|
152
|
+
This dataclass groups features from two :func:`~PFASGroups.parser.parse_mol`
|
|
153
|
+
calls into four semantically distinct dictionaries. It is returned by
|
|
154
|
+
:func:`extract_group_features`.
|
|
155
|
+
|
|
156
|
+
Attributes
|
|
157
|
+
----------
|
|
158
|
+
poly_counts : dict[str, float]
|
|
159
|
+
Match counts for **polyhalogenated** groups (ids 35, 38, 45), aggregated
|
|
160
|
+
across all halogens (F, Cl, Br, I, H). Keys: ``'poly_alkyl'``,
|
|
161
|
+
``'poly_aryl'``, ``'poly_cyclic'``.
|
|
162
|
+
|
|
163
|
+
per_halogen_sizes : dict[str, float]
|
|
164
|
+
Maximum carbon-component size for **perhalogenated** groups (ids 34, 37,
|
|
165
|
+
44) resolved per halogen. Keys follow the pattern ``'g{id}_{hal}'``
|
|
166
|
+
where *id* is in ``{34, 37, 44}`` and *hal* is in
|
|
167
|
+
``['F', 'Cl', 'Br', 'I', 'H']``. Zero when no match is found.
|
|
168
|
+
|
|
169
|
+
h_chain_sizes : dict[str, float]
|
|
170
|
+
Convenience view of the ``'H'`` column of :attr:`per_halogen_sizes`:
|
|
171
|
+
the largest un-substituted alkyl / aryl / cyclic component detected via
|
|
172
|
+
the H pseudo-halogen mechanism. Keys: ``'alkyl_H'``, ``'aryl_H'``,
|
|
173
|
+
``'cyclic_H'``. **Not included in** :meth:`to_array`.
|
|
174
|
+
|
|
175
|
+
generic_groups : dict[str, float]
|
|
176
|
+
Wildcard functional-group match counts for group ids 29-76. Keys:
|
|
177
|
+
``'g29'`` … ``'g76'``. Ids ``{34, 35, 37, 38, 44, 45}`` are excluded
|
|
178
|
+
from wildcard matching and will always be zero.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
poly_counts: dict[str, float] = field(default_factory=dict)
|
|
182
|
+
per_halogen_sizes: dict[str, float] = field(default_factory=dict)
|
|
183
|
+
h_chain_sizes: dict[str, float] = field(default_factory=dict)
|
|
184
|
+
generic_groups: dict[str, float] = field(default_factory=dict)
|
|
185
|
+
|
|
186
|
+
def to_array(self) -> np.ndarray:
|
|
187
|
+
"""Return a fixed-length float32 array of shape ``(66,)``.
|
|
188
|
+
|
|
189
|
+
Layout::
|
|
190
|
+
|
|
191
|
+
[0:3] poly_counts (poly_alkyl, poly_aryl, poly_cyclic)
|
|
192
|
+
[3:18] per_halogen_sizes (g34_F…g44_H, 3 groups × 5 halogens)
|
|
193
|
+
[18:66] generic_groups (g29…g76, 48 entries)
|
|
194
|
+
|
|
195
|
+
Note
|
|
196
|
+
----
|
|
197
|
+
:attr:`h_chain_sizes` is excluded from this array (it is a strict
|
|
198
|
+
subset of :attr:`per_halogen_sizes`).
|
|
199
|
+
"""
|
|
200
|
+
poly = np.array(
|
|
201
|
+
[self.poly_counts[k] for k in ("poly_alkyl", "poly_aryl", "poly_cyclic")],
|
|
202
|
+
dtype=np.float32,
|
|
203
|
+
)
|
|
204
|
+
per_keys = [
|
|
205
|
+
f"g{gid}_{h}"
|
|
206
|
+
for gid in PER_GROUP_IDS
|
|
207
|
+
for h in HALOGENS_ORDER
|
|
208
|
+
]
|
|
209
|
+
per = np.array([self.per_halogen_sizes[k] for k in per_keys], dtype=np.float32)
|
|
210
|
+
gen = np.array(
|
|
211
|
+
[self.generic_groups[f"g{gid}"] for gid in GENERIC_GROUP_VOCAB],
|
|
212
|
+
dtype=np.float32,
|
|
213
|
+
)
|
|
214
|
+
return np.concatenate([poly, per, gen])
|
|
215
|
+
|
|
216
|
+
def feature_names(self) -> list[str]:
|
|
217
|
+
"""Return the 66 feature names in the same order as :meth:`to_array`.
|
|
218
|
+
|
|
219
|
+
Returns
|
|
220
|
+
-------
|
|
221
|
+
list[str]
|
|
222
|
+
Labels: ``['poly_alkyl', 'poly_aryl', 'poly_cyclic',
|
|
223
|
+
'g34_F', 'g34_Cl', …, 'g44_H', 'g29', 'g30', …, 'g76']``.
|
|
224
|
+
"""
|
|
225
|
+
poly_names = ["poly_alkyl", "poly_aryl", "poly_cyclic"]
|
|
226
|
+
per_names = [
|
|
227
|
+
f"g{gid}_{h}"
|
|
228
|
+
for gid in PER_GROUP_IDS
|
|
229
|
+
for h in HALOGENS_ORDER
|
|
230
|
+
]
|
|
231
|
+
gen_names = [f"g{gid}" for gid in GENERIC_GROUP_VOCAB]
|
|
232
|
+
return poly_names + per_names + gen_names
|
|
233
|
+
|
|
234
|
+
def __repr__(self) -> str:
|
|
235
|
+
nz_poly = sum(1 for v in self.poly_counts.values() if v)
|
|
236
|
+
nz_per = sum(1 for v in self.per_halogen_sizes.values() if v)
|
|
237
|
+
nz_gen = sum(1 for v in self.generic_groups.values() if v)
|
|
238
|
+
return (
|
|
239
|
+
f"GroupFeatureResult("
|
|
240
|
+
f"poly_counts={nz_poly} nonzero, "
|
|
241
|
+
f"per_halogen_sizes={nz_per} nonzero, "
|
|
242
|
+
f"generic_groups={nz_gen} nonzero)"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ---------------------------------------------------------------------------
|
|
247
|
+
# Public API
|
|
248
|
+
# ---------------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
def extract_group_features(
|
|
251
|
+
mol: Union[Chem.Mol, str],
|
|
252
|
+
) -> GroupFeatureResult:
|
|
253
|
+
"""Extract structured halogen-group features for a single molecule.
|
|
254
|
+
|
|
255
|
+
Two :func:`~PFASGroups.parser.parse_mol` calls are made internally:
|
|
256
|
+
|
|
257
|
+
1. ``halogens=['H','F','Cl','Br','I']`` with the six component groups
|
|
258
|
+
(ids 34, 35, 37, 38, 44, 45) — populates :attr:`~GroupFeatureResult.poly_counts`
|
|
259
|
+
and :attr:`~GroupFeatureResult.per_halogen_sizes`.
|
|
260
|
+
2. ``halogens=['*']`` (wildcard) — populates
|
|
261
|
+
:attr:`~GroupFeatureResult.generic_groups`.
|
|
262
|
+
|
|
263
|
+
Parameters
|
|
264
|
+
----------
|
|
265
|
+
mol : rdkit.Chem.Mol or str
|
|
266
|
+
RDKit molecule object or a SMILES string.
|
|
267
|
+
|
|
268
|
+
Returns
|
|
269
|
+
-------
|
|
270
|
+
GroupFeatureResult
|
|
271
|
+
Populated result object. Call :meth:`~GroupFeatureResult.to_array` to
|
|
272
|
+
obtain a fixed-length float32 array of shape ``(66,)``.
|
|
273
|
+
|
|
274
|
+
Raises
|
|
275
|
+
------
|
|
276
|
+
ValueError
|
|
277
|
+
If a SMILES string cannot be parsed by RDKit.
|
|
278
|
+
RuntimeError
|
|
279
|
+
If an unexpected error occurs during :func:`parse_mol`.
|
|
280
|
+
|
|
281
|
+
Examples
|
|
282
|
+
--------
|
|
283
|
+
>>> from rdkit import Chem
|
|
284
|
+
>>> from PFASGroups import extract_group_features
|
|
285
|
+
>>> pfoa = Chem.MolFromSmiles("FC(F)(F)C(F)(F)C(F)(F)C(=O)O")
|
|
286
|
+
>>> r = extract_group_features(pfoa)
|
|
287
|
+
>>> r.poly_counts
|
|
288
|
+
{'poly_alkyl': 1.0, 'poly_aryl': 0.0, 'poly_cyclic': 0.0}
|
|
289
|
+
>>> r.per_halogen_sizes['g34_F']
|
|
290
|
+
4.0
|
|
291
|
+
>>> r.h_chain_sizes
|
|
292
|
+
{'alkyl_H': 0.0, 'aryl_H': 0.0, 'cyclic_H': 0.0}
|
|
293
|
+
>>> arr = r.to_array()
|
|
294
|
+
>>> arr.shape
|
|
295
|
+
(66,)
|
|
296
|
+
>>> # Octane — only H alkyl chain detected
|
|
297
|
+
>>> octane = Chem.MolFromSmiles("CCCCCCCC")
|
|
298
|
+
>>> r2 = extract_group_features(octane)
|
|
299
|
+
>>> r2.h_chain_sizes['alkyl_H']
|
|
300
|
+
6.0
|
|
301
|
+
"""
|
|
302
|
+
if isinstance(mol, str):
|
|
303
|
+
mol = Chem.MolFromSmiles(mol)
|
|
304
|
+
if mol is None:
|
|
305
|
+
raise ValueError(f"RDKit could not parse SMILES string.")
|
|
306
|
+
|
|
307
|
+
# --- Call 1: component groups with all halogens including H ---
|
|
308
|
+
emb_chain = _parse_mol(
|
|
309
|
+
mol,
|
|
310
|
+
halogens=["H", "F", "Cl", "Br", "I"],
|
|
311
|
+
pfas_groups=_get_component_groups(),
|
|
312
|
+
compute_component_metrics=False,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# --- Call 2: wildcard generic functional groups ---
|
|
316
|
+
emb_wc = _parse_mol(
|
|
317
|
+
mol,
|
|
318
|
+
halogens=["*"],
|
|
319
|
+
compute_component_metrics=False,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# ------------------------------------------------------------------
|
|
323
|
+
# Build poly_counts
|
|
324
|
+
# ------------------------------------------------------------------
|
|
325
|
+
poly_vals: dict[str, float] = {"poly_alkyl": 0.0, "poly_aryl": 0.0, "poly_cyclic": 0.0}
|
|
326
|
+
_poly_key = {35: "poly_alkyl", 38: "poly_aryl", 45: "poly_cyclic"}
|
|
327
|
+
for match in emb_chain.matches:
|
|
328
|
+
if not match.is_group:
|
|
329
|
+
continue
|
|
330
|
+
key = _poly_key.get(match.group_id)
|
|
331
|
+
if key is not None:
|
|
332
|
+
poly_vals[key] += 1.0
|
|
333
|
+
|
|
334
|
+
# ------------------------------------------------------------------
|
|
335
|
+
# Build per_halogen_sizes (15 entries: 3 groups × 5 halogens)
|
|
336
|
+
# ------------------------------------------------------------------
|
|
337
|
+
phs: dict[str, float] = {
|
|
338
|
+
f"g{gid}_{h}": 0.0
|
|
339
|
+
for gid in PER_GROUP_IDS
|
|
340
|
+
for h in HALOGENS_ORDER
|
|
341
|
+
}
|
|
342
|
+
for match in emb_chain.matches:
|
|
343
|
+
if not match.is_group:
|
|
344
|
+
continue
|
|
345
|
+
row = _PER_GID_TO_ROW.get(match.group_id)
|
|
346
|
+
if row is None:
|
|
347
|
+
continue
|
|
348
|
+
gid = PER_GROUP_IDS[row]
|
|
349
|
+
h_val = match.get("halogen")
|
|
350
|
+
hal_hits: list[str]
|
|
351
|
+
if isinstance(h_val, list):
|
|
352
|
+
hal_hits = [x for x in h_val if x in _HAL_TO_COL]
|
|
353
|
+
elif h_val in _HAL_TO_COL:
|
|
354
|
+
hal_hits = [h_val]
|
|
355
|
+
else:
|
|
356
|
+
continue
|
|
357
|
+
max_size = 0.0
|
|
358
|
+
for comp in match.components:
|
|
359
|
+
s = comp.data.get("size", 0) or 0
|
|
360
|
+
if s > max_size:
|
|
361
|
+
max_size = float(s)
|
|
362
|
+
for hal in hal_hits:
|
|
363
|
+
k = f"g{gid}_{hal}"
|
|
364
|
+
if max_size > phs[k]:
|
|
365
|
+
phs[k] = max_size
|
|
366
|
+
|
|
367
|
+
# ------------------------------------------------------------------
|
|
368
|
+
# Build h_chain_sizes (convenience subset: H column of per_halogen_sizes)
|
|
369
|
+
# ------------------------------------------------------------------
|
|
370
|
+
h_chain: dict[str, float] = {
|
|
371
|
+
"alkyl_H": phs["g34_H"],
|
|
372
|
+
"aryl_H": phs["g37_H"],
|
|
373
|
+
"cyclic_H": phs["g44_H"],
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
# ------------------------------------------------------------------
|
|
377
|
+
# Build generic_groups (48 entries, ids 29-76)
|
|
378
|
+
# ------------------------------------------------------------------
|
|
379
|
+
gen: dict[str, float] = {f"g{gid}": 0.0 for gid in GENERIC_GROUP_VOCAB}
|
|
380
|
+
for match in emb_wc.matches:
|
|
381
|
+
if match.get("halogen") != "*":
|
|
382
|
+
continue
|
|
383
|
+
gid = match.group_id
|
|
384
|
+
k = f"g{gid}"
|
|
385
|
+
if k in gen:
|
|
386
|
+
gen[k] += 1.0
|
|
387
|
+
|
|
388
|
+
return GroupFeatureResult(
|
|
389
|
+
poly_counts=poly_vals,
|
|
390
|
+
per_halogen_sizes=phs,
|
|
391
|
+
h_chain_sizes=h_chain,
|
|
392
|
+
generic_groups=gen,
|
|
393
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PFASGroups
|
|
3
|
-
Version: 3.4.
|
|
3
|
+
Version: 3.4.2
|
|
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>
|
|
@@ -72,10 +72,48 @@ PFASGroups combines SMARTS pattern matching, molecular formula constraints, and
|
|
|
72
72
|
### Additional Tools
|
|
73
73
|
- **Homologue Series Generation**: Iterative component shortening to explore theoretical chemical space
|
|
74
74
|
- **Fingerprint Generation**: PFAS fingerprints for machine learning applications
|
|
75
|
+
- **Group Feature Extraction**: Fixed-length named feature vectors for ML pipelines (see below)
|
|
75
76
|
- **Visualization**: Assign and visualize PFAS groupings
|
|
76
77
|
- **Multiple Interfaces**: Python API, command-line tool, and browser-based JavaScript version (RDKitJS)
|
|
77
78
|
- **Batch Processing**: Efficient analysis of large chemical databases
|
|
78
79
|
|
|
80
|
+
## Group Feature Extraction
|
|
81
|
+
|
|
82
|
+
`extract_group_features` returns a structured `GroupFeatureResult` covering three
|
|
83
|
+
chemically meaningful feature sets derived from the six component groups and the
|
|
84
|
+
48 generic functional groups:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from rdkit import Chem
|
|
88
|
+
from PFASGroups import extract_group_features
|
|
89
|
+
|
|
90
|
+
mol = Chem.MolFromSmiles("FC(F)(F)C(F)(F)C(F)(F)C(=O)O") # PFOA-like
|
|
91
|
+
r = extract_group_features(mol)
|
|
92
|
+
|
|
93
|
+
# Polyhalogenated group match counts (groups 35/38/45, all halogens combined)
|
|
94
|
+
print(r.poly_counts)
|
|
95
|
+
# {'poly_alkyl': 1.0, 'poly_aryl': 0.0, 'poly_cyclic': 0.0}
|
|
96
|
+
|
|
97
|
+
# Perhalogenated group max component sizes per halogen (groups 34/37/44 × F/Cl/Br/I/H)
|
|
98
|
+
print(r.per_halogen_sizes['g34_F']) # longest perfluoroalkyl chain (carbons)
|
|
99
|
+
# 4.0
|
|
100
|
+
|
|
101
|
+
# Alkyl chain size via H pseudo-halogen (convenience view of per_halogen_sizes H column)
|
|
102
|
+
print(r.h_chain_sizes) # octane: {'alkyl_H': 6.0, 'aryl_H': 0.0, 'cyclic_H': 0.0}
|
|
103
|
+
|
|
104
|
+
# Generic functional groups (ids 29-76, wildcard)
|
|
105
|
+
print({k: v for k, v in r.generic_groups.items() if v})
|
|
106
|
+
# {'g42': 1.0, 'g48': 1.0} → carboxylic acid + fluoride
|
|
107
|
+
|
|
108
|
+
# Fixed-length float32 array of shape (66,) for ML use
|
|
109
|
+
arr = r.to_array()
|
|
110
|
+
names = r.feature_names() # matching labels
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
SMILES strings are accepted directly in addition to `rdkit.Chem.Mol` objects.
|
|
114
|
+
See the [Group Feature Extraction](docs/group_features.rst) documentation page for
|
|
115
|
+
the full feature layout, group ID table, and worked examples.
|
|
116
|
+
|
|
79
117
|
## Installation
|
|
80
118
|
|
|
81
119
|
The recommended installation method is via pip. RDKit must already be available in the target environment. It is recommended to use an environment manager (like Conda/Mamba, e.g [Miniforge](https://github.com/conda-forge/miniforge)) and install RDKit via
|
|
@@ -454,7 +492,7 @@ h_group = HalogenGroup(
|
|
|
454
492
|
)
|
|
455
493
|
|
|
456
494
|
# Parse using H mode
|
|
457
|
-
results = parse_smiles(smiles, halogens='H', pfas_groups=[h_group]
|
|
495
|
+
results = parse_smiles(smiles, halogens='H', pfas_groups=[h_group])
|
|
458
496
|
|
|
459
497
|
# Inspect H-component matches for the custom group
|
|
460
498
|
h_matches = [m for m in results[0].matches if m.get('id') == 9990 and m.get('type') == 'HalogenGroup']
|
|
@@ -808,6 +846,9 @@ See [USER_GUIDE.md](USER_GUIDE.md) for comprehensive examples including:
|
|
|
808
846
|
- Integration with pandas and scikit-learn
|
|
809
847
|
|
|
810
848
|
## Summary of changes by version
|
|
849
|
+
- **Version 3.4.2**: Fixed ambiguity with OPPT 2023.
|
|
850
|
+
|
|
851
|
+
- **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.
|
|
811
852
|
|
|
812
853
|
- **Version 3.4.0**: Added H components (use in homologue series generation) and wildcard components to match generic functional groups (broader use than PFAS).
|
|
813
854
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PFASGroups
|
|
3
|
-
Version: 3.4.
|
|
3
|
+
Version: 3.4.2
|
|
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>
|
|
@@ -72,10 +72,48 @@ PFASGroups combines SMARTS pattern matching, molecular formula constraints, and
|
|
|
72
72
|
### Additional Tools
|
|
73
73
|
- **Homologue Series Generation**: Iterative component shortening to explore theoretical chemical space
|
|
74
74
|
- **Fingerprint Generation**: PFAS fingerprints for machine learning applications
|
|
75
|
+
- **Group Feature Extraction**: Fixed-length named feature vectors for ML pipelines (see below)
|
|
75
76
|
- **Visualization**: Assign and visualize PFAS groupings
|
|
76
77
|
- **Multiple Interfaces**: Python API, command-line tool, and browser-based JavaScript version (RDKitJS)
|
|
77
78
|
- **Batch Processing**: Efficient analysis of large chemical databases
|
|
78
79
|
|
|
80
|
+
## Group Feature Extraction
|
|
81
|
+
|
|
82
|
+
`extract_group_features` returns a structured `GroupFeatureResult` covering three
|
|
83
|
+
chemically meaningful feature sets derived from the six component groups and the
|
|
84
|
+
48 generic functional groups:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from rdkit import Chem
|
|
88
|
+
from PFASGroups import extract_group_features
|
|
89
|
+
|
|
90
|
+
mol = Chem.MolFromSmiles("FC(F)(F)C(F)(F)C(F)(F)C(=O)O") # PFOA-like
|
|
91
|
+
r = extract_group_features(mol)
|
|
92
|
+
|
|
93
|
+
# Polyhalogenated group match counts (groups 35/38/45, all halogens combined)
|
|
94
|
+
print(r.poly_counts)
|
|
95
|
+
# {'poly_alkyl': 1.0, 'poly_aryl': 0.0, 'poly_cyclic': 0.0}
|
|
96
|
+
|
|
97
|
+
# Perhalogenated group max component sizes per halogen (groups 34/37/44 × F/Cl/Br/I/H)
|
|
98
|
+
print(r.per_halogen_sizes['g34_F']) # longest perfluoroalkyl chain (carbons)
|
|
99
|
+
# 4.0
|
|
100
|
+
|
|
101
|
+
# Alkyl chain size via H pseudo-halogen (convenience view of per_halogen_sizes H column)
|
|
102
|
+
print(r.h_chain_sizes) # octane: {'alkyl_H': 6.0, 'aryl_H': 0.0, 'cyclic_H': 0.0}
|
|
103
|
+
|
|
104
|
+
# Generic functional groups (ids 29-76, wildcard)
|
|
105
|
+
print({k: v for k, v in r.generic_groups.items() if v})
|
|
106
|
+
# {'g42': 1.0, 'g48': 1.0} → carboxylic acid + fluoride
|
|
107
|
+
|
|
108
|
+
# Fixed-length float32 array of shape (66,) for ML use
|
|
109
|
+
arr = r.to_array()
|
|
110
|
+
names = r.feature_names() # matching labels
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
SMILES strings are accepted directly in addition to `rdkit.Chem.Mol` objects.
|
|
114
|
+
See the [Group Feature Extraction](docs/group_features.rst) documentation page for
|
|
115
|
+
the full feature layout, group ID table, and worked examples.
|
|
116
|
+
|
|
79
117
|
## Installation
|
|
80
118
|
|
|
81
119
|
The recommended installation method is via pip. RDKit must already be available in the target environment. It is recommended to use an environment manager (like Conda/Mamba, e.g [Miniforge](https://github.com/conda-forge/miniforge)) and install RDKit via
|
|
@@ -454,7 +492,7 @@ h_group = HalogenGroup(
|
|
|
454
492
|
)
|
|
455
493
|
|
|
456
494
|
# Parse using H mode
|
|
457
|
-
results = parse_smiles(smiles, halogens='H', pfas_groups=[h_group]
|
|
495
|
+
results = parse_smiles(smiles, halogens='H', pfas_groups=[h_group])
|
|
458
496
|
|
|
459
497
|
# Inspect H-component matches for the custom group
|
|
460
498
|
h_matches = [m for m in results[0].matches if m.get('id') == 9990 and m.get('type') == 'HalogenGroup']
|
|
@@ -808,6 +846,9 @@ See [USER_GUIDE.md](USER_GUIDE.md) for comprehensive examples including:
|
|
|
808
846
|
- Integration with pandas and scikit-learn
|
|
809
847
|
|
|
810
848
|
## Summary of changes by version
|
|
849
|
+
- **Version 3.4.2**: Fixed ambiguity with OPPT 2023.
|
|
850
|
+
|
|
851
|
+
- **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.
|
|
811
852
|
|
|
812
853
|
- **Version 3.4.0**: Added H components (use in homologue series generation) and wildcard components to match generic functional groups (broader use than PFAS).
|
|
813
854
|
|
|
@@ -24,10 +24,48 @@ PFASGroups combines SMARTS pattern matching, molecular formula constraints, and
|
|
|
24
24
|
### Additional Tools
|
|
25
25
|
- **Homologue Series Generation**: Iterative component shortening to explore theoretical chemical space
|
|
26
26
|
- **Fingerprint Generation**: PFAS fingerprints for machine learning applications
|
|
27
|
+
- **Group Feature Extraction**: Fixed-length named feature vectors for ML pipelines (see below)
|
|
27
28
|
- **Visualization**: Assign and visualize PFAS groupings
|
|
28
29
|
- **Multiple Interfaces**: Python API, command-line tool, and browser-based JavaScript version (RDKitJS)
|
|
29
30
|
- **Batch Processing**: Efficient analysis of large chemical databases
|
|
30
31
|
|
|
32
|
+
## Group Feature Extraction
|
|
33
|
+
|
|
34
|
+
`extract_group_features` returns a structured `GroupFeatureResult` covering three
|
|
35
|
+
chemically meaningful feature sets derived from the six component groups and the
|
|
36
|
+
48 generic functional groups:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from rdkit import Chem
|
|
40
|
+
from PFASGroups import extract_group_features
|
|
41
|
+
|
|
42
|
+
mol = Chem.MolFromSmiles("FC(F)(F)C(F)(F)C(F)(F)C(=O)O") # PFOA-like
|
|
43
|
+
r = extract_group_features(mol)
|
|
44
|
+
|
|
45
|
+
# Polyhalogenated group match counts (groups 35/38/45, all halogens combined)
|
|
46
|
+
print(r.poly_counts)
|
|
47
|
+
# {'poly_alkyl': 1.0, 'poly_aryl': 0.0, 'poly_cyclic': 0.0}
|
|
48
|
+
|
|
49
|
+
# Perhalogenated group max component sizes per halogen (groups 34/37/44 × F/Cl/Br/I/H)
|
|
50
|
+
print(r.per_halogen_sizes['g34_F']) # longest perfluoroalkyl chain (carbons)
|
|
51
|
+
# 4.0
|
|
52
|
+
|
|
53
|
+
# Alkyl chain size via H pseudo-halogen (convenience view of per_halogen_sizes H column)
|
|
54
|
+
print(r.h_chain_sizes) # octane: {'alkyl_H': 6.0, 'aryl_H': 0.0, 'cyclic_H': 0.0}
|
|
55
|
+
|
|
56
|
+
# Generic functional groups (ids 29-76, wildcard)
|
|
57
|
+
print({k: v for k, v in r.generic_groups.items() if v})
|
|
58
|
+
# {'g42': 1.0, 'g48': 1.0} → carboxylic acid + fluoride
|
|
59
|
+
|
|
60
|
+
# Fixed-length float32 array of shape (66,) for ML use
|
|
61
|
+
arr = r.to_array()
|
|
62
|
+
names = r.feature_names() # matching labels
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
SMILES strings are accepted directly in addition to `rdkit.Chem.Mol` objects.
|
|
66
|
+
See the [Group Feature Extraction](docs/group_features.rst) documentation page for
|
|
67
|
+
the full feature layout, group ID table, and worked examples.
|
|
68
|
+
|
|
31
69
|
## Installation
|
|
32
70
|
|
|
33
71
|
The recommended installation method is via pip. RDKit must already be available in the target environment. It is recommended to use an environment manager (like Conda/Mamba, e.g [Miniforge](https://github.com/conda-forge/miniforge)) and install RDKit via
|
|
@@ -406,7 +444,7 @@ h_group = HalogenGroup(
|
|
|
406
444
|
)
|
|
407
445
|
|
|
408
446
|
# Parse using H mode
|
|
409
|
-
results = parse_smiles(smiles, halogens='H', pfas_groups=[h_group]
|
|
447
|
+
results = parse_smiles(smiles, halogens='H', pfas_groups=[h_group])
|
|
410
448
|
|
|
411
449
|
# Inspect H-component matches for the custom group
|
|
412
450
|
h_matches = [m for m in results[0].matches if m.get('id') == 9990 and m.get('type') == 'HalogenGroup']
|
|
@@ -760,6 +798,9 @@ See [USER_GUIDE.md](USER_GUIDE.md) for comprehensive examples including:
|
|
|
760
798
|
- Integration with pandas and scikit-learn
|
|
761
799
|
|
|
762
800
|
## Summary of changes by version
|
|
801
|
+
- **Version 3.4.2**: Fixed ambiguity with OPPT 2023.
|
|
802
|
+
|
|
803
|
+
- **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.
|
|
763
804
|
|
|
764
805
|
- **Version 3.4.0**: Added H components (use in homologue series generation) and wildcard components to match generic functional groups (broader use than PFAS).
|
|
765
806
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>PFASGroups GUI
|
|
6
|
+
<title>PFASGroups GUI - Help</title>
|
|
7
7
|
<style>
|
|
8
8
|
:root {
|
|
9
9
|
--orange: #E15D0B;
|
|
@@ -83,25 +83,25 @@
|
|
|
83
83
|
|
|
84
84
|
<h3>Data sources</h3>
|
|
85
85
|
<ul>
|
|
86
|
-
<li><b>File</b>
|
|
86
|
+
<li><b>File</b> - CSV, Excel (.xlsx/.xls), or SQLite (.db/.sqlite). Browse for the
|
|
87
87
|
file, choose a sheet (Excel) and the SMILES / name columns.</li>
|
|
88
|
-
<li><b>Database</b>
|
|
88
|
+
<li><b>Database</b> - SQLite, MariaDB, or PostgreSQL. Fill in the connection form
|
|
89
89
|
(host, port, database name, user, password, table name).</li>
|
|
90
|
-
<li><b>Manual</b>
|
|
90
|
+
<li><b>Manual</b> - Paste SMILES directly. One per line, or comma-separated with
|
|
91
91
|
optional names (<code>Name: SMILES</code>).</li>
|
|
92
92
|
</ul>
|
|
93
93
|
|
|
94
94
|
<h3>Options</h3>
|
|
95
95
|
<ul>
|
|
96
|
-
<li><b>Halogens</b>
|
|
96
|
+
<li><b>Halogens</b> - Which halogens to include in the analysis. Start with
|
|
97
97
|
<code>F only</code> for classical PFAS; broaden to include Cl/Br/I for expanded
|
|
98
98
|
screening.</li>
|
|
99
|
-
<li><b>Saturation filter</b>
|
|
99
|
+
<li><b>Saturation filter</b> - <code>Perf. (CF₂/CF₃)</code> restricts to
|
|
100
100
|
perfluorinated chains; <code>All fluorinated</code> includes partially
|
|
101
101
|
fluorinated structures.</li>
|
|
102
|
-
<li><b>Compute metrics</b>
|
|
103
|
-
for Tabs 4
|
|
104
|
-
<li><b>Check definitions</b>
|
|
102
|
+
<li><b>Compute metrics</b> - Calculate graph-theoretic component metrics (needed
|
|
103
|
+
for Tabs 4-6).</li>
|
|
104
|
+
<li><b>Check definitions</b> - Additionally test each molecule against the PFAS
|
|
105
105
|
definitions ticked below the option.</li>
|
|
106
106
|
</ul>
|
|
107
107
|
|
|
@@ -123,9 +123,9 @@
|
|
|
123
123
|
|
|
124
124
|
<h3>Controls</h3>
|
|
125
125
|
<ul>
|
|
126
|
-
<li><b>Search</b>
|
|
127
|
-
<li><b>Show All / Hide All</b>
|
|
128
|
-
<li><b>Export CSV</b>
|
|
126
|
+
<li><b>Search</b> - filter cards by molecule name.</li>
|
|
127
|
+
<li><b>Show All / Hide All</b> - toggle structure visibility for all cards.</li>
|
|
128
|
+
<li><b>Export CSV</b> - save a summary table (name, SMILES, matched groups,
|
|
129
129
|
definition results) to a CSV file.</li>
|
|
130
130
|
</ul>
|
|
131
131
|
</div>
|
|
@@ -133,24 +133,24 @@
|
|
|
133
133
|
<!-- ────────────────────────────────────────────────────────────────── Tab 3 -->
|
|
134
134
|
<h2><span class="step-num">3</span> Definition & SMARTS Tester Tab</h2>
|
|
135
135
|
<div class="card">
|
|
136
|
-
<h3>Sub-tab A
|
|
136
|
+
<h3>Sub-tab A - PFAS Definitions</h3>
|
|
137
137
|
<p>Enter a SMILES string and test it against:</p>
|
|
138
138
|
<ul>
|
|
139
139
|
<li><b>Built-in definitions</b> (default, when the custom JSON field is left blank).</li>
|
|
140
|
-
<li><b>Custom <code>PFASDefinition</code> JSON</b>
|
|
140
|
+
<li><b>Custom <code>PFASDefinition</code> JSON</b> - paste an array of definition
|
|
141
141
|
objects. Each must have <code>id</code>, <code>name</code>,
|
|
142
142
|
<code>smarts</code> (list of SMARTS strings), and optionally
|
|
143
143
|
<code>fluorineRatio</code> and <code>description</code>.</li>
|
|
144
144
|
</ul>
|
|
145
145
|
|
|
146
|
-
<h3>Sub-tab B
|
|
146
|
+
<h3>Sub-tab B - Custom Group (SMARTS)</h3>
|
|
147
147
|
<p>Define a custom <code>HalogenGroup</code> and test a molecule against it. Fields:</p>
|
|
148
148
|
<ul>
|
|
149
|
-
<li><b>componentSmarts</b>
|
|
149
|
+
<li><b>componentSmarts</b> - name of the underlying fluorine component pattern
|
|
150
150
|
(e.g. <code>Perfluoroalkyl</code>).</li>
|
|
151
|
-
<li><b>SMARTS dict (JSON)</b>
|
|
151
|
+
<li><b>SMARTS dict (JSON)</b> - mapping of SMARTS pattern → required minimum count,
|
|
152
152
|
e.g. <code>{"[CX3](=O)[OX2H1]": 1}</code>.</li>
|
|
153
|
-
<li><b>Constraints (JSON)</b>
|
|
153
|
+
<li><b>Constraints (JSON)</b> - element-count constraints,
|
|
154
154
|
e.g. <code>{"gte": {"F": 4}}</code>.</li>
|
|
155
155
|
</ul>
|
|
156
156
|
<p>Diagnostics are printed as JSON in the right panel, showing which stage passed
|
|
@@ -166,11 +166,11 @@
|
|
|
166
166
|
|
|
167
167
|
<h3>Modes</h3>
|
|
168
168
|
<ul>
|
|
169
|
-
<li><b>Intrinsic: total component size</b>
|
|
170
|
-
<li><b>Intrinsic: max component size</b>
|
|
171
|
-
<li><b>Intrinsic: total + max</b>
|
|
169
|
+
<li><b>Intrinsic: total component size</b> - sum of all matched fragment sizes.</li>
|
|
170
|
+
<li><b>Intrinsic: max component size</b> - largest single matched fragment.</li>
|
|
171
|
+
<li><b>Intrinsic: total + max</b> - weighted combination;
|
|
172
172
|
use α and β to balance the two terms.</li>
|
|
173
|
-
<li><b>Reference-based KL divergence</b>
|
|
173
|
+
<li><b>Reference-based KL divergence</b> - divergence from a supplied reference
|
|
174
174
|
dataset. Supply a CSV/Excel/SQLite file as the reference.</li>
|
|
175
175
|
</ul>
|
|
176
176
|
|
|
@@ -188,10 +188,10 @@
|
|
|
188
188
|
|
|
189
189
|
<h3>Methods</h3>
|
|
190
190
|
<ul>
|
|
191
|
-
<li><b>UMAP</b>
|
|
191
|
+
<li><b>UMAP</b> - fast, topology-preserving. Adjust <em>n_neighbours</em>
|
|
192
192
|
(local structure) and <em>min_dist</em> (cluster compactness).</li>
|
|
193
|
-
<li><b>PCA</b>
|
|
194
|
-
<li><b>t-SNE</b>
|
|
193
|
+
<li><b>PCA</b> - linear; no hyper-parameters.</li>
|
|
194
|
+
<li><b>t-SNE</b> - non-linear; adjust <em>perplexity</em>.</li>
|
|
195
195
|
</ul>
|
|
196
196
|
|
|
197
197
|
<h3>Fingerprint presets</h3>
|
|
@@ -218,13 +218,13 @@
|
|
|
218
218
|
|
|
219
219
|
<h3>Fingerprint sets</h3>
|
|
220
220
|
<ul>
|
|
221
|
-
<li><b>PFASGroups presets</b>
|
|
221
|
+
<li><b>PFASGroups presets</b> - any combination of the benchmark-validated
|
|
222
222
|
fingerprint presets (binary, best, best_2, …).</li>
|
|
223
|
-
<li><b>Morgan (RDKit)</b>
|
|
223
|
+
<li><b>Morgan (RDKit)</b> - radius-2 Morgan fingerprint, 512 bits. Useful
|
|
224
224
|
baseline.</li>
|
|
225
|
-
<li><b>ToxPrint (729 bits)</b>
|
|
226
|
-
<li><b>TxP_PFAS (129 bits)</b>
|
|
227
|
-
<li><b>Custom TSV/CSV</b>
|
|
225
|
+
<li><b>ToxPrint (729 bits)</b> - requires pyCSRML to be installed.</li>
|
|
226
|
+
<li><b>TxP_PFAS (129 bits)</b> - PFAS-specific ToxPrint subset; requires pyCSRML.</li>
|
|
227
|
+
<li><b>Custom TSV/CSV</b> - rows = molecules (same order as input), columns = bits.
|
|
228
228
|
No header row.</li>
|
|
229
229
|
</ul>
|
|
230
230
|
|
|
@@ -232,9 +232,9 @@
|
|
|
232
232
|
<p>All fingerprint-set pairs are compared using the Bayesian correlated t-test
|
|
233
233
|
(Benavoli et al. 2017) with ROPE = 0.01 ROC-AUC. The table reports:</p>
|
|
234
234
|
<ul>
|
|
235
|
-
<li><b>P(A>B)</b>
|
|
236
|
-
<li><b>P(ROPE)</b>
|
|
237
|
-
<li><b>P(B>A)</b>
|
|
235
|
+
<li><b>P(A>B)</b> - probability that set A is meaningfully better than B.</li>
|
|
236
|
+
<li><b>P(ROPE)</b> - probability that the two sets are practically equivalent.</li>
|
|
237
|
+
<li><b>P(B>A)</b> - probability that set B is meaningfully better than A.</li>
|
|
238
238
|
</ul>
|
|
239
239
|
|
|
240
240
|
<div class="note">
|
|
@@ -247,8 +247,8 @@
|
|
|
247
247
|
<h2>Keyboard shortcuts</h2>
|
|
248
248
|
<div class="card">
|
|
249
249
|
<ul>
|
|
250
|
-
<li><code>Ctrl+Q</code>
|
|
251
|
-
<li><code>Ctrl+W</code>
|
|
250
|
+
<li><code>Ctrl+Q</code> - quit the application.</li>
|
|
251
|
+
<li><code>Ctrl+W</code> - close (same as Ctrl+Q on Windows).</li>
|
|
252
252
|
</ul>
|
|
253
253
|
</div>
|
|
254
254
|
|
|
@@ -266,9 +266,9 @@
|
|
|
266
266
|
<div class="card">
|
|
267
267
|
<ul>
|
|
268
268
|
<li>Benavoli A et al. (2017) <em>Time for a Change: a Tutorial for Comparing
|
|
269
|
-
Multiple Classifiers Through Bayesian Analysis.</em> JMLR 18(1):2653
|
|
269
|
+
Multiple Classifiers Through Bayesian Analysis.</em> JMLR 18(1):2653-2688.</li>
|
|
270
270
|
<li>Nadeau C & Bengio Y (2003) <em>Inference for the Generalization Error.</em>
|
|
271
|
-
Machine Learning 52(3):239
|
|
271
|
+
Machine Learning 52(3):239-281.</li>
|
|
272
272
|
</ul>
|
|
273
273
|
</div>
|
|
274
274
|
|
|
@@ -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.2"
|
|
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,12 +1,12 @@
|
|
|
1
1
|
[metadata]
|
|
2
2
|
name = PFASGroups
|
|
3
|
-
version = 3.
|
|
3
|
+
version = 3.4.2
|
|
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
|
|
7
7
|
author = Luc T. Miaz
|
|
8
8
|
author_email = luc@miaz.ch
|
|
9
|
-
license =
|
|
9
|
+
license = CC-BY-NC-4.0
|
|
10
10
|
|
|
11
11
|
[egg_info]
|
|
12
12
|
tag_build =
|
|
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
|