polymon 1.0.2__tar.gz → 1.0.3__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.
- {polymon-1.0.2 → polymon-1.0.3}/PKG-INFO +2 -1
- polymon-1.0.3/README.md +240 -0
- polymon-1.0.3/polymon/__init__.py +1 -0
- polymon-1.0.3/polymon/cli/main.py +363 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/cli/merge.py +63 -1
- polymon-1.0.3/polymon/cli/predict.py +84 -0
- polymon-1.0.3/polymon/cli/recommend.py +154 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/cli/train_ml.py +4 -4
- {polymon-1.0.2 → polymon-1.0.3}/polymon/data/featurizer.py +1 -1
- polymon-1.0.3/polymon/estimator/Tg_atom_contrib.npy +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/__init__.py +4 -0
- polymon-1.0.3/polymon/estimator/tg.py +2553 -0
- polymon-1.0.3/polymon/exp/acquisition.py +334 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/base.py +6 -5
- {polymon-1.0.2 → polymon-1.0.3}/polymon.egg-info/PKG-INFO +2 -1
- {polymon-1.0.2 → polymon-1.0.3}/polymon.egg-info/SOURCES.txt +4 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon.egg-info/requires.txt +1 -0
- {polymon-1.0.2 → polymon-1.0.3}/setup.py +1 -0
- polymon-1.0.2/README.md +0 -42
- polymon-1.0.2/polymon/__init__.py +0 -1
- polymon-1.0.2/polymon/cli/main.py +0 -330
- polymon-1.0.2/polymon/cli/predict.py +0 -38
- {polymon-1.0.2 → polymon-1.0.3}/polymon/cli/__init__.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/cli/train_dl.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/data/__init__.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/data/_dmpnn_transform.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/data/_pretrained.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/data/cgcnn.json +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/data/dataset.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/data/dedup.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/data/mordred_unstable.txt +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/data/polymer.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/data/utils.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/data/xenonpy_elements.csv +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/FFV_atom_contrib.npy +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/Tc_atom_contrib.npy +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/atom_contrib.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/base.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/density.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/density_Fedors.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/density_ibm.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/low_fidelity.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/ml.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/nx_rg.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/rg.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/exp/__init__.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/exp/pipeline.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/exp/score.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/exp/train.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/exp/utils.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/hparams.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/__init__.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/dmpnn.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/ensemble.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/__init__.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/embed_residual.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/fastkan_gatv2.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/gat_chain_readout.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/gatv2_sage.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/kan_gatv2.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/lineevo.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/multi_fidelity.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/position_encoding.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gnn.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gps/__init__.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gps/conv.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gps/gps.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gvp.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/__init__.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/dmpnn.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/efficient_kan.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/fast_kan.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/fourier_kan.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/gcn.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/gin.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/vanilla.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/mlp.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/polycl/__init__.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/polycl/polycl.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/register.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/model/utils.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon/setting.py +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon.egg-info/dependency_links.txt +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon.egg-info/entry_points.txt +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/polymon.egg-info/top_level.txt +0 -0
- {polymon-1.0.2 → polymon-1.0.3}/setup.cfg +0 -0
polymon-1.0.3/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
<img src="assets/polymon.png" alt="Polymon Icon">
|
|
2
|
+
|
|
3
|
+
`PolyMon` is a unified framework for polymer property prediction. It combines traditional machine learning methods (Random Forest, XGBoost, LightGBM, CatBoost, TabPFN) with state-of-the-art deep learning models (Graph Neural Networks including GATv2, GIN, PNA, DimeNet++, and KAN-based architectures).
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="assets/framework.png" alt="framework" width="400">
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Multiple Model Types**: Support for both tabular ML models and graph-based deep learning models
|
|
12
|
+
- **Flexible Training Strategies**: K-fold cross-validation, hyperparameter optimization, ensemble learning, multi-fidelity learning, and active learning
|
|
13
|
+
- **Comprehensive Descriptors**: RDKit 2D/3D descriptors, ECFP fingerprints, Mordred descriptors, and graph-based representations
|
|
14
|
+
- **Multiple Properties**: Predict glass transition temperature (Tg), fractional free volume (FFV), radius of gyration (Rg), density, and thermal conductivity (Tc)
|
|
15
|
+
- **Easy-to-Use CLI**: Simple command-line interface for training, prediction, and active learning recommendations
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
### Prerequisites
|
|
20
|
+
|
|
21
|
+
This package requires `torch>=2.2.2` and `torch_geometric>=2.5.3`. We recommend installing PyTorch with CUDA support first.
|
|
22
|
+
|
|
23
|
+
> **Note:** RDKit (a required dependency) is not yet compatible with NumPy 2.x. If you have NumPy 2.x installed, downgrade it with `pip install 'numpy<2'` before installing PolyMon.
|
|
24
|
+
|
|
25
|
+
### Step 1: Install PyTorch and PyTorch Geometric
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# For CUDA 11.8
|
|
29
|
+
conda install -y pytorch==2.3.0 torchvision==0.18.0 torchaudio==2.3.0 \
|
|
30
|
+
pytorch-cuda=11.8 -c pytorch -c nvidia
|
|
31
|
+
pip install torch_geometric
|
|
32
|
+
pip install torch_scatter torch_sparse -f https://data.pyg.org/whl/torch-2.3.0+cu118.html
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Step 2: Install PolyMon
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install polymon
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Development Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
git clone https://github.com/fate1997/polymon.git
|
|
45
|
+
cd polymon
|
|
46
|
+
pip install -e .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
### Training a Model
|
|
52
|
+
|
|
53
|
+
Train a tabular model (e.g., Random Forest) with RDKit 2D descriptors:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
polymon train \
|
|
57
|
+
--raw-csv ./database/database.csv \
|
|
58
|
+
--sources Kaggle PI1070 PolyMetriX \
|
|
59
|
+
--labels Tg \
|
|
60
|
+
--feature-names rdkit2d \
|
|
61
|
+
--model rf \
|
|
62
|
+
--n-fold 5 \
|
|
63
|
+
--out-dir ./results
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Train a graph neural network (GNN) model:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
polymon train \
|
|
70
|
+
--raw-csv ./database/database.csv \
|
|
71
|
+
--sources Kaggle PI1070 PolyMetriX \
|
|
72
|
+
--labels Tg \
|
|
73
|
+
--model gatv2 \
|
|
74
|
+
--n-fold 5 \
|
|
75
|
+
--n-trials 15 \
|
|
76
|
+
--num-epochs 2500 \
|
|
77
|
+
--out-dir ./results
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Making Predictions
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
polymon predict \
|
|
84
|
+
--model-path ./results/gatv2/Tg/train/gatv2_Tg.pt \
|
|
85
|
+
--csv-path ./data/new_polymers.csv \
|
|
86
|
+
--smiles-column SMILES
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Active Learning Recommendations
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
polymon rec \
|
|
93
|
+
--pool-csv ./database/pool.csv \
|
|
94
|
+
--trained-model ./results/gatv2/Rg/train/gatv2_Rg-KFold.pt \
|
|
95
|
+
--acquisition uncertainty \
|
|
96
|
+
--sample-size 20 \
|
|
97
|
+
--save-path recommended.csv
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Available Models
|
|
101
|
+
|
|
102
|
+
### Tabular Models (for use with `--feature-names`)
|
|
103
|
+
|
|
104
|
+
| Model | CLI Name | Description |
|
|
105
|
+
|-------|----------|-------------|
|
|
106
|
+
| Random Forest | `rf` | Ensemble of decision trees |
|
|
107
|
+
| XGBoost | `xgb` | Gradient boosting framework |
|
|
108
|
+
| LightGBM | `lgbm` | Light gradient boosting machine |
|
|
109
|
+
| CatBoost | `catboost` | Gradient boosting on decision trees |
|
|
110
|
+
| TabPFN | `tabpfn` | Prior-data trained network |
|
|
111
|
+
|
|
112
|
+
### Deep Learning Models
|
|
113
|
+
|
|
114
|
+
| Model | CLI Name | Description |
|
|
115
|
+
|-------|----------|-------------|
|
|
116
|
+
| GATv2 | `gatv2` | Graph Attention Network v2 |
|
|
117
|
+
| GIN | `gin` | Graph Isomorphism Network |
|
|
118
|
+
| PNA | `pna` | Principal Neighbourhood Aggregation |
|
|
119
|
+
| AttentiveFP | `attentivefp` | Attention-based molecular fingerprinting |
|
|
120
|
+
| DimeNet++ | `dimenetpp` | Directional message passing |
|
|
121
|
+
| GPS | `gps` | Graph Positional Encoding network |
|
|
122
|
+
| KAN-GATv2 | `fastkan_gatv2` | Kolmogorov-Arnold Network + GATv2 |
|
|
123
|
+
| KAN-GPS | `kan_gps` | Kolmogorov-Arnold Network + GPS |
|
|
124
|
+
|
|
125
|
+
## Available Descriptors
|
|
126
|
+
|
|
127
|
+
### For Tabular Models (`--feature-names`)
|
|
128
|
+
|
|
129
|
+
- `rdkit2d`: RDKit 2D molecular descriptors
|
|
130
|
+
- `ecfp4`: Extended Connectivity Fingerprints (ECFP4)
|
|
131
|
+
- `mordred`: 1800+ Mordred descriptors
|
|
132
|
+
- `maccs`: MACCS keys
|
|
133
|
+
- `xenonpy_desc`: XenonPy elemental composition descriptors
|
|
134
|
+
|
|
135
|
+
### For Graph Models
|
|
136
|
+
|
|
137
|
+
Graph models automatically use molecular graph features. Additional descriptors can be added via `--descriptors`:
|
|
138
|
+
- `rdkit2d`, `ecfp4`, `mordred`, `maccs`, `xenonpy_desc`
|
|
139
|
+
- `oligomer_rdkit2d`, `oligomer_mordred`, `oligomer_ecfp4` (for oligomer representations)
|
|
140
|
+
|
|
141
|
+
## Target Properties
|
|
142
|
+
|
|
143
|
+
| Property | Symbol | Unit | Description |
|
|
144
|
+
|----------|--------|------|-------------|
|
|
145
|
+
| Glass Transition Temperature | `Tg` | K | Temperature at which polymer transitions from glassy to rubbery |
|
|
146
|
+
| Fractional Free Volume | `FFV` | - | Fraction of volume not occupied by polymer chains |
|
|
147
|
+
| Radius of Gyration | `Rg` | Å | Measure of polymer chain size |
|
|
148
|
+
| Density | `Density` | g/cm³ | Mass per unit volume |
|
|
149
|
+
| Thermal Conductivity | `Tc` | W/m·K | Heat transfer capability |
|
|
150
|
+
|
|
151
|
+
## Advanced Usage
|
|
152
|
+
|
|
153
|
+
### Hyperparameter Optimization
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
polymon train \
|
|
157
|
+
--labels Tg \
|
|
158
|
+
--model gatv2 \
|
|
159
|
+
--n-trials 15 \
|
|
160
|
+
--n-fold 5 \
|
|
161
|
+
--raw-csv ./database/database.csv
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Multi-Fidelity Learning (Fine-tuning)
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Train on low-fidelity data first
|
|
168
|
+
polymon train \
|
|
169
|
+
--labels Density \
|
|
170
|
+
--model gatv2 \
|
|
171
|
+
--sources MD-simulation \
|
|
172
|
+
--run-production
|
|
173
|
+
|
|
174
|
+
# Fine-tune on high-fidelity data
|
|
175
|
+
polymon train \
|
|
176
|
+
--labels Density \
|
|
177
|
+
--model gatv2 \
|
|
178
|
+
--sources Experimental \
|
|
179
|
+
--finetune \
|
|
180
|
+
--pretrained-model ./results/gatv2/Density/production/gatv2_Density.pt \
|
|
181
|
+
--finetune-csv-path ./database/experimental.csv
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Ensemble Learning
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
polymon train \
|
|
188
|
+
--labels Rg \
|
|
189
|
+
--model gatv2 \
|
|
190
|
+
--n-estimator 10 \
|
|
191
|
+
--ensemble-type voting \
|
|
192
|
+
--raw-csv ./database/database.csv
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Delta-Learning with Empirical Estimators
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
polymon train \
|
|
199
|
+
--labels Density \
|
|
200
|
+
--model gatv2 \
|
|
201
|
+
--train-residual \
|
|
202
|
+
--estimator-name Density-IBM \
|
|
203
|
+
--raw-csv ./database/database.csv
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Python API
|
|
207
|
+
|
|
208
|
+
For more advanced usage, you can use the Python API directly:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from polymon.model.base import ModelWrapper
|
|
212
|
+
|
|
213
|
+
# Load a trained model
|
|
214
|
+
model = ModelWrapper.from_file('results/gatv2/Tg/train/gatv2_Tg.pt')
|
|
215
|
+
|
|
216
|
+
# Make predictions
|
|
217
|
+
predictions = model.predict(['*C*', '*CC*', '*CCC*'])
|
|
218
|
+
print(predictions)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Citation
|
|
222
|
+
|
|
223
|
+
If you use PolyMon in your research, please cite:
|
|
224
|
+
|
|
225
|
+
```bibtex
|
|
226
|
+
@article{polymon2024,
|
|
227
|
+
title={PolyMon: A Unified Framework for Polymer Property Prediction},
|
|
228
|
+
author={Ren, Gaopeng and Yang, Yijie and Zhou, Jiajun and Jelfs, Kim E.},
|
|
229
|
+
journal={Journal of Chemical Information and Modeling},
|
|
230
|
+
year={2024}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## License
|
|
235
|
+
|
|
236
|
+
This project is licensed under the MIT License.
|
|
237
|
+
|
|
238
|
+
## Contributing
|
|
239
|
+
|
|
240
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.0.3'
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""PolyMon Command-Line Interface.
|
|
2
|
+
|
|
3
|
+
This module provides the main CLI entry point for the PolyMon framework.
|
|
4
|
+
It supports three main modes:
|
|
5
|
+
- train: Train ML/DL models for polymer property prediction
|
|
6
|
+
- rec: Active learning - recommend molecules for labeling
|
|
7
|
+
- predict: Make predictions on new polymer data
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
Train a model:
|
|
11
|
+
$ polymon train --labels Tg --model rf --n-fold 5
|
|
12
|
+
|
|
13
|
+
Make predictions:
|
|
14
|
+
$ polymon predict --model-path model.pt --csv-path data.csv --smiles-column SMILES
|
|
15
|
+
|
|
16
|
+
Recommend molecules:
|
|
17
|
+
$ polymon rec --pool-csv pool.csv --trained-model model.pt --acquisition uncertainty
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import argparse
|
|
21
|
+
|
|
22
|
+
from polymon.cli.train_dl import main as main_dl
|
|
23
|
+
from polymon.cli.train_ml import MODELS
|
|
24
|
+
from polymon.cli.train_ml import main as main_ml
|
|
25
|
+
from polymon.cli.predict import main as main_predict
|
|
26
|
+
from polymon.cli.recommend import main as main_recommend
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def parse_args():
|
|
30
|
+
"""Parse command-line arguments for the PolyMon CLI.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
argparse.Namespace: Parsed arguments with the following attributes:
|
|
34
|
+
- mode (str): Subcommand to run ('train', 'rec', or 'predict')
|
|
35
|
+
- ... (additional arguments depend on the subcommand)
|
|
36
|
+
"""
|
|
37
|
+
parser = argparse.ArgumentParser(
|
|
38
|
+
description='PolyMon - Polymer Property Prediction Framework',
|
|
39
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
40
|
+
)
|
|
41
|
+
subparsers = parser.add_subparsers(dest='mode', required=True)
|
|
42
|
+
|
|
43
|
+
# ============================================================================
|
|
44
|
+
# Train subcommand
|
|
45
|
+
# ============================================================================
|
|
46
|
+
train_parser = subparsers.add_parser(
|
|
47
|
+
'train',
|
|
48
|
+
help='Train a ML/DL model for polymer property prediction',
|
|
49
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
50
|
+
)
|
|
51
|
+
train_parser.add_argument(
|
|
52
|
+
'--raw-csv',
|
|
53
|
+
type=str,
|
|
54
|
+
default='database/database.csv',
|
|
55
|
+
help='Path to the raw CSV file containing polymer data'
|
|
56
|
+
)
|
|
57
|
+
train_parser.add_argument(
|
|
58
|
+
'--sources',
|
|
59
|
+
type=str,
|
|
60
|
+
nargs='+',
|
|
61
|
+
default=['Kaggle'],
|
|
62
|
+
help='Data sources to filter from the dataset (e.g., Kaggle, PI1070, PolyMetriX)'
|
|
63
|
+
)
|
|
64
|
+
train_parser.add_argument(
|
|
65
|
+
'--tag',
|
|
66
|
+
type=str,
|
|
67
|
+
default='debug',
|
|
68
|
+
help='Tag for organizing this training run'
|
|
69
|
+
)
|
|
70
|
+
train_parser.add_argument(
|
|
71
|
+
'--labels',
|
|
72
|
+
nargs='+',
|
|
73
|
+
required=True,
|
|
74
|
+
choices=['Tg', 'FFV', 'Density', 'Rg', 'Tc'],
|
|
75
|
+
help='Target property/properties to predict'
|
|
76
|
+
)
|
|
77
|
+
train_parser.add_argument(
|
|
78
|
+
'--feature-names',
|
|
79
|
+
type=str,
|
|
80
|
+
nargs='+',
|
|
81
|
+
default=['rdkit2d'],
|
|
82
|
+
help='Feature names for tabular models (rdkit2d, ecfp4, mordred, maccs, xenonpy_desc)'
|
|
83
|
+
)
|
|
84
|
+
train_parser.add_argument(
|
|
85
|
+
'--n-trials',
|
|
86
|
+
type=int,
|
|
87
|
+
default=None,
|
|
88
|
+
help='Number of trials for hyperparameter optimization (enables optimization if set)'
|
|
89
|
+
)
|
|
90
|
+
train_parser.add_argument(
|
|
91
|
+
'--out-dir',
|
|
92
|
+
type=str,
|
|
93
|
+
default='./results',
|
|
94
|
+
help='Directory to save training results'
|
|
95
|
+
)
|
|
96
|
+
train_parser.add_argument(
|
|
97
|
+
'--hparams-from',
|
|
98
|
+
type=str,
|
|
99
|
+
default=None,
|
|
100
|
+
help='Path to hyperparameters file (.json, .pt, or .pkl) to reuse from previous run'
|
|
101
|
+
)
|
|
102
|
+
train_parser.add_argument(
|
|
103
|
+
'--n-fold',
|
|
104
|
+
type=int,
|
|
105
|
+
default=1,
|
|
106
|
+
help='Number of folds for cross-validation (use 1 for single split)'
|
|
107
|
+
)
|
|
108
|
+
train_parser.add_argument(
|
|
109
|
+
'--split-mode',
|
|
110
|
+
type=str,
|
|
111
|
+
default='random',
|
|
112
|
+
choices=['random', 'source', 'scaffold'],
|
|
113
|
+
help='Data splitting strategy: random, by source, or by molecular scaffold'
|
|
114
|
+
)
|
|
115
|
+
train_parser.add_argument(
|
|
116
|
+
'--seed',
|
|
117
|
+
type=int,
|
|
118
|
+
default=42,
|
|
119
|
+
help='Random seed for reproducibility'
|
|
120
|
+
)
|
|
121
|
+
train_parser.add_argument(
|
|
122
|
+
'--remove-hydrogens',
|
|
123
|
+
action='store_true',
|
|
124
|
+
help='Remove hydrogens from molecular graphs (reduces computation)'
|
|
125
|
+
)
|
|
126
|
+
train_parser.add_argument(
|
|
127
|
+
'--descriptors',
|
|
128
|
+
type=str,
|
|
129
|
+
nargs='+',
|
|
130
|
+
default=None,
|
|
131
|
+
help='Additional descriptors to concatenate with graph features for DL models'
|
|
132
|
+
)
|
|
133
|
+
train_parser.add_argument(
|
|
134
|
+
'--model',
|
|
135
|
+
type=str,
|
|
136
|
+
default='rf',
|
|
137
|
+
help='Model type: tabular (rf, xgb, lgbm, catboost, tabpfn) or GNN (gatv2, gin, pna, etc.)'
|
|
138
|
+
)
|
|
139
|
+
train_parser.add_argument(
|
|
140
|
+
'--hidden-dim',
|
|
141
|
+
type=int,
|
|
142
|
+
default=32,
|
|
143
|
+
help='Hidden dimension for neural network models'
|
|
144
|
+
)
|
|
145
|
+
train_parser.add_argument(
|
|
146
|
+
'--num-layers',
|
|
147
|
+
type=int,
|
|
148
|
+
default=3,
|
|
149
|
+
help='Number of layers for neural network models'
|
|
150
|
+
)
|
|
151
|
+
train_parser.add_argument(
|
|
152
|
+
'--batch-size',
|
|
153
|
+
type=int,
|
|
154
|
+
default=128,
|
|
155
|
+
help='Batch size for training neural networks'
|
|
156
|
+
)
|
|
157
|
+
train_parser.add_argument(
|
|
158
|
+
'--lr',
|
|
159
|
+
type=float,
|
|
160
|
+
default=1e-3,
|
|
161
|
+
help='Learning rate for neural network training'
|
|
162
|
+
)
|
|
163
|
+
train_parser.add_argument(
|
|
164
|
+
'--num-epochs',
|
|
165
|
+
type=int,
|
|
166
|
+
default=2500,
|
|
167
|
+
help='Maximum number of training epochs'
|
|
168
|
+
)
|
|
169
|
+
train_parser.add_argument(
|
|
170
|
+
'--early-stopping-patience',
|
|
171
|
+
type=int,
|
|
172
|
+
default=250,
|
|
173
|
+
help='Patience for early stopping (epochs without improvement)'
|
|
174
|
+
)
|
|
175
|
+
train_parser.add_argument(
|
|
176
|
+
'--device',
|
|
177
|
+
type=str,
|
|
178
|
+
default='cuda',
|
|
179
|
+
help='Device for training (cuda or cpu)'
|
|
180
|
+
)
|
|
181
|
+
train_parser.add_argument(
|
|
182
|
+
'--run-production',
|
|
183
|
+
action='store_true',
|
|
184
|
+
help='Run in production mode (95:5 train:val split, no test set)'
|
|
185
|
+
)
|
|
186
|
+
train_parser.add_argument(
|
|
187
|
+
'--finetune',
|
|
188
|
+
action='store_true',
|
|
189
|
+
help='Fine-tune a pretrained model on new data'
|
|
190
|
+
)
|
|
191
|
+
train_parser.add_argument(
|
|
192
|
+
'--finetune-csv-path',
|
|
193
|
+
type=str,
|
|
194
|
+
default=None,
|
|
195
|
+
help='Path to CSV file for fine-tuning data'
|
|
196
|
+
)
|
|
197
|
+
train_parser.add_argument(
|
|
198
|
+
'--pretrained-model',
|
|
199
|
+
type=str,
|
|
200
|
+
default=None,
|
|
201
|
+
help='Path to pretrained model for fine-tuning'
|
|
202
|
+
)
|
|
203
|
+
train_parser.add_argument(
|
|
204
|
+
'--n-estimator',
|
|
205
|
+
type=int,
|
|
206
|
+
default=1,
|
|
207
|
+
help='Number of estimators for ensemble learning (>1 enables ensemble mode)'
|
|
208
|
+
)
|
|
209
|
+
train_parser.add_argument(
|
|
210
|
+
'--additional-features',
|
|
211
|
+
type=str,
|
|
212
|
+
nargs='+',
|
|
213
|
+
default=None,
|
|
214
|
+
help='Additional graph features (monomer, periodic_bond, etc.)'
|
|
215
|
+
)
|
|
216
|
+
train_parser.add_argument(
|
|
217
|
+
'--skip-train',
|
|
218
|
+
action='store_true',
|
|
219
|
+
help='Skip training step (use with --n-estimator to only build ensemble)'
|
|
220
|
+
)
|
|
221
|
+
train_parser.add_argument(
|
|
222
|
+
'--low-fidelity-model',
|
|
223
|
+
type=str,
|
|
224
|
+
default=None,
|
|
225
|
+
help='Path to low-fidelity model for residual learning'
|
|
226
|
+
)
|
|
227
|
+
train_parser.add_argument(
|
|
228
|
+
'--estimator-name',
|
|
229
|
+
type=str,
|
|
230
|
+
default=None,
|
|
231
|
+
help='Name of empirical estimator for delta-learning (e.g., Density-IBM, Rg-monomer)'
|
|
232
|
+
)
|
|
233
|
+
train_parser.add_argument(
|
|
234
|
+
'--emb-model',
|
|
235
|
+
type=str,
|
|
236
|
+
default=None,
|
|
237
|
+
help='Path to embedding model for knowledge transfer'
|
|
238
|
+
)
|
|
239
|
+
train_parser.add_argument(
|
|
240
|
+
'--ensemble-type',
|
|
241
|
+
type=str,
|
|
242
|
+
default='voting',
|
|
243
|
+
choices=['voting', 'bagging', 'gradient_boosting', 'snapshot', 'soft_gradient_boosting'],
|
|
244
|
+
help='Type of ensemble to use'
|
|
245
|
+
)
|
|
246
|
+
train_parser.add_argument(
|
|
247
|
+
'--train-residual',
|
|
248
|
+
action='store_true',
|
|
249
|
+
help='Train on residuals from base estimator or low-fidelity model'
|
|
250
|
+
)
|
|
251
|
+
train_parser.add_argument(
|
|
252
|
+
'--normalizer-type',
|
|
253
|
+
type=str,
|
|
254
|
+
default='normalizer',
|
|
255
|
+
choices=['normalizer', 'log_normalizer', 'none'],
|
|
256
|
+
help='Type of label normalization'
|
|
257
|
+
)
|
|
258
|
+
train_parser.add_argument(
|
|
259
|
+
'--augmentation',
|
|
260
|
+
action='store_true',
|
|
261
|
+
help='Use data augmentation (oligomer building)'
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# ============================================================================
|
|
265
|
+
# Recommend subcommand
|
|
266
|
+
# ============================================================================
|
|
267
|
+
recommend_parser = subparsers.add_parser(
|
|
268
|
+
'rec',
|
|
269
|
+
help='Recommend molecules for active learning',
|
|
270
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
271
|
+
)
|
|
272
|
+
recommend_parser.add_argument(
|
|
273
|
+
'--pool-csv',
|
|
274
|
+
type=str,
|
|
275
|
+
required=True,
|
|
276
|
+
help='Path to CSV file with candidate molecules (must have SMILES column)'
|
|
277
|
+
)
|
|
278
|
+
recommend_parser.add_argument(
|
|
279
|
+
'--trained-model',
|
|
280
|
+
type=str,
|
|
281
|
+
required=True,
|
|
282
|
+
help='Path to trained model (.pt or .pkl file)'
|
|
283
|
+
)
|
|
284
|
+
recommend_parser.add_argument(
|
|
285
|
+
'--model-type',
|
|
286
|
+
type=str,
|
|
287
|
+
default='kfold',
|
|
288
|
+
choices=['kfold', 'ensemble'],
|
|
289
|
+
help='Type of model: kfold or ensemble'
|
|
290
|
+
)
|
|
291
|
+
recommend_parser.add_argument(
|
|
292
|
+
'--acquisition',
|
|
293
|
+
type=str,
|
|
294
|
+
default='uncertainty',
|
|
295
|
+
choices=['epig', 'uncertainty', 'random'],
|
|
296
|
+
help='Acquisition function: epig (expected improvement), uncertainty, or random'
|
|
297
|
+
)
|
|
298
|
+
recommend_parser.add_argument(
|
|
299
|
+
'--sample-size',
|
|
300
|
+
type=int,
|
|
301
|
+
default=100,
|
|
302
|
+
help='Number of molecules to recommend'
|
|
303
|
+
)
|
|
304
|
+
recommend_parser.add_argument(
|
|
305
|
+
'--save-path',
|
|
306
|
+
type=str,
|
|
307
|
+
default=None,
|
|
308
|
+
help='Path to save recommended molecules (CSV format)'
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# ============================================================================
|
|
312
|
+
# Predict subcommand
|
|
313
|
+
# ============================================================================
|
|
314
|
+
predict_parser = subparsers.add_parser(
|
|
315
|
+
'predict',
|
|
316
|
+
help='Make predictions on new polymer data',
|
|
317
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
318
|
+
)
|
|
319
|
+
predict_parser.add_argument(
|
|
320
|
+
'--model-path',
|
|
321
|
+
type=str,
|
|
322
|
+
required=True,
|
|
323
|
+
help='Path to trained model (.pt or .pkl file)'
|
|
324
|
+
)
|
|
325
|
+
predict_parser.add_argument(
|
|
326
|
+
'--csv-path',
|
|
327
|
+
type=str,
|
|
328
|
+
required=True,
|
|
329
|
+
help='Path to CSV file with molecules to predict'
|
|
330
|
+
)
|
|
331
|
+
predict_parser.add_argument(
|
|
332
|
+
'--smiles-column',
|
|
333
|
+
type=str,
|
|
334
|
+
required=True,
|
|
335
|
+
help='Name of column containing SMILES strings'
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
return parser.parse_args()
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def main():
|
|
342
|
+
"""Main entry point for the PolyMon CLI.
|
|
343
|
+
|
|
344
|
+
Parses command-line arguments and routes to the appropriate subcommand handler:
|
|
345
|
+
- train: Routes to ML or DL training based on model type
|
|
346
|
+
- rec: Active learning recommendations
|
|
347
|
+
- predict: Inference on new data
|
|
348
|
+
"""
|
|
349
|
+
args = parse_args()
|
|
350
|
+
if args.mode == 'train':
|
|
351
|
+
# Route to appropriate training function
|
|
352
|
+
if args.model in MODELS.keys():
|
|
353
|
+
main_ml(args)
|
|
354
|
+
else:
|
|
355
|
+
main_dl(args)
|
|
356
|
+
elif args.mode == 'rec':
|
|
357
|
+
main_recommend(args)
|
|
358
|
+
elif args.mode == 'predict':
|
|
359
|
+
main_predict(args)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
if __name__ == '__main__':
|
|
363
|
+
main()
|
|
@@ -365,4 +365,66 @@ def main(args: argparse.Namespace):
|
|
|
365
365
|
|
|
366
366
|
if __name__ == '__main__':
|
|
367
367
|
args = arg_parser()
|
|
368
|
-
main(args)
|
|
368
|
+
main(args)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def score(
|
|
373
|
+
train_file: str,
|
|
374
|
+
model_file: str,
|
|
375
|
+
smiles_cluster_file: str = None,
|
|
376
|
+
model_type: str = 'ensemble',
|
|
377
|
+
device: str = 'cuda',
|
|
378
|
+
ordered_tasks: List[str] = ['Rg', 'Density', 'Bulk_modulus', 'FFV', 'PLD', 'CLD'],
|
|
379
|
+
acquisition_function: str = 'uncertainty',
|
|
380
|
+
all_clusters_file: str = 'all_clusters.npy',
|
|
381
|
+
n_sample: int = 50,
|
|
382
|
+
):
|
|
383
|
+
if smiles_cluster_file is not None:
|
|
384
|
+
smiles_cluster_df = pd.read_csv(smiles_cluster_file)
|
|
385
|
+
cluster_smiles = smiles_cluster_df.groupby('cluster_id')['SMILES'].apply(list).to_dict()
|
|
386
|
+
else:
|
|
387
|
+
all_smiles = np.load(all_clusters_file, allow_pickle=True).item()
|
|
388
|
+
|
|
389
|
+
train_df = pd.read_csv(train_file)
|
|
390
|
+
train_df = train_df[train_df['Source'].isin(INITIAL_SOURCES)]
|
|
391
|
+
train_smiles = train_df['SMILES'].tolist()
|
|
392
|
+
scorer = Acquisition(
|
|
393
|
+
acquisition_function= acquisition_function,
|
|
394
|
+
model_file = model_file,
|
|
395
|
+
model_type=model_type,
|
|
396
|
+
device=device,
|
|
397
|
+
ordered_tasks=ordered_tasks,
|
|
398
|
+
)
|
|
399
|
+
sampled_scores = defaultdict(list)
|
|
400
|
+
for cluster_id, smiles in tqdm(cluster_smiles.items(), desc='Scoring'):
|
|
401
|
+
score = scorer.score(
|
|
402
|
+
pool_smiles = smiles,
|
|
403
|
+
train_smiles = train_smiles,
|
|
404
|
+
)
|
|
405
|
+
sampled_scores[cluster_id] = score
|
|
406
|
+
|
|
407
|
+
for cluster_id, scores in sampled_scores.items():
|
|
408
|
+
if isinstance(scores, list):
|
|
409
|
+
scores_np = []
|
|
410
|
+
for s in scores:
|
|
411
|
+
if hasattr(s, 'detach'): # torch.Tensor
|
|
412
|
+
s = s.detach().cpu().numpy()
|
|
413
|
+
scores_np.append(s)
|
|
414
|
+
sampled_scores[cluster_id] = np.array(scores_np)
|
|
415
|
+
elif hasattr(scores, 'detach'):
|
|
416
|
+
sampled_scores[cluster_id] = scores.detach().cpu().numpy()
|
|
417
|
+
|
|
418
|
+
all_scores = [score for scores in sampled_scores.values() for score in scores]
|
|
419
|
+
threshold = np.quantile(all_scores, 0.95)
|
|
420
|
+
best_cluster = max(
|
|
421
|
+
sampled_scores,
|
|
422
|
+
key=lambda c: sum(s > threshold for s in sampled_scores[c]) / len(sampled_scores[c])
|
|
423
|
+
)
|
|
424
|
+
all_clusters = np.load(all_clusters_file, allow_pickle=True)
|
|
425
|
+
cluster_smiles = all_clusters.item()[best_cluster]
|
|
426
|
+
cluster_scores = scorer.score(cluster_smiles, train_smiles)
|
|
427
|
+
cluster_scores = cluster_scores.detach().cpu()
|
|
428
|
+
top_n_sample = torch.topk(cluster_scores, n_sample).indices.tolist()
|
|
429
|
+
top_n_smiles = [cluster_smiles[i] for i in top_n_sample]
|
|
430
|
+
return dict(zip(top_n_smiles, cluster_scores[top_n_sample]))
|