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
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PFASGroups
|
|
3
|
+
Version: 3.2.2
|
|
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
|
+
Author: Luc T. Miaz
|
|
6
|
+
Author-email: "Luc T. Miaz" <luc@miaz.ch>
|
|
7
|
+
Maintainer-email: "Luc T. Miaz" <luc@miaz.ch>
|
|
8
|
+
License-Expression: CC-BY-NC-4.0
|
|
9
|
+
Project-URL: Homepage, https://github.com/lucmiaz/PFASGroups
|
|
10
|
+
Project-URL: Repository, https://github.com/lucmiaz/PFASGroups
|
|
11
|
+
Project-URL: Documentation, https://pfasgroups.readthedocs.io
|
|
12
|
+
Project-URL: Bug Tracker, https://github.com/lucmiaz/PFASGroups/issues
|
|
13
|
+
Keywords: PFAS,halogenated substances,per- and polyfluoroalkyl substances,chemical classification,cheminformatics,environmental chemistry,molecular structure,fingerprint,multi-halogen,fluorine,chlorine,bromine,iodine
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
26
|
+
Requires-Python: >=3.7
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
Requires-Dist: rdkit
|
|
29
|
+
Requires-Dist: numpy
|
|
30
|
+
Requires-Dist: pandas
|
|
31
|
+
Requires-Dist: networkx
|
|
32
|
+
Requires-Dist: tqdm
|
|
33
|
+
Requires-Dist: svgutils
|
|
34
|
+
Requires-Dist: scikit-learn
|
|
35
|
+
Requires-Dist: matplotlib
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pytest; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
39
|
+
Provides-Extra: database
|
|
40
|
+
Requires-Dist: sqlalchemy>=1.4.0; extra == "database"
|
|
41
|
+
|
|
42
|
+
# PFASGroups
|
|
43
|
+
|
|
44
|
+
A comprehensive cheminformatics package for automated detection, classification, and analysis of halogenated substances, in particular per- and polyfluoroalkyl substances (PFAS) in chemical databases.
|
|
45
|
+
|
|
46
|
+
## Overview
|
|
47
|
+
|
|
48
|
+
PFASGroups combines SMARTS pattern matching, molecular formula constraints, and graph-based pathfinding (using RDKit and NetworkX) to identify and classify PFAS compounds. The package enables systematic PFAS universe mapping and environmental monitoring applications.
|
|
49
|
+
|
|
50
|
+
## Key Features
|
|
51
|
+
|
|
52
|
+
### Core Capabilities
|
|
53
|
+
- **Halogen Group Identification**: Automated detection of 119 functional groups (114 compiled for fluorine-only embedding):
|
|
54
|
+
- 27 PFAS OECD groups
|
|
55
|
+
- 48 generic functional groups (IDs 29–73, 117–119; the last 3 are halogen-context-dependent or recently added)
|
|
56
|
+
- 43 fluorotelomer-specific groups with CH₂ linker validation
|
|
57
|
+
- 1 aggregate pattern-matching group (Group 116: Telomers, `compute=False`)
|
|
58
|
+
- **Atom Reference Requirement**: For non-telomer groups, SMARTS patterns must match atoms that are part of or directly connected to the fluorinated component (per/polyfluorinated carbons), respecting the `max_dist_from_comp` constraint
|
|
59
|
+
- **Linker Validation**: CH₂-specific validation for 40 fluorotelomer groups to distinguish from direct-attachment analogues. Telomer groups use `linker_smarts` to allow functional groups separated from perfluoro chains by non-fluorinated linkers
|
|
60
|
+
- **Aggregate Groups**: Pattern-matching groups that collect related PFAS groups via regex (e.g., Group 113 matches all "telomer" groups)
|
|
61
|
+
- **Component Length Analysis**: Quantification of per- and polyfluorinated alkyl components with CF₂ unit counting
|
|
62
|
+
- **Graph Metrics**: Comprehensive structural characterization (branching, eccentricity, diameter, resistance, centrality, telomer spacer length, ring size)
|
|
63
|
+
- **Customizable Definitions**: Easy extension to additional PFAS groups and halogenated chemical classes via JSON configuration. Component type definitions in `data/component_smarts_halogens.json` support optional per-type `constraints` (e.g. `{"gte": {"F": 2}}`) that are evaluated against the component's full atom count (backbone carbons plus attached halogens) to enforce minimum halogen counts or element exclusions — without repeating the check in every group definition
|
|
64
|
+
|
|
65
|
+
### Additional Tools
|
|
66
|
+
- **Homologue Series Generation**: Iterative component shortening to explore theoretical chemical space
|
|
67
|
+
- **Fingerprint Generation**: PFAS fingerprints for machine learning applications
|
|
68
|
+
- **Visualization**: Assign and visualize PFAS groupings
|
|
69
|
+
- **Multiple Interfaces**: Python API, command-line tool, and browser-based JavaScript version (RDKitJS)
|
|
70
|
+
- **Batch Processing**: Efficient analysis of large chemical databases
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
|
|
74
|
+
### From Pypi
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
pip install PFASGroups
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### From source (recommended for development)
|
|
81
|
+
|
|
82
|
+
**Prerequisites**: Python ≥ 3.7, RDKit (install via conda or pip before the steps below).
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
# Clone the repository
|
|
86
|
+
git clone https://github.com/lucmiaz/PFASGroups.git
|
|
87
|
+
cd PFASGroups
|
|
88
|
+
|
|
89
|
+
# Install in editable mode (development install)
|
|
90
|
+
pip install -e .
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
> **Note for conda users**: install RDKit via conda before running pip:
|
|
94
|
+
> ```sh
|
|
95
|
+
> conda install -c conda-forge rdkit
|
|
96
|
+
> ```
|
|
97
|
+
|
|
98
|
+
After installation, both `HalogenGroups` (all halogens by default) and `PFASgroups` (fluorine only by default) are importable, and the `pfasgroups` CLI command is available in your terminal.
|
|
99
|
+
|
|
100
|
+
### Verify installation
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from PFASGroups import parse_smiles
|
|
104
|
+
results = parse_smiles("FC(F)(F)C(F)(F)C(=O)O") # → PFASEmbeddingSet
|
|
105
|
+
print(results) # prints PFASEmbeddingSet summary (molecule count, matched groups, …)
|
|
106
|
+
print(results[0]) # prints PFASEmbedding summary for the first molecule
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Repository Structure
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
PFASGroups/
|
|
113
|
+
├── HalogenGroups/ # Multi-halogen wrapper package
|
|
114
|
+
│ └── __init__.py # Wraps PFASgroups; defaults halogens=['F','Cl','Br','I']
|
|
115
|
+
├── PFASGroups/ # Core implementation package (also importable as PFASgroups)
|
|
116
|
+
│ ├── __init__.py # Public API
|
|
117
|
+
│ ├── core.py # SMARTS matching engine, component detection, decorators
|
|
118
|
+
│ ├── parser.py # parse_smiles / parse_mols entry points
|
|
119
|
+
│ ├── embeddings.py # presets
|
|
120
|
+
│ ├── PFASEmbeddings.py # PFASEmbeddingSet, PFASEmbedding
|
|
121
|
+
│ ├── HalogenGroupModel.py # HalogenGroup data model
|
|
122
|
+
│ ├── PFASDefinitionModel.py # PFASDefinition model
|
|
123
|
+
│ ├── ComponentsSolverModel.py # Graph-based path-finding solver
|
|
124
|
+
│ ├── getter.py # get_HalogenGroups, get_componentSMARTSs, …
|
|
125
|
+
│ ├── cli.py # Command-line interface (halogengroups / pfasgroups)
|
|
126
|
+
│ ├── prioritise.py # prioritise_molecules
|
|
127
|
+
│ ├── generate_homologues.py # Homologue series generation
|
|
128
|
+
│ ├── fragmentation.py # Fragment utilities
|
|
129
|
+
│ ├── draw_mols.py # Molecule and group visualisation helpers
|
|
130
|
+
│ └── data/ # JSON configuration files (bundled with the package)
|
|
131
|
+
│ ├── Halogen_groups_smarts.json # 113 halogen group definitions
|
|
132
|
+
│ ├── component_smarts.json # Fluorinated component SMARTS patterns
|
|
133
|
+
│ ├── component_smarts_halogens.json # Multi-halogen component SMARTS patterns
|
|
134
|
+
│ └── PFAS_definitions_smarts.json # PFAS regulatory definitions
|
|
135
|
+
├── tests/ # Pytest test suite (25+ test modules)
|
|
136
|
+
│ ├── test_halogen_groups_smarts.py
|
|
137
|
+
│ ├── test_results_fingerprint.py
|
|
138
|
+
│ ├── test_results_model.py
|
|
139
|
+
│ ├── test_prioritise.py
|
|
140
|
+
│ └── …
|
|
141
|
+
├── examples/ # Standalone usage example scripts
|
|
142
|
+
│ ├── results_fingerprint_analysis.py
|
|
143
|
+
│ ├── prioritization_examples.py
|
|
144
|
+
│ └── …
|
|
145
|
+
├── docs/ # Sphinx documentation source
|
|
146
|
+
│ ├── quickstart.rst
|
|
147
|
+
│ ├── algorithm.rst
|
|
148
|
+
│ ├── customization.rst
|
|
149
|
+
│ ├── ResultsFingerprint_Guide.md
|
|
150
|
+
│ └── …
|
|
151
|
+
├── benchmark/ # Benchmarking scripts and timing reports
|
|
152
|
+
│ ├── data/
|
|
153
|
+
│ ├── reports/
|
|
154
|
+
│ └── …
|
|
155
|
+
└── pyproject.toml # Package metadata and build configuration
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Two importable packages, one codebase:**
|
|
159
|
+
|
|
160
|
+
| Package | Default `halogens` | Typical use |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| `HalogenGroups` | `['F', 'Cl', 'Br', 'I']` (all) | Multi-halogen analysis |
|
|
163
|
+
| `PFASgroups` | `'F'` (fluorine only) | PFAS / fluorine-focused analysis |
|
|
164
|
+
|
|
165
|
+
## Benchmark Summary (Mar 2026, v. 3.2.2, F groups)
|
|
166
|
+
|
|
167
|
+
Benchmarks were run on an Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (4C/8T), 15.5 GB RAM, Python 3.9.23, RDKit 2025.09.2, NetworkX 3.2.1 (the old version of Python was taken for compatibility with PFAS-atlas).
|
|
168
|
+
|
|
169
|
+
| Dataset/Profile | Count | Atom range | PFASgroups mean/median (ms) | PFAS-Atlas mean/median (ms) | Relative speed | Notes |
|
|
170
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
171
|
+
| OECD reference (real compounds) | 3,707 | varied | 26.3 / 20.8 | 32.4 / 37.0 | 1.23x faster | Real-world OECD dataset. |
|
|
172
|
+
| CLInventory, F only (real compounds) | 28,328 | varied | 23.2 / 17.5 | 5.1 / 1.0 | 4.6x slower | Large real-world dataset; Atlas faster due to smaller typical molecule size. |
|
|
173
|
+
| Large PFAS (≥35 atoms) | 1,242 | ≥35 | 526.0 / 253.4 | 93.8 / 72.5 | 5.6x slower | Large-molecule subset; heavy-tail runtime. |
|
|
174
|
+
| Stress-test (full metrics) | 2,500 | 11-622 | 271.8 / 35.9 | 67.0 / 38.7 | 4.1x slower | Synthetic stress-test; heavy-tail runtime. |
|
|
175
|
+
| Stress-test (no EGR) | 2,500 | 11-625 | 272.6 / 31.6 | 67.2 / 38.7 | ≈ full | Disables effective graph resistance; negligible speedup. |
|
|
176
|
+
| Stress-test (no metrics) | 2,500 | 9-621 | 205.1 / 28.7 | 67.0 / 38.4 | 1.33x faster vs full | Disables all component graph metrics. |
|
|
177
|
+
|
|
178
|
+
Timing profile plots (full vs no EGR vs no metrics):
|
|
179
|
+
- [benchmark/imgs/si/timing_profiles_comparison.png](benchmark/imgs/si/timing_profiles_comparison.png)
|
|
180
|
+
|
|
181
|
+
Disable or limit graph metrics in the Python API:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from PFASGroups import parse_smiles
|
|
185
|
+
|
|
186
|
+
smiles_list = ['C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(=O)O',
|
|
187
|
+
'C1=CC(=CC=C1[N+](=O)[O-])OC2=C(C=C(C=C2)C(F)(F)F)[N+](=O)[O-]',
|
|
188
|
+
'C(C(C(C(F)(F)S(=O)(=O)O)(F)F)(F)F)(C(C(F)(F)F)(F)F)(C(F)(F)F)C(F)(F)F'
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
# Skip all component graph metrics (fastest)
|
|
192
|
+
rFull = parse_smiles(smiles_list, compute_component_metrics=False)
|
|
193
|
+
print(rFull)
|
|
194
|
+
rFull.summary()
|
|
195
|
+
|
|
196
|
+
# Keep metrics but skip effective graph resistance entirely
|
|
197
|
+
rNoEGR = parse_smiles(smiles_list, limit_effective_graph_resistance=0)
|
|
198
|
+
print(rNoEGR)
|
|
199
|
+
|
|
200
|
+
# Compute resistance only for components below a size threshold
|
|
201
|
+
rLimitEGR = parse_smiles(smiles_list, limit_effective_graph_resistance=20)
|
|
202
|
+
print(rLimitEGR)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
CLI equivalents:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# Skip all component graph metrics (fastest)
|
|
209
|
+
pfasgroups parse --no-component-metrics "C(C(F)(F)F)F"
|
|
210
|
+
|
|
211
|
+
# Skip effective graph resistance entirely
|
|
212
|
+
pfasgroups parse --limit-effective-graph-resistance 0 "C(C(F)(F)F)F"
|
|
213
|
+
|
|
214
|
+
# Compute resistance only for components below a size threshold
|
|
215
|
+
pfasgroups parse --limit-effective-graph-resistance 200 "C(C(F)(F)F)F"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Quick Start
|
|
219
|
+
|
|
220
|
+
### Python API
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from PFASGroups import parse_smiles
|
|
224
|
+
|
|
225
|
+
# Parse PFAS structures — returns a PFASEmbeddingSet
|
|
226
|
+
smiles_list = ["C[Si](C)(Cl)CCC(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F",
|
|
227
|
+
"FC(=C(C(F)(F)F)C(F)(F)F)C(F)(F)C(F)(F)F",
|
|
228
|
+
"FC(F)(F)C(F)(C(F)(F)F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(C(F)(F)F)C(F)(F)F",
|
|
229
|
+
"C[Si](Cl)(Cl)CCC(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F",
|
|
230
|
+
"FC(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F",
|
|
231
|
+
"CC(=O)C(F)(F)C(F)(F)C(F)(F)F",
|
|
232
|
+
"FC(F)=C(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F",
|
|
233
|
+
"FC(F)(F)N1C(F)(F)C(F)(F)C(F)(F)C(F)(F)C1(F)F",
|
|
234
|
+
"FC(F)(F)C(F)(F)C(F)(F)CCl",
|
|
235
|
+
"OCC(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F",
|
|
236
|
+
"OS(=O)(=O)CCC(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F",
|
|
237
|
+
"FC(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)COC(=O)C=C",
|
|
238
|
+
"FC(F)(F)C(F)(F)C(F)(F)C(F)(F)C(=O)OC=C",
|
|
239
|
+
"FC(F)(F)C(I)(C(F)(F)F)C(F)(F)F",
|
|
240
|
+
"OC(C(F)(F)C(F)(F)F)(C(F)(F)C(F)(F)F)C(F)(F)C(F)(F)F",
|
|
241
|
+
"C[Si](Cl)(Cl)CCC(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F",
|
|
242
|
+
"FC(F)=C(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F",
|
|
243
|
+
"CCN(CC)CC.NS(=O)(=O)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F",
|
|
244
|
+
"[OH-].C[N+](C)(C)CCCNS(=O)(=O)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F",
|
|
245
|
+
"[K+].[O-]S(=O)(=O)C(F)(F)C(F)(C(F)(F)F)C(F)(F)F",
|
|
246
|
+
"[NH4+].[O-]P(=O)(OCCC(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F)OCCC(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F",
|
|
247
|
+
"OCCN(CCO)S(=O)(=O)C(F)(F)C(F)(F)C(F)(F)C(F)F",
|
|
248
|
+
"OP(O)(=O)OCCC(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(C(F)(F)F)C(F)(F)F",
|
|
249
|
+
"FC(F)(F)C(F)(F)C(F)(F)C(F)(F)N(C(F)(F)C(F)(F)C(F)(F)C(F)(F)F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F",
|
|
250
|
+
"COC(=O)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F",
|
|
251
|
+
"FC(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(=O)OCC=C",
|
|
252
|
+
"CC(C)CCOC(=O)CC(NC(=O)C(F)(F)C(F)(F)C(F)(F)F)C(=O)OCCC(C)C",
|
|
253
|
+
"CN(C(=O)C(F)(F)C(F)(F)C(F)(F)F)C(=O)C(F)(F)C(F)(F)C(F)(F)F",
|
|
254
|
+
"FC(F)(F)C(F)(F)C(F)(F)C=CI",
|
|
255
|
+
"OC(=O)C1=CC=CC=C1NC(=O)C(F)(F)C(F)(F)C(F)(F)F",
|
|
256
|
+
"FC(F)(F)C1(F)C(F)(F)C(F)(F)C(F)(F)C(F)(C(F)(F)F)C1(F)F"]
|
|
257
|
+
|
|
258
|
+
results = parse_smiles(smiles_list) # → PFASEmbeddingSet
|
|
259
|
+
print(results) # summary: molecule count, top groups, …
|
|
260
|
+
print(results[0]) # per-molecule summary for first entry
|
|
261
|
+
|
|
262
|
+
# Embedding from a pre-parsed set (avoids re-parsing)
|
|
263
|
+
arr = results.to_array() # default: binary, all groups
|
|
264
|
+
arr = results.to_array(group_selection='oecd', component_metrics=['binary'])
|
|
265
|
+
cols = results.column_names() # matching column labels
|
|
266
|
+
|
|
267
|
+
# Filter components by halogen, form, and saturation
|
|
268
|
+
results_f = parse_smiles(smiles_list, halogens='F') # Fluorine only
|
|
269
|
+
results_pfa = parse_smiles(smiles_list, halogens='F', saturation='per', form='alkyl') # Perfluoroalkyl only
|
|
270
|
+
results_cyclic = parse_smiles(smiles_list, form='cyclic') # Cyclic forms only
|
|
271
|
+
|
|
272
|
+
# Dimensionality reduction (methods on PFASEmbeddingSet)
|
|
273
|
+
pca_result = results.perform_pca(n_components=5, plot=True)
|
|
274
|
+
tsne_result = results.perform_tsne(perplexity=30, plot=True)
|
|
275
|
+
umap_result = results.perform_umap(n_neighbors=15, plot=True)
|
|
276
|
+
|
|
277
|
+
# Colour each point by the PFAS group with the highest match count
|
|
278
|
+
pca_result = results.perform_pca(n_components=5, color_by='top_group')
|
|
279
|
+
tsne_result = results.perform_tsne(perplexity=30, color_by='top_group')
|
|
280
|
+
umap_result = results.perform_umap(n_neighbors=15, color_by='top_group')
|
|
281
|
+
|
|
282
|
+
# Or supply your own per-molecule labels (e.g. from external metadata)
|
|
283
|
+
labels = ['industrial'] * 10 + ['environmental'] * (len(smiles_list) - 10)
|
|
284
|
+
pca_result = results.perform_pca(color_by=labels)
|
|
285
|
+
|
|
286
|
+
# Compare two datasets using KL divergence
|
|
287
|
+
other_smiles_list = smiles_list[0:10] + \
|
|
288
|
+
["OC(=O)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(O)=O",
|
|
289
|
+
"OCC(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F",
|
|
290
|
+
"FC(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)COCC=C"]
|
|
291
|
+
other_results = parse_smiles(other_smiles_list)
|
|
292
|
+
similarity = results.compare_kld(other_results, method='minmax')
|
|
293
|
+
print(f"Similarity between the lists of compounds: {similarity:2.3f}")
|
|
294
|
+
|
|
295
|
+
# Save to SQL database
|
|
296
|
+
results.to_sql(filename='results.db')
|
|
297
|
+
|
|
298
|
+
# Prioritization tool for screening and ranking
|
|
299
|
+
from PFASGroups import prioritise_molecules
|
|
300
|
+
|
|
301
|
+
# Prioritize by similarity to reference list (e.g., known persistent PFAS)
|
|
302
|
+
reference = ["FC(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(=O)O"]
|
|
303
|
+
prioritised, scores = prioritise_molecules(
|
|
304
|
+
molecules=smiles_list,
|
|
305
|
+
reference=reference,
|
|
306
|
+
return_scores=True
|
|
307
|
+
)
|
|
308
|
+
print(f"Prioritisation order: {'\n'.join([x['smiles'] for x in prioritised])}")
|
|
309
|
+
|
|
310
|
+
# Prioritize by intrinsic fluorination properties (long chains, high F content)
|
|
311
|
+
prioritised = prioritise_molecules(
|
|
312
|
+
molecules=smiles_list,
|
|
313
|
+
a=1.0, # Weight for total fluorination
|
|
314
|
+
b=5.0, # Weight for longest chains
|
|
315
|
+
percentile=90, # Focus on 90th percentile of component sizes
|
|
316
|
+
return_scores = False
|
|
317
|
+
)
|
|
318
|
+
print(f"Prioritisation order: {'\n'.join([x['smiles'] for x in prioritised])}")
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Command Line
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
# Parse SMILES strings
|
|
326
|
+
pfasgroups parse "C(C(F)(F)F)F" "FC(F)(F)C(F)(F)C(=O)O"
|
|
327
|
+
|
|
328
|
+
# Generate fingerprints
|
|
329
|
+
pfasgroups fingerprint "C(C(F)(F)F)F" --format dict
|
|
330
|
+
|
|
331
|
+
# List available PFAS groups
|
|
332
|
+
pfasgroups list-groups
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Filtering Components by Halogen, Form, and Saturation
|
|
336
|
+
|
|
337
|
+
Filter component matches by specific halogens, chemical forms, or saturation levels:
|
|
338
|
+
|
|
339
|
+
```python
|
|
340
|
+
from HalogenGroups import parse_smiles
|
|
341
|
+
|
|
342
|
+
smiles_list = ["C(C(F)(F)F)F", "FC(F)(F)C(F)(F)C(=O)O", "C(C(Cl)(Cl)Cl)Cl"]
|
|
343
|
+
|
|
344
|
+
# Filter only fluorine components
|
|
345
|
+
results_f = parse_smiles(smiles_list, halogens='F')
|
|
346
|
+
|
|
347
|
+
# Filter perfluorinated alkyl compounds
|
|
348
|
+
results_pfa = parse_smiles(smiles_list, halogens='F', saturation='per', form='alkyl')
|
|
349
|
+
|
|
350
|
+
# Filter polyfluorinated cyclic compounds
|
|
351
|
+
results_polyf_cyclic = parse_smiles(smiles_list, halogens='F', saturation='poly', form='cyclic')
|
|
352
|
+
|
|
353
|
+
# Filter multiple halogens (F and Cl)
|
|
354
|
+
results_multi = parse_smiles(smiles_list, halogens=['F', 'Cl'])
|
|
355
|
+
|
|
356
|
+
# Valid filter options:
|
|
357
|
+
# - halogens: 'F', 'Cl', 'Br', 'I', or list like ['F', 'Cl']
|
|
358
|
+
# - saturation: 'per' or 'poly' (or list like ['per', 'poly'] for both)
|
|
359
|
+
# - form: 'alkyl' or 'cyclic' (or list like ['alkyl', 'cyclic'] for both)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Embedding with Graph Metrics
|
|
363
|
+
|
|
364
|
+
The `to_array()` / `to_fingerprint()` methods accept a `component_metrics` list that
|
|
365
|
+
stacks one block of *N_G* columns per metric (default `N_G = 114` for fluorine-only).
|
|
366
|
+
|
|
367
|
+
```python
|
|
368
|
+
import numpy as np
|
|
369
|
+
from PFASGroups import parse_smiles, EMBEDDING_PRESETS
|
|
370
|
+
|
|
371
|
+
smiles = [
|
|
372
|
+
"O=C(O)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F", # PFOA
|
|
373
|
+
"O=C(O)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F", # PFHpA
|
|
374
|
+
"OCC(F)(F)C(F)(F)C(F)(F)C(F)(F)F", # 4:2 FTOH
|
|
375
|
+
"OCC(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)C(F)(F)F", # 6:2 FTOH
|
|
376
|
+
]
|
|
377
|
+
results = parse_smiles(smiles)
|
|
378
|
+
|
|
379
|
+
# --- binary embedding (default) ---
|
|
380
|
+
arr_bin = results.to_array() # shape (4, 114)
|
|
381
|
+
|
|
382
|
+
# --- preset 'best': binary + effective_graph_resistance ---
|
|
383
|
+
# Best discrimination (mean Tanimoto 0.184, outperforms TxP-PFAS 129-bit)
|
|
384
|
+
print(EMBEDDING_PRESETS['best']['description'])
|
|
385
|
+
arr_best = results.to_array(preset='best') # shape (4, 228) = 114 × 2
|
|
386
|
+
|
|
387
|
+
print(arr_best.shape)
|
|
388
|
+
|
|
389
|
+
# --- effective graph resistance directly ---
|
|
390
|
+
arr_egr = results.to_array(
|
|
391
|
+
component_metrics=['binary', 'effective_graph_resistance']
|
|
392
|
+
) # shape (4, 228)
|
|
393
|
+
|
|
394
|
+
print(arr_egr.shape)
|
|
395
|
+
|
|
396
|
+
# --- n_spacer: telomer CH₂ spacer length (encodes 'm' in 'm:n' notation) ---
|
|
397
|
+
# Zero for non-telomers; distinguishes 2:1, 4:2, 6:2 FTOHs in a single column
|
|
398
|
+
arr_ns = results.to_array(component_metrics=['n_spacer']) # shape (4, 114)
|
|
399
|
+
|
|
400
|
+
print(arr_egr.ns)
|
|
401
|
+
|
|
402
|
+
# --- ring_size: smallest ring overlapping each matched component ---
|
|
403
|
+
# Zero for acyclic groups; 5 for azoles/furans; 6 for benzene/cyclohexane
|
|
404
|
+
arr_rs = results.to_array(component_metrics=['ring_size']) # shape (4, 114)
|
|
405
|
+
|
|
406
|
+
print(arr_rs.shape)
|
|
407
|
+
|
|
408
|
+
# --- combined: EGR + n_spacer + ring_size + molecule-wide descriptors ---
|
|
409
|
+
arr_combined = results.to_array(
|
|
410
|
+
component_metrics=['binary', 'effective_graph_resistance',
|
|
411
|
+
'n_spacer', 'ring_size'],
|
|
412
|
+
molecule_metrics=['n_components', 'max_size',
|
|
413
|
+
'mean_branching', 'max_branching',
|
|
414
|
+
'mean_component_fraction', 'max_component_fraction'],
|
|
415
|
+
) # shape (4, 4*114 + 6) = (4, 462)
|
|
416
|
+
|
|
417
|
+
print(arr_combined.shape)
|
|
418
|
+
|
|
419
|
+
# --- multi-halogen: one parse per halogen, then hstack ---
|
|
420
|
+
halogens = ['F','Cl','Br','I']
|
|
421
|
+
r_hal = parse_smiles(smiles, halogens=halogens)
|
|
422
|
+
arr_4x = r_hal.to_array(component_metrics=['effective_graph_resistance'], molecule_metrics=['n_components','max_size','mean_branching'], halogens=halogens)
|
|
423
|
+
|
|
424
|
+
print(arr_4x.shape)
|
|
425
|
+
|
|
426
|
+
# --- column names ---
|
|
427
|
+
cols = results.column_names(
|
|
428
|
+
component_metrics=['binary', 'effective_graph_resistance']
|
|
429
|
+
)
|
|
430
|
+
print(cols[:4]) # e.g. ['Perfluoroalkyl [binary]', ..., 'Perfluoroalkyl [EGR]', ...]
|
|
431
|
+
|
|
432
|
+
print(arr_4x[:4])
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
See [`examples/embedding_with_graph_metrics.py`](examples/embedding_with_graph_metrics.py)
|
|
436
|
+
for a complete runnable script covering all options.
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
## Multi-Halogen Analysis
|
|
440
|
+
|
|
441
|
+
PFASGroups supports fluorine, chlorine, bromine, and iodine. There are two ways
|
|
442
|
+
to analyse all halogens at once:
|
|
443
|
+
|
|
444
|
+
### Option A – import `HalogenGroups` (all halogens by default)
|
|
445
|
+
|
|
446
|
+
```python
|
|
447
|
+
from HalogenGroups import parse_smiles
|
|
448
|
+
|
|
449
|
+
smiles_list = [
|
|
450
|
+
"C(C(F)(F)F)F", # fluorinated
|
|
451
|
+
"ClC(Cl)(Cl)C(Cl)(Cl)Cl", # chlorinated
|
|
452
|
+
"BrC(Br)(Br)CBr", # brominated
|
|
453
|
+
]
|
|
454
|
+
|
|
455
|
+
# halogens defaults to ['F','Cl','Br','I'] — no extra argument needed
|
|
456
|
+
results = parse_smiles(smiles_list) # → PFASEmbeddingSet
|
|
457
|
+
|
|
458
|
+
# to_array() reads the halogen info already captured during parsing
|
|
459
|
+
arr = results.to_array(group_selection='oecd', component_metrics=['binary'], halogens = ['F','Cl','Br','I'])
|
|
460
|
+
cols = results.column_names(group_selection='oecd')
|
|
461
|
+
|
|
462
|
+
print(arr.shape)
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### Option B – import `PFASgroups` and specify `halogens` explicitly
|
|
466
|
+
|
|
467
|
+
```python
|
|
468
|
+
from PFASgroups import parse_smiles
|
|
469
|
+
|
|
470
|
+
smiles_list = [
|
|
471
|
+
"C(C(F)(F)F)F",
|
|
472
|
+
"ClC(Cl)(Cl)C(Cl)(Cl)Cl",
|
|
473
|
+
"BrC(Br)(Br)CBr",
|
|
474
|
+
]
|
|
475
|
+
|
|
476
|
+
# Explicitly pass all halogens
|
|
477
|
+
results = parse_smiles(smiles_list, halogens=['F', 'Cl', 'Br', 'I']) # → PFASEmbeddingSet
|
|
478
|
+
|
|
479
|
+
arr = results.to_array(group_selection='oecd', component_metrics=['binary'], halogens=['F', 'Cl', 'Br', 'I'])
|
|
480
|
+
cols = results.column_names(group_selection='oecd')
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Custom Configuration
|
|
484
|
+
|
|
485
|
+
Use custom pathtype definitions and PFAS groups:
|
|
486
|
+
|
|
487
|
+
```python
|
|
488
|
+
# Load custom files entirely
|
|
489
|
+
from PFASGroups import get_componentSMARTSs, get_HalogenGroups, parse_smiles
|
|
490
|
+
from PFASGroups.core import preprocess_componentsSmarts
|
|
491
|
+
from PFASGroups.parser import preprocess_HalogenGroups
|
|
492
|
+
|
|
493
|
+
# You can load component smarts
|
|
494
|
+
custom_paths = get_componentSMARTSs()
|
|
495
|
+
# Add or modify componentSMARTSs as you want
|
|
496
|
+
# ....
|
|
497
|
+
# then preprocess them
|
|
498
|
+
custom_paths = preprocess_componentsSmarts(custom_paths)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
# You can load HalogenGroups
|
|
502
|
+
custom_groups = get_HalogenGroups()
|
|
503
|
+
# Add or modify HalogenGroups as you want
|
|
504
|
+
# ....
|
|
505
|
+
# then preprocess them
|
|
506
|
+
pfas_groups, agg_groups = preprocess_HalogenGroups(custom_groups)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
results = parse_smiles(
|
|
510
|
+
["C(C(F)(F)F)F"],
|
|
511
|
+
componentSmartss=custom_paths,
|
|
512
|
+
pfas_groups=pfas_groups,
|
|
513
|
+
agg_pfas_groups = agg_groups
|
|
514
|
+
)
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
```python
|
|
518
|
+
# Or extend defaults with your custom groups
|
|
519
|
+
from PFASGroups import get_compiled_HalogenGroups, HalogenGroup, parse_smiles, get_componentSMARTSs
|
|
520
|
+
from PFASGroups.core import preprocess_componentsSmarts
|
|
521
|
+
|
|
522
|
+
# Add custom PFAS groups
|
|
523
|
+
groups = get_compiled_HalogenGroups() # Get defaults, precompiled
|
|
524
|
+
# Create a new group
|
|
525
|
+
custom_group = HalogenGroup(
|
|
526
|
+
id=999,
|
|
527
|
+
name="My Custom Group",
|
|
528
|
+
smarts={"[C](F)(F)F": 1},
|
|
529
|
+
componentSmarts=None,
|
|
530
|
+
componentSaturation="per",
|
|
531
|
+
componentHalogens="F",
|
|
532
|
+
componentForm="alkyl",
|
|
533
|
+
constraints={"gte": {"F": 1}}
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# then add it to the lit of groups
|
|
537
|
+
groups.append(custom_group)
|
|
538
|
+
|
|
539
|
+
# pass the new set of groups as argument to parse_smiles
|
|
540
|
+
results = parse_smiles(["FC(F)(F)C(F)(F)[N+](=O)[O-]"], pfas_groups=groups)
|
|
541
|
+
|
|
542
|
+
# Alternatively, select and modify some existing default_groups
|
|
543
|
+
from PFASGroups import get_HalogenGroups, HalogenGroup
|
|
544
|
+
# get and subset the default groups
|
|
545
|
+
default_groups = get_HalogenGroups()[2:4]
|
|
546
|
+
# extract one group
|
|
547
|
+
modified_group = default_groups[1]
|
|
548
|
+
# modify its parameters, here simply require presence of at least two structures
|
|
549
|
+
modified_group['smarts']={x:2 for x, i in modified_group['smarts'].items()}
|
|
550
|
+
# replace the modified group in the groups list
|
|
551
|
+
default_groups[1] = modified_group
|
|
552
|
+
# compile the groups
|
|
553
|
+
compiled_groups = [HalogenGroup(**x) for x in default_groups]
|
|
554
|
+
|
|
555
|
+
# Possibly add another group with max_dist_from_comp = 3
|
|
556
|
+
compiled_groups.append(HalogenGroup(
|
|
557
|
+
id=998,
|
|
558
|
+
name="Extended Distance Group",
|
|
559
|
+
smarts={"[#6$([#6][OH1])]": 1},
|
|
560
|
+
componentSmarts=None,
|
|
561
|
+
constraints={},
|
|
562
|
+
max_dist_from_comp=3 # Allow up to 3 bonds from fluorinated carbon
|
|
563
|
+
))
|
|
564
|
+
|
|
565
|
+
# parse the groups:
|
|
566
|
+
results = parse_smiles(["FC(F)(F)C(F)(F)[N+](=O)[O-]"], pfas_groups=compiled_groups)
|
|
567
|
+
|
|
568
|
+
# Add or modify custom path types (e.g., chlorinated analogs)
|
|
569
|
+
paths = get_componentSMARTSs()
|
|
570
|
+
paths['Cl']['alkyl'] = {"per":{
|
|
571
|
+
"component":"[C;X4](Cl)(Cl)!@!=!#[C;X4](Cl)(Cl)",
|
|
572
|
+
"name":"Custom Chloro"}}
|
|
573
|
+
paths = preprocess_componentsSmarts(paths)
|
|
574
|
+
|
|
575
|
+
results = parse_smiles(["ClC(Cl)(Cl)C(Cl)(Cl)C(=O)O"], halogens = 'Cl', componentSmartss=paths)
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
## Documentation
|
|
579
|
+
|
|
580
|
+
- **[USER_GUIDE.md](USER_GUIDE.md)** - Complete documentation with examples
|
|
581
|
+
- **[QUICK_REFERENCE.md](QUICK_REFERENCE.md)** - Quick reference for common tasks
|
|
582
|
+
|
|
583
|
+
## Usage Examples
|
|
584
|
+
|
|
585
|
+
See [USER_GUIDE.md](USER_GUIDE.md) for comprehensive examples including:
|
|
586
|
+
- Basic PFAS parsing and analysis
|
|
587
|
+
- Fingerprint generation for machine learning
|
|
588
|
+
- Custom configuration files
|
|
589
|
+
- Batch processing
|
|
590
|
+
- Integration with pandas and scikit-learn
|
|
591
|
+
|
|
592
|
+
## Summary of changes by version
|
|
593
|
+
|
|
594
|
+
- **Version 3.2.2**: Fixed polyhalogenated alkyl components matching less than 2 halogens. Added option to pass 'halogens' to formula constraints. Added options to include component-wide formula constraints (on dist-1 neighbours from matched C-only components).
|
|
595
|
+
|
|
596
|
+
- **Version 3.2.1**: Added n-spacer metric for telomers and ring size for aryl and cyclic groups. These can be used in the embeddings.
|
|
597
|
+
|
|
598
|
+
- **Version 3.2.0**: Use of BDE for computing graph resistance.
|
|
599
|
+
|
|
600
|
+
- **Version 3.1.4**: Changed fingerprint parameters to molecule wide and component wide, with pre-configuration for best combinations.
|
|
601
|
+
|
|
602
|
+
- **Version 3.1.0**: Added support for other halogens, changed names to be more generic (with some support for backward compatibility). Added component smarts for other halogens, cyclic and alkyl components.
|
|
603
|
+
|
|
604
|
+
- **Version 2.2.4 (Feb 2026)**: Advanced fingerprint analysis with dimensionality reduction (PCA, kernel-PCA, t-SNE, UMAP), KL divergence comparison for dataset similarity assessment, and SQL persistence for results. Added molecule prioritization tool for screening applications, ranking by similarity to reference lists or by intrinsic fluorination properties. Introduced `PFASEmbeddingSet` and `PFASEmbedding` with comprehensive analysis methods, automated plot generation, and extensive documentation.
|
|
605
|
+
|
|
606
|
+
- **Version 2.2.3 (Feb 2026)**: Added `PFASEmbeddingSet` container to offer easier plotting and summarising capabilities for results.
|
|
607
|
+
|
|
608
|
+
- **Version 2.2 (Feb 2026)**: Added linked_smarts option to specify a restriction on path between smarts groups and fluorinated component. Added new PFASgroups (telomers). **v2.2.2** Fixed telomers and added examples and counter-examples to each PFASgroup. Removed boundary O in fluorinated components (for both Per and Polyalkyl components).
|
|
609
|
+
|
|
610
|
+
- **Version 2.1 (Jan 2026)**: Added support for multiple smarts, with individual minimum count, per PFASgroup.
|
|
611
|
+
|
|
612
|
+
- **Version 2.0 (Jan 2026)**: Major expansion of graph‑based component metrics, new coverage statistics, schema updates, and richer per‑component outputs.
|
|
613
|
+
|
|
614
|
+
### Version 2.2.4 (February 2026) - Advanced Fingerprint Analysis
|
|
615
|
+
|
|
616
|
+
Major enhancement adding comprehensive dimensionality reduction and statistical comparison capabilities:
|
|
617
|
+
|
|
618
|
+
**New Features:**
|
|
619
|
+
- **PFASEmbeddingSet / PFASEmbedding**: Unified result container with flexible embedding generation
|
|
620
|
+
- **Dimensionality Reduction**: PCA, kernel-PCA, t-SNE, and UMAP with automatic plotting
|
|
621
|
+
- **Statistical Comparison**: KL divergence for comparing dataset compositions
|
|
622
|
+
- **Database Persistence**: SQL save/load for results
|
|
623
|
+
- **Molecule Prioritization**: Screening and ranking tool for PFAS datasets
|
|
624
|
+
- **Comprehensive Documentation**: Complete API reference, examples, and 100+ tests
|
|
625
|
+
|
|
626
|
+
**Key Methods:**
|
|
627
|
+
- `PFASEmbeddingSet.to_array()`: Generate numeric embedding matrix from parsed results
|
|
628
|
+
- `PFASEmbeddingSet.column_names()`: Column labels matching `to_array()` output
|
|
629
|
+
- `PFASEmbeddingSet.perform_pca()`: Principal Component Analysis
|
|
630
|
+
- `PFASEmbeddingSet.perform_kernel_pca()`: Non-linear kernel PCA
|
|
631
|
+
- `PFASEmbeddingSet.perform_tsne()`: t-SNE visualization
|
|
632
|
+
- `PFASEmbeddingSet.perform_umap()`: Fast UMAP dimensionality reduction
|
|
633
|
+
- `PFASEmbeddingSet.compare_kld()`: Dataset similarity via KL divergence
|
|
634
|
+
- `prioritise_molecules()`: Rank molecules by similarity or fluorination properties
|
|
635
|
+
- `PFASEmbeddingSet.to_sql()`: Persist results to a SQLite/PostgreSQL database
|
|
636
|
+
|
|
637
|
+
**Prioritization Strategies:**
|
|
638
|
+
- **Reference-based**: Rank by distributional similarity to known PFAS (e.g., persistent chemicals)
|
|
639
|
+
- **Intrinsic properties**: Score by fluorination characteristics (total F content, chain length)
|
|
640
|
+
- **Flexible weighting**: Tune parameters for different screening objectives
|
|
641
|
+
|
|
642
|
+
**Use Cases:**
|
|
643
|
+
- Exploratory data analysis of PFAS inventories
|
|
644
|
+
- Database comparison and compositional analysis
|
|
645
|
+
- Cluster identification and pattern recognition
|
|
646
|
+
- Machine learning preprocessing
|
|
647
|
+
- Chemical space visualization
|
|
648
|
+
- Priority screening for environmental monitoring
|
|
649
|
+
- Regulatory watchlist generation
|
|
650
|
+
|
|
651
|
+
See `docs/ResultsFingerprint_Guide.md`, `docs/prioritization.rst`, `examples/results_fingerprint_analysis.py`, and `examples/prioritization_examples.py` for details.
|
|
652
|
+
|
|
653
|
+
### Version 2.2.3 (February 2026) - PFASEmbeddingSet Container
|
|
654
|
+
|
|
655
|
+
**New Features:**
|
|
656
|
+
- `PFASEmbeddingSet` container with visualization helpers
|
|
657
|
+
- Enhanced component plotting utilities
|
|
658
|
+
- Improved documentation and examples
|
|
659
|
+
|
|
660
|
+
### Version 2.0 (January 2026) - Comprehensive Graph Metrics
|
|
661
|
+
|
|
662
|
+
Major enhancement adding comprehensive NetworkX graph theory metrics for detailed component analysis:
|
|
663
|
+
|
|
664
|
+
**New Features:**
|
|
665
|
+
- **Component-Level Metrics**: Each fluorinated component now includes 15+ graph metrics:
|
|
666
|
+
- `diameter` and `radius` - Graph eccentricity bounds
|
|
667
|
+
- `center`, `periphery`, `barycenter` - Structural node sets
|
|
668
|
+
- `effective_graph_resistance` - Sum of resistance distances
|
|
669
|
+
- `component_fraction` - Fraction of molecule covered by component (includes all attached H, F, Cl, Br, I)
|
|
670
|
+
- Distance metrics from functional groups to structural features
|
|
671
|
+
- **Molecular Coverage Metrics**: New fraction-based metrics quantify fluorination extent:
|
|
672
|
+
- `mean_component_fraction` - Average coverage per component
|
|
673
|
+
- `total_components_fraction` - Total coverage by union of all components (accounts for overlaps)
|
|
674
|
+
- **Summary Statistics**: Aggregated metrics across all components per PFAS group
|
|
675
|
+
- **Enhanced Database Models**: New `Components` model stores individual component data with all metrics
|
|
676
|
+
- **Improved Analysis**: Better understanding of molecular topology, branching, functional group positioning, and fluorination extent
|
|
677
|
+
|
|
678
|
+
**Breaking Changes:**
|
|
679
|
+
- `parse_mols` output now includes additional summary metric fields (`mean_diameter`, `mean_radius`, etc.)
|
|
680
|
+
- Database schema changes require migration (see `DATABASE_MIGRATION_GUIDE.md`)
|
|
681
|
+
|
|
682
|
+
**Metrics Explained:**
|
|
683
|
+
- `branching` (0-1): Measures linearity (1.0 = linear, 0.0 = highly branched) - renamed from "eccentricity"
|
|
684
|
+
- `mean_eccentricity`, `median_eccentricity`: Graph-theoretic eccentricity statistics for component nodes
|
|
685
|
+
- `smarts_centrality` (0-1): Functional group position (1.0 = central, 0.0 = peripheral)
|
|
686
|
+
- `n_spacer` (int ≥ 0): Fluorotelomer CH₂ spacer length — the "m" in "m:n" telomer notation; 0 for all non-telomeric groups
|
|
687
|
+
- `ring_size` (int ≥ 0): Smallest ring overlapping the matched component; 0 for acyclic chains, 5 for azoles/furans, 6 for benzene/cyclohexane derivatives
|
|
688
|
+
- `component_fraction` (0-1): Fraction of total molecule atoms in this component (includes all attached atoms)
|
|
689
|
+
- `total_components_fraction` (0-1): Fraction of molecule covered by union of all components
|
|
690
|
+
- `diameter`: Maximum distance between any two atoms in component
|
|
691
|
+
- `radius`: Minimum eccentricity across component nodes
|
|
692
|
+
- `barycenter`: Nodes minimizing total distance to all other nodes
|
|
693
|
+
- `center`: Nodes with minimum eccentricity
|
|
694
|
+
- `periphery`: Nodes with maximum eccentricity
|
|
695
|
+
|
|
696
|
+
See `COMPREHENSIVE_METRICS_SUMMARY.md` for complete documentation.
|
|
697
|
+
|
|
698
|
+
- **Version 1.x**: Shift to component‑based analysis with improved SMARTS matching and better handling of branched/cyclic structures.
|
|
699
|
+
|
|
700
|
+
### Version 1.x - Component-Based Analysis
|
|
701
|
+
|
|
702
|
+
- Replaced chain-finding with connected component analysis
|
|
703
|
+
- Added support for branched and cyclic structures
|
|
704
|
+
- Improved SMARTS pattern matching for diverse PFAS classes
|
|
705
|
+
|
|
706
|
+
### Version 0.x - Path-Based Analysis
|
|
707
|
+
|
|
708
|
+
- Find SMARTS match connected to either a second SMARTS or a default path-related SMARTS using networkx shortest_path.
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
## Licence
|
|
713
|
+
<a rel="license" href="http://creativecommons.org/licenses/by-nd/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-nd/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nd/4.0/">Creative Commons Attribution-NoDerivatives 4.0 International License</a>.
|
|
714
|
+
|
|
715
|
+
Contact me in case you want an exception to the No Derivatives term.
|
|
716
|
+
|
|
717
|
+
## Acknowledgments
|
|
718
|
+
This project is part of the [ZeroPM project](https://zeropm.eu/) (WP2) and has received funding from the European Union’s Horizon 2020 research and innovation programme under grant agreement No 101036756. This work was developed at the [Department of Environmental Science](https://aces.su.se) at Stockholm University.<br />
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
<img alt="EU logo" src="https://zeropm.eu/wp-content/uploads/2021/12/flag_yellow_low.jpg" width=100/> <a rel='zeropm_web' href="https://zeropm.eu/"/><img alt="zeropm logo" src="https://zeropm.eu/wp-content/uploads/2022/01/ZeroPM-logo.png" width=250 /></a><a rel='zeropm_web' href="https://su.se/"/><img alt="zeropm logo" src="https://eu01web.zoom.us/account/branding/p/5065401a-9915-4baa-9c16-665dcd743470.png" width=200 /></a>
|
|
722
|
+
|
|
723
|
+
[](https://www.rdkit.org/)
|
|
724
|
+
|