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.
Files changed (86) hide show
  1. {polymon-1.0.2 → polymon-1.0.3}/PKG-INFO +2 -1
  2. polymon-1.0.3/README.md +240 -0
  3. polymon-1.0.3/polymon/__init__.py +1 -0
  4. polymon-1.0.3/polymon/cli/main.py +363 -0
  5. {polymon-1.0.2 → polymon-1.0.3}/polymon/cli/merge.py +63 -1
  6. polymon-1.0.3/polymon/cli/predict.py +84 -0
  7. polymon-1.0.3/polymon/cli/recommend.py +154 -0
  8. {polymon-1.0.2 → polymon-1.0.3}/polymon/cli/train_ml.py +4 -4
  9. {polymon-1.0.2 → polymon-1.0.3}/polymon/data/featurizer.py +1 -1
  10. polymon-1.0.3/polymon/estimator/Tg_atom_contrib.npy +0 -0
  11. {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/__init__.py +4 -0
  12. polymon-1.0.3/polymon/estimator/tg.py +2553 -0
  13. polymon-1.0.3/polymon/exp/acquisition.py +334 -0
  14. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/base.py +6 -5
  15. {polymon-1.0.2 → polymon-1.0.3}/polymon.egg-info/PKG-INFO +2 -1
  16. {polymon-1.0.2 → polymon-1.0.3}/polymon.egg-info/SOURCES.txt +4 -0
  17. {polymon-1.0.2 → polymon-1.0.3}/polymon.egg-info/requires.txt +1 -0
  18. {polymon-1.0.2 → polymon-1.0.3}/setup.py +1 -0
  19. polymon-1.0.2/README.md +0 -42
  20. polymon-1.0.2/polymon/__init__.py +0 -1
  21. polymon-1.0.2/polymon/cli/main.py +0 -330
  22. polymon-1.0.2/polymon/cli/predict.py +0 -38
  23. {polymon-1.0.2 → polymon-1.0.3}/polymon/cli/__init__.py +0 -0
  24. {polymon-1.0.2 → polymon-1.0.3}/polymon/cli/train_dl.py +0 -0
  25. {polymon-1.0.2 → polymon-1.0.3}/polymon/data/__init__.py +0 -0
  26. {polymon-1.0.2 → polymon-1.0.3}/polymon/data/_dmpnn_transform.py +0 -0
  27. {polymon-1.0.2 → polymon-1.0.3}/polymon/data/_pretrained.py +0 -0
  28. {polymon-1.0.2 → polymon-1.0.3}/polymon/data/cgcnn.json +0 -0
  29. {polymon-1.0.2 → polymon-1.0.3}/polymon/data/dataset.py +0 -0
  30. {polymon-1.0.2 → polymon-1.0.3}/polymon/data/dedup.py +0 -0
  31. {polymon-1.0.2 → polymon-1.0.3}/polymon/data/mordred_unstable.txt +0 -0
  32. {polymon-1.0.2 → polymon-1.0.3}/polymon/data/polymer.py +0 -0
  33. {polymon-1.0.2 → polymon-1.0.3}/polymon/data/utils.py +0 -0
  34. {polymon-1.0.2 → polymon-1.0.3}/polymon/data/xenonpy_elements.csv +0 -0
  35. {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/FFV_atom_contrib.npy +0 -0
  36. {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/Tc_atom_contrib.npy +0 -0
  37. {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/atom_contrib.py +0 -0
  38. {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/base.py +0 -0
  39. {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/density.py +0 -0
  40. {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/density_Fedors.py +0 -0
  41. {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/density_ibm.py +0 -0
  42. {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/low_fidelity.py +0 -0
  43. {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/ml.py +0 -0
  44. {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/nx_rg.py +0 -0
  45. {polymon-1.0.2 → polymon-1.0.3}/polymon/estimator/rg.py +0 -0
  46. {polymon-1.0.2 → polymon-1.0.3}/polymon/exp/__init__.py +0 -0
  47. {polymon-1.0.2 → polymon-1.0.3}/polymon/exp/pipeline.py +0 -0
  48. {polymon-1.0.2 → polymon-1.0.3}/polymon/exp/score.py +0 -0
  49. {polymon-1.0.2 → polymon-1.0.3}/polymon/exp/train.py +0 -0
  50. {polymon-1.0.2 → polymon-1.0.3}/polymon/exp/utils.py +0 -0
  51. {polymon-1.0.2 → polymon-1.0.3}/polymon/hparams.py +0 -0
  52. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/__init__.py +0 -0
  53. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/dmpnn.py +0 -0
  54. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/ensemble.py +0 -0
  55. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/__init__.py +0 -0
  56. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/embed_residual.py +0 -0
  57. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/fastkan_gatv2.py +0 -0
  58. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/gat_chain_readout.py +0 -0
  59. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/gatv2_sage.py +0 -0
  60. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/kan_gatv2.py +0 -0
  61. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/lineevo.py +0 -0
  62. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/multi_fidelity.py +0 -0
  63. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gatv2/position_encoding.py +0 -0
  64. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gnn.py +0 -0
  65. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gps/__init__.py +0 -0
  66. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gps/conv.py +0 -0
  67. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gps/gps.py +0 -0
  68. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/gvp.py +0 -0
  69. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/__init__.py +0 -0
  70. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/dmpnn.py +0 -0
  71. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/efficient_kan.py +0 -0
  72. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/fast_kan.py +0 -0
  73. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/fourier_kan.py +0 -0
  74. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/gcn.py +0 -0
  75. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/gin.py +0 -0
  76. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/kan/vanilla.py +0 -0
  77. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/mlp.py +0 -0
  78. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/polycl/__init__.py +0 -0
  79. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/polycl/polycl.py +0 -0
  80. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/register.py +0 -0
  81. {polymon-1.0.2 → polymon-1.0.3}/polymon/model/utils.py +0 -0
  82. {polymon-1.0.2 → polymon-1.0.3}/polymon/setting.py +0 -0
  83. {polymon-1.0.2 → polymon-1.0.3}/polymon.egg-info/dependency_links.txt +0 -0
  84. {polymon-1.0.2 → polymon-1.0.3}/polymon.egg-info/entry_points.txt +0 -0
  85. {polymon-1.0.2 → polymon-1.0.3}/polymon.egg-info/top_level.txt +0 -0
  86. {polymon-1.0.2 → polymon-1.0.3}/setup.cfg +0 -0
@@ -1,8 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polymon
3
- Version: 1.0.2
3
+ Version: 1.0.3
4
4
  Summary: PolyMon
5
5
  Author: PolyMon Group
6
+ Requires-Dist: numpy<2
6
7
  Requires-Dist: mordredcommunity
7
8
  Requires-Dist: mordred==1.2.0
8
9
  Requires-Dist: rdkit==2023.09.6
@@ -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]))