andy88836 commited on
Commit
4d0da28
·
verified ·
1 Parent(s): 619b1e3

Deploy MOFScreen-Agent FastAPI backend

Browse files
Files changed (31) hide show
  1. Dockerfile +2 -0
  2. coprinet/LICENSE +21 -0
  3. coprinet/data/models/trained_inStock/lightning_logs/version_0/checkpoints/epoch=238-step=1295857.ckpt +3 -0
  4. coprinet/pricePrediction/ArgParser_base.py +112 -0
  5. coprinet/pricePrediction/config.py +50 -0
  6. coprinet/pricePrediction/dataManager/__init__.py +0 -0
  7. coprinet/pricePrediction/dataManager/dataManager.py +255 -0
  8. coprinet/pricePrediction/dataManager/dataset.py +171 -0
  9. coprinet/pricePrediction/evaluation/__init__.py +0 -0
  10. coprinet/pricePrediction/evaluation/evaluation.py +198 -0
  11. coprinet/pricePrediction/nets/FDS_imbalance.py +189 -0
  12. coprinet/pricePrediction/nets/__init__.py +0 -0
  13. coprinet/pricePrediction/nets/basicArchitectures.py +165 -0
  14. coprinet/pricePrediction/nets/netsGraph.py +236 -0
  15. coprinet/pricePrediction/predict/__init__.py +0 -0
  16. coprinet/pricePrediction/predict/__main__.py +4 -0
  17. coprinet/pricePrediction/predict/predict.py +137 -0
  18. coprinet/pricePrediction/preprocessData/__init__.py +0 -0
  19. coprinet/pricePrediction/preprocessData/prepareDataMol2Price.py +185 -0
  20. coprinet/pricePrediction/preprocessData/serializeDatapoints.py +15 -0
  21. coprinet/pricePrediction/preprocessData/smilesToDescriptors.py +239 -0
  22. coprinet/pricePrediction/preprocessData/smilesToGraph.py +160 -0
  23. coprinet/pricePrediction/selectFromRawData/__init__.py +0 -0
  24. coprinet/pricePrediction/selectFromRawData/selectFromRawDataNonVirtualAll.py +41 -0
  25. coprinet/pricePrediction/selectFromRawData/selectFromRawDataNonVirtualAll_base.py +76 -0
  26. coprinet/pricePrediction/selectFromRawData/selectFromRawDataOnlyVirtualAll.py +55 -0
  27. coprinet/pricePrediction/train/__init__.py +0 -0
  28. coprinet/pricePrediction/train/trainNet.py +147 -0
  29. coprinet/pricePrediction/utils.py +32 -0
  30. requirements.txt +5 -0
  31. tools/online_screening.py +3 -10
Dockerfile CHANGED
@@ -13,6 +13,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
13
 
14
  COPY requirements.txt /app/requirements.txt
15
  RUN python -m pip install --upgrade pip && \
 
16
  python -m pip install --no-cache-dir -r /app/requirements.txt
17
 
18
  COPY agent /app/agent
@@ -21,6 +22,7 @@ COPY configs /app/configs
21
  COPY data /app/data
22
  COPY tools /app/tools
23
  COPY trained_toxicity_models /app/trained_toxicity_models
 
24
 
25
  EXPOSE 7860
26
 
 
13
 
14
  COPY requirements.txt /app/requirements.txt
15
  RUN python -m pip install --upgrade pip && \
16
+ python -m pip install --no-cache-dir --index-url https://download.pytorch.org/whl/cpu torch==2.6.0 && \
17
  python -m pip install --no-cache-dir -r /app/requirements.txt
18
 
19
  COPY agent /app/agent
 
22
  COPY data /app/data
23
  COPY tools /app/tools
24
  COPY trained_toxicity_models /app/trained_toxicity_models
25
+ COPY coprinet /app/coprinet
26
 
27
  EXPOSE 7860
28
 
coprinet/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2022 rsanchezgarc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
coprinet/data/models/trained_inStock/lightning_logs/version_0/checkpoints/epoch=238-step=1295857.ckpt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ef10cad50629f328a2302d0301c50efbf2666d091692a4382beef8a18dfab152
3
+ size 25121165
coprinet/pricePrediction/ArgParser_base.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import abc
2
+ import argparse
3
+ import inspect
4
+ from argparse import ArgumentParser
5
+
6
+ from pricePrediction import config
7
+
8
+
9
+ class ArgParseable(abc.ABC):
10
+
11
+ @property
12
+ @abc.abstractmethod
13
+ def DESIRED_PARAMS_TO_ASK(self): #TOOD: Properties always apply to instances, not classes. to be modified
14
+ raise NotImplementedError()
15
+
16
+ @classmethod
17
+ def fromTypeStrToType(cls, strType: str):
18
+ if strType == "str":
19
+ return str
20
+ elif strType == "float":
21
+ return float
22
+ elif strType == "int":
23
+ return int
24
+ elif strType == "bool":
25
+ return bool
26
+ else:
27
+ raise ValueError("Parser not recognized type: "+str(strType))
28
+
29
+ @classmethod
30
+ def fromDefaultStrToValue(cls, argname:str, typeFun:type, defaultStr: str): #Currently not used
31
+ '''
32
+ TO BE USED if defaults in the docstring E.g. :param int batch_size: Batch size. defaults to %(config.BATCH_SIZE)s
33
+
34
+ :param argname:
35
+ :param typeFun:
36
+ :param defaultStr:
37
+ :return:
38
+ '''
39
+
40
+ if defaultStr is None or defaultStr == "None":
41
+ return None
42
+ return typeFun(defaultStr)
43
+
44
+ @classmethod
45
+ def getArgsForParser(cls):
46
+ from docstring_parser import parse
47
+ docstring = cls.__init__.__doc__
48
+ config_vars = vars(config)
49
+ config_vars = { "config."+key: val for key,val in config_vars.items() }
50
+ docstring = docstring%config_vars
51
+ docstring = parse(docstring)
52
+
53
+ signature = inspect.signature(cls.__init__)
54
+ name_to_default = {
55
+ k: v.default
56
+ for k, v in signature.parameters.items()
57
+ if v.default is not inspect.Parameter.empty
58
+ }
59
+
60
+ params = []
61
+ for elem in docstring.params:
62
+ if elem.arg_name in cls.DESIRED_PARAMS_TO_ASK:
63
+ typeFun = cls.fromTypeStrToType(elem.type_name)
64
+ # print(elem.default, elem.description)
65
+ ### default = cls.fromDefaultStrToValue(elem.arg_name, typeFun, elem.default)
66
+ default = name_to_default[elem.arg_name]
67
+ params.append((elem.arg_name, typeFun, default, elem.description))
68
+ return params
69
+
70
+ # @classmethod
71
+ # def _addParamToArgParse(cls, parser:ArgumentParser, paramTuple):
72
+ # name, typeFun, default, help= paramTuple
73
+ # if typeFun == bool:
74
+ # if default is True:
75
+ # name = "no_"+name
76
+ # help = "Deactivate "+help
77
+ # parser.add_argument("--" + name, help=help+" Default=%(default)s", action="store_true")
78
+ # else:
79
+ # parser.add_argument("--"+name, type=typeFun, help=help+" Default=%(default)s", default=default)
80
+ # return parser
81
+
82
+ @classmethod
83
+ def addParamsToArgParse(cls, parser: ArgumentParser):
84
+ '''
85
+
86
+ :param parser: Argparser to which methods will be added
87
+ :return: the input parser but with added arguments
88
+ '''
89
+ for paramTuple in cls.getArgsForParser():
90
+ name, typeFun, default, help= paramTuple
91
+ if typeFun == bool:
92
+ if default is True:
93
+ action="store_false"
94
+ else:
95
+ action="store_true"
96
+ help += " Action: "+action
97
+ parser.add_argument("--" + name, help=help, action=action)
98
+ else:
99
+ parser.add_argument("--"+name, type=typeFun, help=help+" Default=%(default)s", default=default)
100
+ return parser
101
+
102
+
103
+
104
+ class MyArgParser(argparse.ArgumentParser):
105
+ def parse_args(self, args=None, namespace=None):
106
+ args = super().parse_args(args=args, namespace=namespace)
107
+ arg_groups = {}
108
+ for group in self._action_groups:
109
+ group_dict = {a.dest: getattr(args, a.dest, None) for a in group._group_actions}
110
+ arg_groups[group.title] = group_dict
111
+
112
+ return arg_groups
coprinet/pricePrediction/config.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ ################## Common options ##################
4
+ NUM_BUCKETS = 100
5
+ N_CPUS = 12
6
+
7
+ MULTIPROC_PREFETCH_FACTOR = 4
8
+ MULTIPROC_TIMEOUT = 60
9
+ PERSISTENT_WORKERS = False
10
+
11
+ BATCH_SIZE = 512
12
+ NUM_WORKERS_PER_GPU = 2
13
+ BUFFER_N_BATCHES_FOR_PRED=50 #Process as many batches together for speed. Set it as large as possible to fit your RAM memory
14
+
15
+ USE_MMOL_INSTEAD_GRAM = True
16
+
17
+ RANDOM_SEED = 121
18
+
19
+ ################## Net ##################
20
+ N_LAYERS=10
21
+ N_HIDDEN_NODE=75
22
+ N_HIDDEN_EDGE=50
23
+
24
+ LEARNING_RATE= 1e-5
25
+ PATIENT_REDUCE_LR_PLATEAU_N_EPOCHS = 25
26
+ COSINE_LR_SCHEDULE_N_EPOCHS = 10
27
+
28
+ ############# Data ##################
29
+
30
+ TEST_SIZE= int(2e5)
31
+ CSV_CHUNK_SIZE= int(1e5)
32
+
33
+ DATA_DIR = os.path.abspath(os.path.join(__file__, "../.."))
34
+
35
+ RAW_DATA_DIRNAME= "data/MCule" #Can be provided via command line arguments
36
+ BUILDING_BLOCKS_FNAME= os.path.join(RAW_DATA_DIRNAME, "mcule_purchasable_building_blocks_210806.smi.gz")
37
+
38
+ DATASET_DIRNAME = "data/mcule_chunks"
39
+ DATASET_IN_MEMORY = True # Set to true to load the dataset into memory
40
+ ENCODED_DIR = os.path.join(DATASET_DIRNAME, "encoded")
41
+
42
+ RAW_DATA_FILE_SUFFIX = r"mcule_full_(train|test|val)\.csv_split_\w+\.csv$"
43
+
44
+ DEFAULT_MODEL = os.path.join( DATA_DIR, "data/models/trained_inStock/lightning_logs/version_0/checkpoints/epoch=238-step=1295857.ckpt")
45
+ # DEFAULT_MODEL = os.path.join( DATA_DIR, "data/models/trained_virtual/lightning_logs/version_0/checkpoints/epoch=172-step=3231639.ckpt")
46
+
47
+
48
+ ######################### Ignore the next lines if not running a comparison ################
49
+ USE_FEATURES_NET = False #set this to True to use a MLP instead of GNN
50
+ NORMALIZE_NODE_FEATS = False #set this to True if USE_FEATURES_NET==True
coprinet/pricePrediction/dataManager/__init__.py ADDED
File without changes
coprinet/pricePrediction/dataManager/dataManager.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import atexit
2
+ import logging
3
+ import os
4
+ import shutil
5
+
6
+ import dirsync
7
+ import joblib
8
+ import torch
9
+
10
+ import pytorch_lightning as pl
11
+ from pytorch_lightning import seed_everything
12
+
13
+ from torch.utils.data import DataLoader, Subset
14
+
15
+ from pricePrediction import config
16
+ from pricePrediction.ArgParser_base import ArgParseable
17
+ from pricePrediction.dataManager.dataset import Dataset_graphPrice
18
+ from pricePrediction.preprocessData.prepareDataMol2Price import DataBuilder
19
+ from pricePrediction.utils import EncodedDirNamesAndTemplates
20
+
21
+
22
+ class GraphPriceDatamodule(pl.LightningDataModule, ArgParseable):
23
+
24
+
25
+ DESIRED_PARAMS_TO_ASK= ['encodedDir', 'batch_size', 'num_workers', 'use_lds', 'use_weights', 'force_compute_data',
26
+ 'do_undersample', 'augment_labels', 'copy_data_to_wdir', 'debug']
27
+
28
+ def __init__(self, encodedDir=config.ENCODED_DIR, deg_fname=None, batch_size: int = config.BATCH_SIZE,
29
+ num_workers: int = config.NUM_WORKERS_PER_GPU, random_seed: int = config.RANDOM_SEED,
30
+ use_log_for_target: bool = True, use_lds: bool = False,
31
+ use_weights: bool = False, force_compute_data: bool = False, do_undersample: bool = False,
32
+ augment_labels: bool =True, shuffle_training: bool =True, copy_data_to_wdir: str = None, debug =False):
33
+ '''The DataManager that will create several DataLoader(s)
34
+
35
+ :param str encodedDir: The directory where the dataset has been prepared as lmdb files.
36
+ :param str deg_fname: The file containing the statistics about the nodes degree. By default is automatically searched within
37
+ encodedDir.
38
+ :param int batch_size: Batch size.
39
+ :param int num_workers: Number of workers as in "pytorch.DataLoader".
40
+ :param int random_seed: Random seed.
41
+ :param bool use_log_for_target: Apply log to the target (log(price)).
42
+ :param bool use_lds: Use label density smoothing to reduce data imbalance.
43
+ :param bool use_weights: Apply weights to reduce data imbalance.
44
+ :param bool force_compute_data: Recompute all data before using the DataManager. Takes some time.
45
+ :param bool do_undersample: Use undersampling to reduce data imbalance.
46
+ :param bool augment_labels: Add random perturbation to training labels to reduce overfitting.
47
+ :param bool shuffle_training: Shuffle training data.
48
+ :param str copy_data_to_wdir: An optional working dir where data can be copied. Useful to avoid network disk usage.
49
+ :param bool debug: Use only a small subset of the dataset
50
+ '''
51
+
52
+ seed_everything(random_seed)
53
+ super().__init__()
54
+
55
+ if encodedDir is None:
56
+ encodedDir = config.ENCODED_DIR
57
+ if deg_fname is None:
58
+ names = EncodedDirNamesAndTemplates(encodedDir)
59
+ deg_fname = names.DEGREES_FNAME
60
+
61
+ self.deg_fname = deg_fname
62
+ self.do_undersample = do_undersample
63
+ self.use_weights = use_weights or use_lds
64
+ self.use_lds = use_lds
65
+ self.use_log_for_target = use_log_for_target
66
+ self.random_seed = random_seed
67
+
68
+ self.copy_data_to_wdir = copy_data_to_wdir
69
+
70
+ self.encodedDir = encodedDir
71
+ self.batch_size = batch_size
72
+ self.num_workers = num_workers
73
+ self.force_compute_data = force_compute_data
74
+ self.augment_labels = augment_labels
75
+ self.shuffle_training = shuffle_training
76
+
77
+ self.debug = debug
78
+
79
+ ###### Delayed variables
80
+ self._dims = None
81
+ self._train_n_instances = None
82
+ self.setup_done = False
83
+ self.dataset_train = None
84
+ self.dataset_val = None
85
+ self.dataset_test = None
86
+
87
+ def _getBucketsFname(self, training_phase):
88
+ names = EncodedDirNamesAndTemplates(self.encodedDir)
89
+ return names.BUCKETS_FNAME_TEMPLATE % training_phase
90
+
91
+ @property
92
+ def dims(self):
93
+ if self._dims is None:
94
+ metadata_fname = EncodedDirNamesAndTemplates(self.encodedDir).DATASET_METADATA_FNAME_TEMPLATE % "train"
95
+ size_metadata_info = joblib.load(metadata_fname)
96
+ self._dims = (size_metadata_info["nodes_n_features"], size_metadata_info["edges_n_features"])
97
+ self._train_n_instances = size_metadata_info["total_size"]
98
+ return self._dims
99
+
100
+ def get_nodes_degree(self):
101
+ if os.path.isfile( self.deg_fname ):
102
+ return joblib.load(self.deg_fname)
103
+ else:
104
+ self.prepare_data()
105
+ return self.get_nodes_degree()
106
+
107
+ def prepare_data(self):
108
+ # Compute if not already done
109
+ if self.force_compute_data:
110
+ dataBuilder = DataBuilder()
111
+ dataBuilder.prepareDataset(encodedDir=self.encodedDir, datasetSplit="train")
112
+ dataBuilder.prepareDataset(encodedDir=self.encodedDir, datasetSplit="test")
113
+ dataBuilder.prepareDataset(encodedDir=self.encodedDir, datasetSplit="val")
114
+ else:
115
+ dataBuilder = DataBuilder()
116
+ if not os.path.exists(os.path.join(self.encodedDir, "train")):
117
+ dataBuilder.prepareDataset(encodedDir=self.encodedDir, datasetSplit="train")
118
+ if not os.path.exists(os.path.join(self.encodedDir, "test")):
119
+ dataBuilder.prepareDataset(encodedDir=self.encodedDir, datasetSplit="test")
120
+ if not os.path.exists(os.path.join(self.encodedDir, "val")):
121
+ dataBuilder.prepareDataset(encodedDir=self.encodedDir, datasetSplit="val")
122
+
123
+ # if self.copy_data_to_wdir:
124
+ # tmpArgs = {}
125
+ # print(self.copy_data_to_wdir, os.path.isdir(self.copy_data_to_wdir))
126
+ # if not os.path.isdir(self.copy_data_to_wdir):
127
+ # tmpArgs["prefix"] = self.copy_data_to_wdir
128
+ # self._tmpdir = TemporaryDirectory(**tmpArgs)
129
+ # def cleanTmpdir():
130
+ # del self._tmpdir
131
+ # atexit.register( cleanTmpdir)
132
+ #
133
+ # self.copy_data_to_wdir = self._tmpdir.name
134
+
135
+ if self.copy_data_to_wdir:
136
+ if not os.path.isdir(self.copy_data_to_wdir):
137
+ os.makedirs(self.copy_data_to_wdir)
138
+ _tmpdir = self.copy_data_to_wdir
139
+ def cleanTmpdir():
140
+ shutil.rmtree( _tmpdir)
141
+ atexit.register(cleanTmpdir)
142
+
143
+ print("copying training data from %s to %s" % (self.encodedDir, self.copy_data_to_wdir))
144
+ dirsync.sync(self.encodedDir, self.copy_data_to_wdir, 'sync', verbose=True, logger=logging.getLogger('dummy'))
145
+
146
+ def setup(self, stage=None):
147
+ if self.dataset_train is not None and self.dataset_val is not None and self.dataset_test is not None:
148
+ return
149
+
150
+ if self.copy_data_to_wdir is not None:
151
+ encodedDir = self.copy_data_to_wdir
152
+ else:
153
+ encodedDir = self.encodedDir
154
+
155
+ self.dataset_train = Dataset_graphPrice(encodedDir, datasetSplit="train",
156
+ use_log_for_target=self.use_log_for_target,
157
+ use_weights=self.use_weights, augment_labels = self.augment_labels,
158
+ buckets_fname=self._getBucketsFname("train"),
159
+ use_lds=self.use_lds, do_undersample=self.do_undersample)
160
+
161
+ self.dataset_val = Dataset_graphPrice(encodedDir, datasetSplit="val",
162
+ use_log_for_target=self.use_log_for_target,
163
+ use_weights=self.use_weights, augment_labels = False,
164
+ buckets_fname=self._getBucketsFname("val"),
165
+ use_lds=self.use_lds, do_undersample=False)
166
+
167
+ self.dataset_test = Dataset_graphPrice(encodedDir, datasetSplit="test",
168
+ use_log_for_target=self.use_log_for_target,
169
+ use_weights=self.use_weights, augment_labels = False,
170
+ buckets_fname=self._getBucketsFname("test"),
171
+ use_lds=self.use_lds, do_undersample=False)
172
+
173
+ atexit.register( self.dataset_train.close )
174
+ atexit.register( self.dataset_val.close )
175
+ atexit.register( self.dataset_test.close )
176
+
177
+ def _generic_dataloader(self, dataset, num_workers=None, **kwargs):
178
+ if num_workers is None:
179
+ num_workers = self.num_workers
180
+
181
+
182
+ if num_workers>0 and "timeout" not in kwargs:
183
+ kwargs["timeout"] = config.MULTIPROC_TIMEOUT
184
+
185
+ if num_workers > 0 and "prefetch_factor" not in kwargs:
186
+ kwargs["prefetch_factor"] = config.MULTIPROC_PREFETCH_FACTOR
187
+
188
+ if num_workers > 0 and "persistent_workers" not in kwargs:
189
+ kwargs["persistent_workers"] = config.PERSISTENT_WORKERS
190
+
191
+ print("Using num_workers:", num_workers)
192
+ #print(self.trainer.reload_dataloaders_every_epoch)
193
+
194
+ from torch_geometric.data import Batch
195
+ def from_list_of_examples_to_batch(examples):
196
+ graphs, ys = zip(* examples)
197
+ g_batch = Batch.from_data_list(graphs)
198
+ ys = torch.stack(ys)
199
+ return [g_batch, ys]
200
+ if self.debug:
201
+ dataset = Subset(dataset, range(21*self.batch_size))
202
+ return DataLoader(dataset, num_workers=num_workers, batch_size=self.batch_size,
203
+ pin_memory=num_workers>0, collate_fn = from_list_of_examples_to_batch,
204
+ **kwargs)
205
+
206
+ def train_dataloader(self, **kwargs):
207
+ if self.trainer:
208
+ print("Generating a train_dataloader at epoch: %d"% ( self.trainer.current_epoch ))
209
+ if "persistent_workers" not in kwargs:
210
+ kwargs["persistent_workers"] = False
211
+ return self._generic_dataloader(self.dataset_train, shuffle=True, **kwargs)
212
+
213
+ def val_dataloader(self, **kwargs):
214
+ return self._generic_dataloader(self.dataset_val, shuffle=False, **kwargs)
215
+
216
+ def test_dataloader(self, **kwargs):
217
+ return self._generic_dataloader(self.dataset_test, shuffle=False, **kwargs)
218
+
219
+ def predict_dataloader(self, dataset=None):
220
+
221
+ if dataset == "train":
222
+ raise NotImplementedError()
223
+ elif dataset == "test":
224
+ raise NotImplementedError()
225
+ elif dataset == "val":
226
+ raise NotImplementedError()
227
+ if dataset is not None: # TODO: check type
228
+ raise NotImplementedError()
229
+ else:
230
+ raise NotImplementedError()
231
+
232
+
233
+
234
+ if __name__ == "__main__":
235
+
236
+ from pricePrediction.ArgParser_base import ArgParseable, MyArgParser
237
+
238
+ parser = MyArgParser(prog=None, usage=None, description=None, )
239
+
240
+ group = parser.add_argument_group(title="data")
241
+ GraphPriceDatamodule.addParamsToArgParse(group)
242
+ cmd_args = parser.parse_args()
243
+ dm = GraphPriceDatamodule(**cmd_args.get("data", {}))
244
+ dm.setup()
245
+ allPrices = []
246
+ for batchG, batchY in dm.test_dataloader():
247
+ allPrices.extend( batchY.tolist() )
248
+
249
+ import matplotlib.pyplot as plt
250
+ plt.hist(allPrices, bins=200)
251
+ plt.show()
252
+
253
+ '''
254
+ python -m pricePrediction.dataManager.dataManager
255
+ '''
coprinet/pricePrediction/dataManager/dataset.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import operator
2
+ import os
3
+ import joblib
4
+ import lmdb
5
+ import numpy as np
6
+ import torch
7
+
8
+ from torch.utils.data import Dataset
9
+
10
+ from pricePrediction import config
11
+ from pricePrediction.preprocessData.serializeDatapoints import getExampleId, deserializeExample
12
+ from pricePrediction.utils import search_buckedId, EncodedDirNamesAndTemplates
13
+
14
+
15
+ class Dataset_graphPrice(Dataset):
16
+ def __init__(self, encodedDir, datasetSplit, use_log_for_target=True, use_weights=False, buckets_fname=None,
17
+ use_lds=False, do_undersample=False, augment_labels=False, store_in_memory=config.DATASET_IN_MEMORY,
18
+ normalize_node_feats=config.NORMALIZE_NODE_FEATS):
19
+
20
+ self.use_log_for_target = use_log_for_target
21
+ self.use_weights = use_weights
22
+ self.buckets_fname = buckets_fname
23
+ self.use_lds = use_lds
24
+ self.do_undersample = do_undersample
25
+ self.augment_labels = augment_labels
26
+ self.store_in_memory = store_in_memory
27
+ self.normalize_node_feats = normalize_node_feats
28
+
29
+
30
+ assert (self.buckets_fname is not None and self.use_weights) or not self.use_weights
31
+ if self.do_undersample: raise NotImplementedError()
32
+
33
+ dataNames = EncodedDirNamesAndTemplates(encodedDir)
34
+ size_metadata_info = joblib.load( dataNames.DATASET_METADATA_FNAME_TEMPLATE % datasetSplit)
35
+
36
+ if self.normalize_node_feats:
37
+ self.min_x = torch.FloatTensor(size_metadata_info["feature_stats"]["min"])
38
+ self.max_x = torch.FloatTensor(size_metadata_info["feature_stats"]["max"])
39
+ self.range_x = self.max_x - self.min_x
40
+ assert not torch.isclose(self.range_x, torch.zeros(1)).any(), f"Error, range close to 0, {torch.where(torch.isclose(self.range_x, torch.zeros(1)))}"
41
+ assert not torch.isinf(self.range_x).any()
42
+
43
+ self.total_size = size_metadata_info["total_size"]
44
+ self.size_per_file = size_metadata_info["sizes_list"]
45
+ self.db_fnames = [ os.path.join( encodedDir, datasetSplit, os.path.basename(fname))
46
+ for fname in size_metadata_info["fnames_list"]]
47
+ self.cum_size_at_file = np.cumsum(np.array(size_metadata_info["sizes_list"], dtype=int))
48
+ self.last_cum_elemIdx_at_file = self.cum_size_at_file - 1
49
+
50
+ self.fileIdx_to_fileBasename = [os.path.basename(fname) for fname in self.db_fnames]
51
+
52
+ #Delayed load
53
+ self.db_managers= [None]*len(self.size_per_file)
54
+
55
+ self._bucket_ranges = None
56
+ self._n_per_bucket = None
57
+ self._cache = {}
58
+
59
+
60
+ def _idx_to_file_and_num(self, idx):
61
+ fileIdx = search_buckedId(idx, self.last_cum_elemIdx_at_file)
62
+ idx = idx - (self.cum_size_at_file[fileIdx - 1] if fileIdx - 1 >= 0 else 0)
63
+ return fileIdx, idx
64
+
65
+ def _init_db(self, fileIdx):
66
+ env = lmdb.open(self.db_fnames[fileIdx],
67
+ readonly=True, lock=False,
68
+ readahead=False, meminit=False)
69
+ txn = env.begin()
70
+ self.db_managers[fileIdx] = (env, txn)
71
+ return env, txn
72
+
73
+ @property
74
+ def bucket_ranges(self):
75
+ if self._bucket_ranges is None:
76
+ self._getBuckets()
77
+ return self._bucket_ranges
78
+
79
+ @property
80
+ def n_per_bucket(self):
81
+ if self._n_per_bucket is None:
82
+ self._getBuckets()
83
+ return self._n_per_bucket
84
+
85
+ def n_batches(self, batch_size):
86
+ return len(self)//batch_size + int(bool(len(self)%batch_size))
87
+
88
+ def _getBuckets(self):
89
+ buckets = joblib.load(self.buckets_fname)
90
+ bucket_ranges, n_per_bucket = buckets["bucket_ranges"], buckets["n_per_bucket"]
91
+
92
+ if self.use_lds:
93
+ print("using lds")
94
+ from scipy.ndimage import gaussian_filter1d
95
+ n_per_bucket = gaussian_filter1d(n_per_bucket, sigma=2)
96
+ self._bucket_ranges = bucket_ranges
97
+ self._n_per_bucket = n_per_bucket
98
+
99
+ def compute_weight(self, label):
100
+ if not self.use_log_for_target:
101
+ label = np.exp(label)
102
+ bucket_num = search_buckedId(label, self.bucket_ranges)
103
+ w = self.total_size / (1 + self.n_per_bucket[bucket_num])
104
+ return w
105
+
106
+ def prepare_undersample(self, maximum_percentile=80):
107
+ maximum_per_bucket = np.percentile(self.n_per_bucket, maximum_percentile)
108
+ prob_per_bucket = maximum_per_bucket / (1e-10 + self.n_per_bucket)
109
+ prob_per_bucket = np.where(prob_per_bucket < 1, prob_per_bucket, 1)
110
+ print("maximum_per_bucket while sampling: %d" % maximum_per_bucket)
111
+ new_len = sum([min(maximum_per_bucket, n_elems) for n_elems in self.n_per_bucket])
112
+
113
+ def predicate(graph_y):
114
+ y = graph_y[1]
115
+ bucket_num = search_buckedId(y, self.bucket_ranges)
116
+ return np.random.rand() < prob_per_bucket[bucket_num]
117
+
118
+ # dataset = dataset.select(predicate)
119
+ raise NotImplementedError() #This code is obsolete and requires refactoring
120
+
121
+ def normalize_x(self, x):
122
+ return (x-self.min_x)/self.range_x
123
+
124
+ def __getitem__(self, index):
125
+
126
+ out = self._cache.get(index, None)
127
+ if out is not None:
128
+ return out
129
+ else:
130
+ if index >= self.total_size:
131
+ raise IndexError()
132
+ fileIdx, pointIdx = self._idx_to_file_and_num(index)
133
+ db_manager = self.db_managers[fileIdx]
134
+ if db_manager is None:
135
+ txn= self._init_db(fileIdx)[-1]
136
+ else:
137
+ txn = db_manager[-1]
138
+
139
+ dataBytes = txn.get(getExampleId(self.fileIdx_to_fileBasename[fileIdx], pointIdx))
140
+ if dataBytes is None:
141
+ msg = "Error:\n%s %s %s %s"%(index, fileIdx, pointIdx, getExampleId(self.fileIdx_to_fileBasename[fileIdx], pointIdx))
142
+ raise Exception(msg)
143
+ graph, label = deserializeExample(dataBytes)
144
+
145
+ if self.normalize_node_feats:
146
+ graph.x = self.normalize_x(graph.x)
147
+
148
+ if self.use_log_for_target:
149
+ label = np.log(label)
150
+
151
+ if self.augment_labels:
152
+ label = label * (1+0.05*np.random.rand()-0.025)
153
+
154
+ if self.use_weights:
155
+ graph.w = self.compute_weight(label)
156
+ else:
157
+ graph.w = 1
158
+ graph.y = label
159
+ if self.store_in_memory:
160
+ self._cache[index] = (graph, label)
161
+ return graph, label
162
+
163
+ def __len__(self):
164
+ return self.total_size
165
+
166
+ def close(self):
167
+ for db_handler in self.db_managers:
168
+ if db_handler:
169
+ env, txn = db_handler
170
+ env.close()
171
+
coprinet/pricePrediction/evaluation/__init__.py ADDED
File without changes
coprinet/pricePrediction/evaluation/evaluation.py ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, io
2
+ from datetime import datetime
3
+
4
+ import numpy as np
5
+ import torch
6
+ import matplotlib.pyplot as plt
7
+ from scipy.stats import pearsonr, spearmanr
8
+ from pytorch_lightning.callbacks import Callback
9
+
10
+ def plt_to_numpy(fig):
11
+ #it should be called after having done the plt.plot stuff, in replacement of plt.show()
12
+ #TO be used with torch tensorboard
13
+ from skimage.color import rgba2rgb
14
+ io_buf = io.BytesIO()
15
+ fig.savefig(io_buf, format='raw', dpi=fig.dpi)
16
+ io_buf.seek(0)
17
+ img_arr = np.reshape(np.frombuffer(io_buf.getvalue(), dtype=np.uint8),
18
+ newshape=(int(fig.bbox.bounds[3]), int(fig.bbox.bounds[2]), -1))
19
+ img_arr = rgba2rgb( img_arr )
20
+ img_arr = img_arr.astype(np.float32)
21
+ io_buf.close()
22
+ return img_arr
23
+
24
+ class InferencePlotter():
25
+ def __init__(self, pl_module: 'pl.LightningModule'):
26
+ self.pl_module = pl_module
27
+ self.data_loaders = {}
28
+
29
+ def get_dataloader(self, mode, n_workers=None):
30
+
31
+ if mode in self.data_loaders:
32
+ return self.data_loaders[mode]
33
+ else:
34
+ if mode == "train":
35
+ dataloader = self.pl_module.train_dataloader(num_workers=n_workers)
36
+ elif mode == "val":
37
+ dataloader = self.pl_module.val_dataloader(num_workers=n_workers)
38
+ elif mode == "test":
39
+ dataloader = self.pl_module.test_dataloader(num_workers=n_workers)
40
+ else:
41
+ raise ValueError("Error. Option mode=%s not supported" % mode)
42
+
43
+ self.data_loaders[mode] = dataloader
44
+ return dataloader
45
+
46
+ @classmethod
47
+ def do_plots(cls, ys, y_preds, label="test", tensorboard_wrapper=None, step=0):
48
+
49
+ if tensorboard_wrapper:
50
+ figArgs = dict(figsize=(5, 4), dpi=450)
51
+ else:
52
+ figArgs = {}
53
+ fig = plt.figure(**figArgs)
54
+ plt.hist(ys, bins=200)
55
+ name = label + " price hist"
56
+ plt.title(name)
57
+ img_arr = plt_to_numpy(fig)
58
+ if tensorboard_wrapper:
59
+ tensorboard_wrapper.add_image(name, img_arr.transpose([2, 0, 1]), step)
60
+ plt.close()
61
+ else:
62
+ plt.show()
63
+
64
+ if y_preds is not None:
65
+ fig = plt.figure(**figArgs)
66
+ plt.hist(y_preds, bins=200)
67
+ name = label + " pred hist"
68
+ plt.title(name)
69
+ img_arr = plt_to_numpy(fig)
70
+ if tensorboard_wrapper:
71
+ tensorboard_wrapper.add_image(name, img_arr.transpose([2, 0, 1]), step)
72
+ plt.close()
73
+ else:
74
+ plt.show()
75
+
76
+ r, p = pearsonr(ys, y_preds)
77
+ rho, p2 = spearmanr(ys, y_preds)
78
+ if tensorboard_wrapper:
79
+ tensorboard_wrapper.add_scalar(label + '_pearson-r', r, step)
80
+ tensorboard_wrapper.add_scalar(label + '_spearman-r', rho, step)
81
+
82
+ if label == "test":
83
+ print("pearson-r", r, "p-val", p)
84
+ print("spearman-r", rho, "p-val", p)
85
+ else:
86
+ print("pearson-r", r, "p-val", p)
87
+ print("spearman-r", rho, "p-val", p)
88
+
89
+ fig = plt.figure(**figArgs)
90
+ max_val = max([9, ys.max()])
91
+ min_val = 1
92
+ plt.hexbin(ys, y_preds, bins="log", gridsize=100, extent=[min_val, max_val, min_val, max_val] )
93
+ plt.axline((1,1), slope=1, c="r", lw=1, ls="--", alpha=0.5)
94
+ plt.xlabel("log(price)")
95
+ plt.ylabel("Pred")
96
+ plt.colorbar()
97
+ name = label + " price vs pred"
98
+ plt.title(name)
99
+ img_arr = plt_to_numpy(fig)
100
+
101
+ if tensorboard_wrapper:
102
+ tensorboard_wrapper.add_image(name, img_arr.transpose([2, 0, 1]), step)
103
+ plt.close()
104
+ else:
105
+ plt.show()
106
+
107
+ # fig = plt.figure(**figArgs)
108
+ # plt.boxplot( abs(y_preds - ys)/ys )
109
+ # name = method_name + "_relative_error_boxplot"
110
+ # plt.title(name)
111
+ # img_arr = plt_to_numpy(fig)
112
+ # if tensorboard_wrapper:
113
+ # tensorboard_wrapper.add_image(name, img_arr.transpose([2,0,1]), step )
114
+ # plt.close()
115
+ # else:
116
+ # plt.show()
117
+ return r
118
+
119
+ def get_ytrue_ypred(self, mode="test"):
120
+ with torch.no_grad():
121
+ ytrue_ypred = []
122
+ dataLoader = self.get_dataloader(mode=mode)
123
+ n_elems = len(dataLoader)
124
+ for i, (batch, y) in enumerate(dataLoader):
125
+ ytrue_ypred.append((y, self.pl_module(batch.to(self.pl_module.device))))
126
+ if i % 10 == 0: print("%07d/%07d " % (i,n_elems), end="\r")
127
+ print()
128
+ ytrue_train, ypred_train = zip(*ytrue_ypred)
129
+ ytrue = torch.cat(ytrue_train).to("cpu").numpy()
130
+ ypred = torch.cat(ypred_train).to("cpu").numpy()
131
+ return ytrue, ypred
132
+
133
+ def pred_and_plot(self, mode="test", step=0):
134
+ ytrue, ypred = self.get_ytrue_ypred(mode=mode)
135
+ r = self.do_plots(ytrue, ypred, label=mode,
136
+ tensorboard_wrapper=self.pl_module.logger.experiment,
137
+ step=step)
138
+ return r, (ytrue, ypred)
139
+
140
+
141
+ class PlotFigsTensorboardCallback(Callback):
142
+
143
+ def __init__(self, output_dir: str = None, frequency=10, save_csv=True):
144
+ super().__init__()
145
+ self.output_dir = output_dir
146
+ self.frequency = frequency
147
+ self.current_epoch = 0
148
+ self.plotter = None # InferencePlotter(pl_module=)
149
+ self.save_csv = save_csv
150
+
151
+ def setup(self, trainer: 'pl.Trainer', pl_module: 'pl.LightningModule', stage=None) -> None:
152
+ self.plotter = InferencePlotter(pl_module=pl_module)
153
+
154
+ # def on_init_end(self, trainer):
155
+ # if self.output_dir is None:
156
+ # self.output_dir = trainer.log_dir
157
+
158
+ def on_train_start(self, trainer: 'pl.Trainer', pl_module: 'pl.LightningModule') -> None:
159
+ if trainer.is_global_zero:
160
+ if self.output_dir is None:
161
+ self.output_dir = trainer.log_dir
162
+ if self.save_csv:
163
+ self.csv_fname = os.path.join(self.output_dir, "logs.csv")
164
+ with open(self.csv_fname, "w") as f:
165
+ f.write("Epoch,Date,trainL1,valL1,trainR,valR")
166
+
167
+ def _getDateHourStr(self):
168
+ return datetime.now().strftime("%d/%m-%H:%M")
169
+
170
+ def on_train_epoch_end(
171
+ self, trainer: 'pl.Trainer', pl_module: 'pl.LightningModule', unused=None
172
+ ) -> None:
173
+
174
+ if trainer.is_global_zero:
175
+ self.current_epoch += 1
176
+ if self.current_epoch % self.frequency != 0:
177
+ return
178
+ print("Evaluating preds on epoch %d [%s]" % (self.current_epoch - 1, self._getDateHourStr()))
179
+ r_train, lossL1_train = self.process_data(mode="train")
180
+ r_val, lossL1_val = self.process_data(mode="val")
181
+
182
+ if self.save_csv:
183
+ with open(self.csv_fname, "a") as f:
184
+ f.write("\n%d,%s,%f,%f,%1.4f,%1.4f" % (self.current_epoch - 1, self._getDateHourStr(),
185
+ lossL1_train, lossL1_val, r_train, r_val,))
186
+
187
+ def process_data(self, mode="train"):
188
+ r, (ytrue, ypred) = self.plotter.pred_and_plot(mode=mode, step=self.current_epoch)
189
+ lossL1 = float(np.mean(np.abs(ytrue - ypred)))
190
+ return r, lossL1
191
+
192
+
193
+ if __name__ == "__main__":
194
+ import numpy as np
195
+
196
+ ys = 1 + np.random.rand(4000) * 7
197
+ y_preds = ys - np.random.rand()
198
+ InferencePlotter.do_plots(ys, y_preds, label="test", tensorboard_wrapper=None)
coprinet/pricePrediction/nets/FDS_imbalance.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import numpy as np
3
+ from scipy.ndimage import gaussian_filter1d
4
+ from scipy.signal.windows import triang
5
+ import torch
6
+ import torch.nn as nn
7
+ import torch.nn.functional as F
8
+
9
+ '''
10
+ Modified from:
11
+
12
+ https://github.com/YyzHarry/imbalanced-regression
13
+ https://arxiv.org/pdf/2102.09554.pdf
14
+ '''
15
+
16
+ print = logging.info
17
+
18
+ def calibrate_mean_var(matrix, m1, v1, m2, v2, clip_min=0.1, clip_max=10):
19
+ if torch.sum(v1) < 1e-10:
20
+ return matrix
21
+ if (v1 == 0.).any():
22
+ valid = (v1 != 0.)
23
+ factor = torch.clamp(v2[valid] / v1[valid], clip_min, clip_max)
24
+ matrix[:, valid] = (matrix[:, valid] - m1[valid]) * torch.sqrt(factor) + m2[valid]
25
+ return matrix
26
+
27
+ factor = torch.clamp(v2 / v1, clip_min, clip_max)
28
+ return (matrix - m1) * torch.sqrt(factor) + m2
29
+
30
+
31
+ class FDS(nn.Module):
32
+
33
+ def __init__(self, feature_dim, max_val, min_val=1, bucket_step=0.1, start_update=0, start_smooth=1,
34
+ kernel='gaussian', ks=5, sigma=2, momentum=0.9):
35
+ super(FDS, self).__init__()
36
+ self.feature_dim = feature_dim
37
+
38
+ self.bucket_start = min_val
39
+ self.bucket_step = bucket_step
40
+
41
+ bucket_end = min_val
42
+ bucket_ranges = []
43
+ while bucket_end < max_val:
44
+ next_bucket = bucket_end + bucket_step
45
+ bucket_ranges.append((bucket_end, next_bucket))
46
+ bucket_end = next_bucket
47
+
48
+ n_buckets = len( bucket_ranges )
49
+ self.n_buckets = n_buckets
50
+ self.bucket_end = bucket_end
51
+
52
+ self.kernel_window = self._get_kernel_window(kernel, ks, sigma)
53
+ self.half_ks = (ks - 1) // 2
54
+ self.momentum = momentum
55
+ self.start_update = start_update
56
+ self.start_smooth = start_smooth
57
+
58
+ self.register_buffer('epoch', torch.zeros(1).fill_(start_update))
59
+ self.register_buffer('running_mean', torch.zeros(n_buckets, feature_dim))
60
+ self.register_buffer('running_var', torch.ones(n_buckets, feature_dim))
61
+ self.register_buffer('running_mean_last_epoch', torch.zeros(n_buckets , feature_dim))
62
+ self.register_buffer('running_var_last_epoch', torch.ones(n_buckets , feature_dim))
63
+ self.register_buffer('smoothed_mean_last_epoch', torch.zeros(n_buckets , feature_dim))
64
+ self.register_buffer('smoothed_var_last_epoch', torch.ones(n_buckets , feature_dim))
65
+ self.register_buffer('num_samples_tracked', torch.zeros(n_buckets))
66
+
67
+ @staticmethod
68
+ def _get_kernel_window(kernel, ks, sigma):
69
+ assert kernel in ['gaussian', 'triang', 'laplace']
70
+ half_ks = (ks - 1) // 2
71
+ if kernel == 'gaussian':
72
+ base_kernel = [0.] * half_ks + [1.] + [0.] * half_ks
73
+ base_kernel = np.array(base_kernel, dtype=np.float32)
74
+ kernel_window = gaussian_filter1d(base_kernel, sigma=sigma) / sum(gaussian_filter1d(base_kernel, sigma=sigma))
75
+ elif kernel == 'triang':
76
+ kernel_window = triang(ks) / sum(triang(ks))
77
+ else:
78
+ laplace = lambda x: np.exp(-abs(x) / sigma) / (2. * sigma)
79
+ kernel_window = list(map(laplace, np.arange(-half_ks, half_ks + 1))) / sum(map(laplace, np.arange(-half_ks, half_ks + 1)))
80
+
81
+ print(f'Using FDS: [{kernel.upper()}] ({ks}/{sigma})')
82
+ return torch.tensor(kernel_window, dtype=torch.float32).cuda()
83
+
84
+ def _update_last_epoch_stats(self):
85
+ self.running_mean_last_epoch = self.running_mean
86
+ self.running_var_last_epoch = self.running_var
87
+
88
+ self.smoothed_mean_last_epoch = F.conv1d(
89
+ input=F.pad(self.running_mean_last_epoch.unsqueeze(1).permute(2, 1, 0),
90
+ pad=(self.half_ks, self.half_ks), mode='reflect'),
91
+ weight=self.kernel_window.view(1, 1, -1), padding=0
92
+ ).permute(2, 1, 0).squeeze(1)
93
+ self.smoothed_var_last_epoch = F.conv1d(
94
+ input=F.pad(self.running_var_last_epoch.unsqueeze(1).permute(2, 1, 0),
95
+ pad=(self.half_ks, self.half_ks), mode='reflect'),
96
+ weight=self.kernel_window.view(1, 1, -1), padding=0
97
+ ).permute(2, 1, 0).squeeze(1)
98
+
99
+ def reset(self):
100
+ self.running_mean.zero_()
101
+ self.running_var.fill_(1)
102
+ self.running_mean_last_epoch.zero_()
103
+ self.running_var_last_epoch.fill_(1)
104
+ self.smoothed_mean_last_epoch.zero_()
105
+ self.smoothed_var_last_epoch.fill_(1)
106
+ self.num_samples_tracked.zero_()
107
+
108
+ def update_last_epoch_stats(self, epoch):
109
+ if epoch == self.epoch + 1:
110
+ self.epoch += 1
111
+ self._update_last_epoch_stats()
112
+ print(f"Updated smoothed statistics on Epoch [{epoch}]!")
113
+
114
+ def get_bucket_id(self, ys):
115
+ bucket_id = (ys - self.bucket_start) // self.bucket_step
116
+ return bucket_id.type(torch.int32)
117
+
118
+ def update_running_stats(self, features, ys, epoch):
119
+ if epoch < self.epoch:
120
+ return
121
+
122
+ assert self.feature_dim == features.size(1), "Input feature dimension is not aligned!"
123
+ assert features.size(0) == ys.size(0), "Dimensions of features and labels are not aligned!"
124
+
125
+ bucket_ids = self.get_bucket_id(ys)
126
+ lower_mask = ys <= self.bucket_start
127
+ upper_mask = ys >= self.bucket_end
128
+ # for y in torch.unique(ys):
129
+ # bucket_id = self.get_bucket_id(y)
130
+ for bucket_id in torch.unique(bucket_ids):
131
+ if bucket_id > self.n_buckets - 1 or bucket_id < self.bucket_start:
132
+ continue
133
+ elif bucket_id == 0:
134
+ curr_feats = features[lower_mask]
135
+ elif bucket_id == self.n_buckets - 1:
136
+ curr_feats = features[upper_mask]
137
+ else:
138
+ curr_feats = features[bucket_ids == bucket_id]
139
+
140
+ curr_num_sample = curr_feats.size(0)
141
+ curr_mean = torch.mean(curr_feats, 0)
142
+ curr_var = torch.var(curr_feats, 0, unbiased=True if curr_feats.size(0) != 1 else False)
143
+ self.num_samples_tracked[bucket_id] += curr_num_sample
144
+ factor = self.momentum if self.momentum is not None else \
145
+ (1 - curr_num_sample / float(self.num_samples_tracked[bucket_id]))
146
+ factor = 0 if epoch == self.start_update else factor
147
+ self.running_mean[bucket_id] = \
148
+ (1 - factor) * curr_mean + factor * self.running_mean[bucket_id]
149
+ self.running_var[bucket_id] = \
150
+ (1 - factor) * curr_var + factor * self.running_var[bucket_id]
151
+
152
+ print(f"Updated running statistics with Epoch [{epoch}] features!")
153
+
154
+ def smooth(self, features, ys, epoch): #TODO: rename to forward
155
+ if epoch < self.start_smooth:
156
+ return features
157
+
158
+ bucket_ids = self.get_bucket_id(ys)
159
+ lower_mask = ys <= self.bucket_start
160
+ upper_mask = ys >= self.bucket_end
161
+
162
+ # ys = ys.squeeze(1)
163
+ # for y in torch.unique(ys):
164
+ for bucket_id in torch.unique(bucket_ids):
165
+ if bucket_id > self.n_buckets - 1 or bucket_id < self.bucket_start:
166
+ continue
167
+ elif bucket_id == self.bucket_start:
168
+ features[lower_mask] = calibrate_mean_var(
169
+ features[lower_mask], #TODO: indexing many times is not efficient
170
+ self.running_mean_last_epoch[bucket_id],
171
+ self.running_var_last_epoch[bucket_id],
172
+ self.smoothed_mean_last_epoch[bucket_id],
173
+ self.smoothed_var_last_epoch[bucket_id])
174
+ elif bucket_id == self.n_buckets - 1:
175
+ features[upper_mask] = calibrate_mean_var(
176
+ features[upper_mask],
177
+ self.running_mean_last_epoch[bucket_id],
178
+ self.running_var_last_epoch[bucket_id],
179
+ self.smoothed_mean_last_epoch[bucket_id],
180
+ self.smoothed_var_last_epoch[bucket_id])
181
+ else:
182
+ bucket_mask = bucket_ids == bucket_id
183
+ features[bucket_mask] = calibrate_mean_var(
184
+ features[bucket_mask],
185
+ self.running_mean_last_epoch[bucket_id],
186
+ self.running_var_last_epoch[bucket_id],
187
+ self.smoothed_mean_last_epoch[bucket_id],
188
+ self.smoothed_var_last_epoch[bucket_id])
189
+ return features
coprinet/pricePrediction/nets/__init__.py ADDED
File without changes
coprinet/pricePrediction/nets/basicArchitectures.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import numpy as np
3
+ from torch import nn
4
+ import torch.nn.functional as F
5
+ from torch_geometric.nn import PNAConv, BatchNorm, global_add_pool, AttentiveFP, Set2Set
6
+ from torch.nn import ModuleList, Linear, ReLU, Sequential, Dropout
7
+
8
+
9
+ class MLP(nn.Module):
10
+
11
+ def __init__(self,dims, n_layers, hidden_size, dropout=0 ):
12
+
13
+ super().__init__()
14
+
15
+ self.n_layers = n_layers
16
+ self.hidden_size = hidden_size
17
+ self.dims = dims
18
+ self.dropout = dropout
19
+
20
+
21
+ def block(in_size, n_hidden):
22
+ layers = [
23
+ nn.Linear(in_size, n_hidden),
24
+ nn.BatchNorm1d(n_hidden),
25
+ nn.ReLU(),
26
+ ]
27
+ if self.dropout > 0:
28
+ layers.append(
29
+ nn.Dropout(self.dropout),
30
+ )
31
+ return layers
32
+
33
+ # Define PyTorch model
34
+ self.model = nn.Sequential(
35
+ *block(np.prod(dims), self.hidden_size)
36
+ )
37
+
38
+ self.latent_size = self.hidden_size
39
+
40
+ def forward(self, x):
41
+ return self.model(x)
42
+
43
+
44
+ class GNN_PNAConv(torch.nn.Module):
45
+ def __init__(self,
46
+ nodes_n_features: int,
47
+ edges_n_features: int,
48
+ deg: torch.tensor ,
49
+ n_layers: int = 6,
50
+ hidden_size_node: int = 75,
51
+ hidden_size_edges: int = 50,
52
+ towers: int = 5,
53
+ fcc_hidden_size: int = 50,
54
+ dropout: float = 0,
55
+ use_fds: bool = False,
56
+ **args
57
+ ):
58
+ super(GNN_PNAConv, self).__init__()
59
+
60
+ self.node_emb = MLP(nodes_n_features, 1, hidden_size_node)
61
+ self.edge_emb = MLP(edges_n_features, 1, hidden_size_edges)
62
+
63
+ aggregators = ['mean', 'min', 'max', 'std']
64
+ scalers = ['identity', 'amplification', 'attenuation']
65
+
66
+ self.convs = ModuleList()
67
+ self.batch_norms = ModuleList()
68
+ for _ in range(n_layers):
69
+ conv = PNAConv(in_channels=hidden_size_node, out_channels=hidden_size_node,
70
+ aggregators=aggregators, scalers=scalers, deg=deg,
71
+ edge_dim=hidden_size_edges, towers=towers, pre_layers=2, post_layers=2,
72
+ divide_input=False)
73
+ self.convs.append(conv)
74
+ self.batch_norms.append(BatchNorm(hidden_size_node))
75
+ self.set2set = Set2Set(hidden_size_node, processing_steps=6)
76
+ fc_layers = [
77
+ Linear(2*hidden_size_node, hidden_size_node), BatchNorm(hidden_size_node), ReLU(),
78
+ Linear(hidden_size_node, fcc_hidden_size), BatchNorm(fcc_hidden_size), ReLU(),
79
+ ]
80
+ self.fcc_hidden_size = fcc_hidden_size
81
+ if dropout>0:
82
+ fc_layers += [ Dropout(p=dropout) ]
83
+
84
+ self.mlp = Sequential( *fc_layers )
85
+
86
+ def forward(self, x, edge_index, edge_attr, batch):
87
+ x = self.node_emb(x.squeeze())
88
+ edge_attr = self.edge_emb(edge_attr)
89
+
90
+ for conv, batch_norm in zip(self.convs, self.batch_norms):
91
+ x = F.relu(batch_norm(conv(x, edge_index, edge_attr)))
92
+
93
+ x = self.set2set(x, batch) #Set2Set #GlobalAttention #GraphMultisetTransformer
94
+
95
+ return self.mlp(x)
96
+
97
+
98
+ class QdolarAR(torch.nn.Module):
99
+ def __init__(self,
100
+ nodes_n_features: int,
101
+ *args, **kargs
102
+ ):
103
+ super(QdolarAR, self).__init__()
104
+
105
+ n_layers: int = 5
106
+ hidden_size_node: int = nodes_n_features
107
+ fcc_hidden_size: int = 100
108
+ dropout: float = 0.28
109
+
110
+ self.fcc_hidden_size = fcc_hidden_size
111
+ self.dropout = dropout
112
+ self.n_layers = n_layers
113
+ self.hidden_size_node = hidden_size_node
114
+
115
+ assert n_layers >=3
116
+ fc_layers = [
117
+ BatchNorm(hidden_size_node),
118
+ Linear( hidden_size_node, fcc_hidden_size), BatchNorm(fcc_hidden_size), ReLU(),
119
+ ]
120
+ for i in range(n_layers-2):
121
+ fc_layers += [
122
+ Linear(fcc_hidden_size, fcc_hidden_size), BatchNorm(fcc_hidden_size), ReLU(),
123
+ ]
124
+ if dropout>0:
125
+ fc_layers += [ Dropout(p=dropout) ]
126
+
127
+ self.mlp = Sequential( *fc_layers )
128
+
129
+ def forward(self, x, edge_index, edge_attr, batch):
130
+ assert len(batch) == len(batch.unique())
131
+ assert not torch.isnan(x).any(), "Error, x contains nan"
132
+ return self.mlp(x)
133
+
134
+ class GNN_AttentiveFP(torch.nn.Module):
135
+ def __init__(self,
136
+ nodes_n_features: int,
137
+ edges_n_features: int,
138
+ deg: torch.tensor ,
139
+ n_layers: int = 4,
140
+ hidden_size_node: int = 75,
141
+ hidden_size_edges: int = 50,
142
+ towers: int = 5,
143
+ fcc_hidden_size: int = 50,
144
+ dropout: float = 0,
145
+ use_fds: bool = False,
146
+ **args
147
+ ):
148
+ super(GNN_AttentiveFP, self).__init__()
149
+
150
+ self.attent_net = AttentiveFP(nodes_n_features, hidden_size_node, hidden_size_node, edges_n_features,
151
+ n_layers, n_layers, dropout= dropout)
152
+
153
+ fc_layers = [
154
+ Linear(hidden_size_node, hidden_size_edges), ReLU(),
155
+ Linear(hidden_size_edges, fcc_hidden_size), ReLU(),
156
+ ]
157
+ self.fcc_hidden_size = fcc_hidden_size
158
+ if dropout>0:
159
+ fc_layers += Dropout(p=dropout)
160
+
161
+ self.mlp = Sequential( *fc_layers )
162
+
163
+ def forward(self, x, edge_index, edge_attr, batch):
164
+ x = self.attent_net(x, edge_index, edge_attr, batch)
165
+ return self.mlp(x)
coprinet/pricePrediction/nets/netsGraph.py ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import OrderedDict
2
+ from typing import Iterable
3
+
4
+ import torch
5
+ import numpy as np
6
+ from torch import nn
7
+ import torch.nn.functional as F
8
+ import pytorch_lightning as pl
9
+
10
+ from pricePrediction import config
11
+ from pricePrediction.ArgParser_base import ArgParseable
12
+ from pricePrediction.config import USE_FEATURES_NET
13
+ from pricePrediction.nets.FDS_imbalance import FDS
14
+
15
+
16
+ class PricePredictorModule(pl.LightningModule, ArgParseable):
17
+
18
+ DESIRED_PARAMS_TO_ASK= ['n_layers', 'hidden_size_node', 'hidden_size_edges', 'fcc_hidden_size', 'use_fds', 'lr',
19
+ 'dropout', 'gnn_class', 'weight_decay', 'training_loss']
20
+ def __init__(
21
+ self,
22
+ n_layers: int = config.N_LAYERS,
23
+ hidden_size_node: int = config.N_HIDDEN_NODE,
24
+ hidden_size_edges: int = config.N_HIDDEN_EDGE,
25
+ gnn_class="GNN_PNAConv",
26
+ towers: int = 5,
27
+ fcc_hidden_size: int = 25,
28
+ dropout: float = 0,
29
+ use_fds: bool = False,
30
+ training_loss : str= "l2", # l1, l2 or huber
31
+ last_layer_size: int = 1,
32
+
33
+ nodes_n_features: int = None,
34
+ edges_n_features: int = None,
35
+ deg: Iterable[float] = None,
36
+
37
+ lr: float = config.LEARNING_RATE,
38
+ b1: float = 0.5,
39
+ b2: float = 0.999,
40
+ weight_decay=1e-8,
41
+ data_hparams =None,
42
+ logs_only_in_epoch = False
43
+ ):
44
+
45
+
46
+ '''
47
+
48
+ :param str encodedDir: The directory where the dataset has been prepared as lmdb files. defaults to %(config.ENCODED_DIR)s
49
+ :param str deg_fname: The file containing the statistics about the nodes degree. By default is automatically searched within
50
+ encodedDir defaults to None
51
+ :param int n_layers: The number of layers.
52
+ :param int hidden_size_node: Channels for nodes.
53
+ :param int hidden_size_edges: Channels for edges.
54
+ :param str gnn_class: Name of the model type.
55
+ :param int towers: Only for gnn_class="GNN_PNAConv", The number of towers of the model.
56
+ :param int fcc_hidden_size: Size of fully connected.
57
+ :param float dropout: Dropout rate.
58
+ :param bool use_fds: Use feature density smoothing for data imbalance
59
+ :param str training_loss: Training loss: "l2" "l1" or "huber"
60
+ :param int last_layer_size: The number of neurons for last layer. Set it to 1 for regression
61
+ :param int nodes_n_features: The number of features used to encode a node
62
+ :param int edges_n_features: The number of features used to encode an edge
63
+ :param Iterable deg: The degree count of the dataset
64
+ :param float lr: Learning rate
65
+ :param float b1: Adam optimizer b1
66
+ :param float b2: Adam optimizer b1
67
+ '''
68
+
69
+ super().__init__()
70
+ self.save_hyperparameters() #copies __init__() kwargs to self.hparams
71
+ deg = self.hparams.deg
72
+ if isinstance(deg, list):
73
+ deg = torch.tensor(deg[:], dtype=torch.long)
74
+ else:
75
+ deg = deg.clone().detach()
76
+ self.hparams.deg = deg
77
+
78
+
79
+
80
+ if gnn_class == "GNN_PNAConv":
81
+ from pricePrediction.nets.basicArchitectures import GNN_PNAConv as GNN
82
+ elif gnn_class == "GNN_AttentiveFP":
83
+ from pricePrediction.nets.basicArchitectures import GNN_AttentiveFP as GNN
84
+ elif gnn_class == "QdolarAR":
85
+ assert USE_FEATURES_NET
86
+ from pricePrediction.nets.basicArchitectures import QdolarAR as GNN
87
+
88
+ else:
89
+ raise ValueError("Error, gnn_class not supported")
90
+
91
+ self.net = GNN(** self.hparams)
92
+
93
+ self.dropoutLayer = nn.Dropout(self.hparams.dropout)
94
+
95
+ self.final_layer = nn.Linear(self.net.fcc_hidden_size, 1)
96
+
97
+ if use_fds:
98
+ self.fds = FDS(self.net.latent_size, max_val=10, start_update=1)
99
+ else:
100
+ self.fds = None
101
+ if training_loss=="l1":
102
+ self.trainLossF = F.l1_loss
103
+ elif training_loss=="l2":
104
+ self.trainLossF = F.mse_loss
105
+ elif training_loss=="huber":
106
+ self.trainLossF = F.smooth_l1_loss
107
+ else:
108
+ raise ValueError("Loss option not recognized")
109
+
110
+ if logs_only_in_epoch: #TODO: check if this prevents multi-gpu hang
111
+ self.logs_kwargs = dict(on_step=False, on_epoch=True, sync_dist=True)
112
+ else:
113
+ self.logs_kwargs = dict(on_step=True, on_epoch=False, sync_dist=False)
114
+
115
+
116
+ def compute_training_loss(self, y_pred, y, w=None):
117
+ loss = self.trainLossF(y_pred, y, reduction='none')
118
+ if w is not None:
119
+ loss *= w
120
+ loss = torch.mean(loss)
121
+ return loss
122
+
123
+ def forward(self, g):
124
+ return self.compute_y_pred( g, training=False)
125
+
126
+ def compute_y_pred(self, g, y=None, training=True):
127
+ x = self.net(g.x, g.edge_index, g.edge_attr, g.batch)
128
+ if training and self.fds is not None:
129
+ self.fds.smooth(x, y, self.current_epoch)
130
+ x = self.dropoutLayer(x)
131
+ y_pred = self.final_layer(x)
132
+ return y_pred.view(-1)
133
+
134
+ def resolve_batch(self, batch):
135
+ if isinstance(batch, list):
136
+ return batch[0]
137
+ else:
138
+ return batch
139
+
140
+ def training_step(self, batch, batch_idx):
141
+ graphs = self.resolve_batch(batch)
142
+ y_pred = self.compute_y_pred(graphs, training=True)
143
+ loss = self.compute_training_loss(y_pred, graphs.y, graphs.w)
144
+
145
+ loss_l1 = F.l1_loss(y_pred, graphs.y)
146
+
147
+ self.log('loss', loss, **self.logs_kwargs)
148
+ self.log('loss_l1', loss_l1, prog_bar=True, **self.logs_kwargs)
149
+
150
+ tqdm_dict = {'loss': loss.detach()}
151
+ output = OrderedDict({
152
+ 'loss': loss,
153
+ 'targets': graphs.y,
154
+ 'preds': y_pred.detach(),
155
+ 'progress_bar': tqdm_dict,
156
+ 'log': tqdm_dict
157
+ })
158
+ return output
159
+
160
+ def _validation_step(self, graphs, batch_idx):
161
+ with torch.no_grad():
162
+ y_pred = self.compute_y_pred(graphs, training=False)
163
+ loss = self.compute_training_loss(y_pred, graphs.y, graphs.w)
164
+ loss_l1 = F.l1_loss(y_pred, graphs.y)
165
+ return y_pred, loss, loss_l1
166
+
167
+ def validation_step(self, batch, batch_idx):
168
+ graphs = self.resolve_batch(batch)
169
+ y_pred, loss, loss_l1 = self._validation_step(graphs, batch_idx)
170
+ # tensorboard = self.logger.experiment
171
+ # tensorboard.add_histogram("val_price", graphs.y)
172
+ # tensorboard.add_histogram("val_preds", y_pred)
173
+ self.log('val_loss', loss, prog_bar=True, sync_dist=True) #rank_zero_only=True
174
+ self.log('val_lossL1', loss_l1, prog_bar=True, sync_dist=True)
175
+ return loss
176
+
177
+ def training_epoch_end(self, outputs ):
178
+ # tensorboard = self.logger.experiment
179
+ # tensorboard.add_histogram("train_price", y)
180
+ # tensorboard.add_histogram("train_preds", torch.cat([ x["preds"] for x in outputs]))
181
+ # tensorboard.add_histogram("train_price", torch.cat([ x["targets"] for x in outputs]))
182
+ print("Epoch %d done"%self.current_epoch)
183
+
184
+ def test_step(self, batch, batch_idx):
185
+ graphs = self.resolve_batch(batch)
186
+ y_pred, loss, loss_l1 = self._validation_step(graphs, batch_idx)
187
+ tensorboard = self.logger.experiment
188
+ # tensorboard.add_histogram("test_price", graphs.y)
189
+ # tensorboard.add_histogram("test_preds", y_pred)
190
+ self.log('test_loss', loss, prog_bar=True, sync_dist=True)
191
+ self.log('test_lossL1', loss_l1, prog_bar=True, sync_dist=True)
192
+ return loss
193
+
194
+ def configure_optimizers(self):
195
+ lr = self.hparams.lr
196
+ b1 = self.hparams.b1
197
+ b2 = self.hparams.b2
198
+
199
+ opt = torch.optim.Adam(self.parameters(), lr=lr, betas=(b1, b2), weight_decay=self.hparams.weight_decay)
200
+
201
+ # for param_group in opt.param_groups:
202
+ # print(param_group["lr"])
203
+
204
+ conf = {
205
+ 'optimizer': opt,
206
+ 'scheduler': torch.optim.lr_scheduler.ReduceLROnPlateau(opt, verbose=True, cooldown=2,
207
+ patience=config.PATIENT_REDUCE_LR_PLATEAU_N_EPOCHS, ),
208
+ 'monitor': 'val_loss'
209
+ }
210
+
211
+ return conf
212
+
213
+ if __name__ == "__main__":
214
+ from torch_geometric.data import Batch
215
+
216
+ degree =[0, 41130, 117278, 70152, 3104]
217
+
218
+ gnn_class = "QdolarAR" #"GNN_PNAConv"# "GNN_AttentiveFP"
219
+
220
+ if gnn_class != "QdolarAR":
221
+ from pricePrediction.preprocessData.smilesToGraph import smiles_to_graph
222
+ else:
223
+ from pricePrediction.preprocessData.smilesToDescriptors import smiles_to_graph
224
+
225
+ g = smiles_to_graph("CCCCCCCCCCC")
226
+ nodes_n_features = g.x.shape[1]
227
+ edges_n_features = g.edge_attr.shape[1]
228
+
229
+ net = PricePredictorModule(nodes_n_features=nodes_n_features, edges_n_features=edges_n_features,
230
+ deg=degree, lr=1e-3, gnn_class=gnn_class)
231
+
232
+ print( net )
233
+
234
+ graphs = [smiles_to_graph(smi) for smi in ["CCCCCO", "CCCCCNCCCCN"]]
235
+ print(graphs[0])
236
+ print( net(Batch.from_data_list(graphs)))
coprinet/pricePrediction/predict/__init__.py ADDED
File without changes
coprinet/pricePrediction/predict/__main__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from pricePrediction.predict.predict import main
2
+
3
+ if __name__ == '__main__':
4
+ main()
coprinet/pricePrediction/predict/predict.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os, sys
3
+ from itertools import islice
4
+ import numpy as np
5
+ import pandas as pd
6
+ import torch
7
+ from joblib import Parallel, delayed
8
+ from more_itertools import chunked
9
+ from torch.utils.data import IterableDataset, DataLoader
10
+ from torch_geometric.data import Batch
11
+ from tqdm import tqdm
12
+ import pytorch_lightning as pl
13
+ from pricePrediction.nets.netsGraph import PricePredictorModule
14
+ from pricePrediction.config import NUM_WORKERS_PER_GPU, BATCH_SIZE, DEFAULT_MODEL, USE_FEATURES_NET, \
15
+ BUFFER_N_BATCHES_FOR_PRED
16
+
17
+
18
+ class GraphPricePredictor():
19
+ name = "price_GNN"
20
+
21
+ def __init__(self, model_path=DEFAULT_MODEL, n_gpus = 1, n_cpus= NUM_WORKERS_PER_GPU, batch_size:int=BATCH_SIZE,
22
+ **kwargs):
23
+ self.model_path = model_path
24
+ self.n_gpus = n_gpus
25
+ self.n_cpus = n_cpus
26
+ self.batch_size = batch_size
27
+ self.trainer = pl.Trainer(gpus=self.n_gpus, logger=False)
28
+ self.model = PricePredictorModule.load_from_checkpoint(
29
+ self.model_path,
30
+ batch_size=self.batch_size,
31
+ strict=False,
32
+ )
33
+
34
+ if USE_FEATURES_NET:
35
+ from pricePrediction.preprocessData.smilesToDescriptors import smiles_to_graph
36
+
37
+ else:
38
+ from pricePrediction.preprocessData.smilesToGraph import smiles_to_graph
39
+
40
+ self.smiles_to_graph = smiles_to_graph
41
+
42
+ def prepare_smi(self, idx_smi):
43
+ idx, smi = idx_smi
44
+ graph = self.smiles_to_graph(smi)
45
+ if graph is None:
46
+ return None
47
+ graph.input_idx = idx
48
+ return graph
49
+
50
+ def yieldPredictions(self, smiles_generator, buffer_n_batches=BUFFER_N_BATCHES_FOR_PRED):
51
+ buffer_size = buffer_n_batches * self.batch_size
52
+ preds_iter = map(lambda x: self.predictListOfSmiles(x), tqdm(chunked(smiles_generator, buffer_size)))
53
+ for preds_batch in preds_iter:
54
+ for pred in preds_batch:
55
+ yield pred
56
+
57
+ def predictListOfSmiles(self, smiles_list):
58
+ smiles_list = list(smiles_list)
59
+ graphs_list = list(filter(None.__ne__, map(self.prepare_smi, enumerate(smiles_list) )))
60
+ graphs_fn = lambda : graphs_list
61
+ dataset = MyIterableDataset(graphs_fn, self.n_cpus)
62
+ dataloader = DataLoader(dataset=dataset, batch_size=self.batch_size, collate_fn=Batch.from_data_list,
63
+ num_workers=self.n_cpus)
64
+
65
+ preds = self.trainer.predict(self.model, dataloader)
66
+ n_smiles = len(smiles_list)
67
+ all_preds = np.nan * np.ones(n_smiles)
68
+ for i, batch in enumerate(dataloader):
69
+ batch_preds = preds[i].to("cpu").numpy()
70
+ idxs = batch.input_idx.to("cpu").numpy().astype(np.int64).tolist()
71
+ all_preds[idxs] = batch_preds
72
+ return all_preds
73
+
74
+
75
+ class MyIterableDataset(IterableDataset):
76
+ def __init__(self, generator_fun, num_workers):
77
+ super().__init__()
78
+ self.generator_fun = generator_fun
79
+ self.num_workers = num_workers
80
+
81
+ def __iter__(self):
82
+ worker_info = torch.utils.data.get_worker_info()
83
+ if worker_info is not None:
84
+ uid = torch.utils.data.get_worker_info().id
85
+ return islice( self.generator_fun(), uid, None, self.num_workers)
86
+ else:
87
+ return iter(self.generator_fun())
88
+
89
+
90
+ def main():
91
+ parser = argparse.ArgumentParser(prog="CoPriNet")
92
+ parser.add_argument("input_csv_file", type=str, help="The input csv filename containing a smiles column")
93
+ parser.add_argument("-o", "--output_file", type=str, required=False, default=None, help="The output filename that will be "
94
+ "identical to the input_csv_file but with one additional column for the f1")
95
+ parser.add_argument("--smiles_colname", type=str, required=False, default="SMILES", help="The colname for SMILES "
96
+ "in the input file. Default: %(default)s")
97
+ parser.add_argument("--model_path", type=str, required=False, default=DEFAULT_MODEL,
98
+ help="The CoPriNet model checkpoing path. Default: %(default)s")
99
+
100
+ parser.add_argument("--n_cpus", type=int, required=False, default=NUM_WORKERS_PER_GPU,
101
+ help="The number of cpu workers. Default: %(default)s")
102
+
103
+ parser.add_argument("--batch_size", type=int, required=False, default=BATCH_SIZE,
104
+ help="Batch size. Default: %(default)s")
105
+
106
+ parser.add_argument("--convert_to_g", action="store_true", help="Convert the f1 from $/mmol to $/g")
107
+
108
+ coprinet_colname = "CoPriNet"
109
+
110
+ args = parser.parse_args()
111
+
112
+ df = pd.read_csv(args.input_csv_file)
113
+ nans = df[args.smiles_colname].isna()
114
+ df = df[~nans]
115
+ smiles_list = df[args.smiles_colname]
116
+ predictor = GraphPricePredictor( **vars(args))
117
+ preds = predictor.yieldPredictions(smiles_list)
118
+ if args.convert_to_g:
119
+ from rdkit import Chem
120
+ def convert_pred(smi, pred):
121
+ mol = Chem.MolFromSmiles(smi)
122
+ if mol is None:
123
+ return np.nan
124
+ mw = Chem.Descriptors.ExactMolWt(mol)
125
+ price = np.log( np.exp(pred)*1000/mw)
126
+ return price
127
+
128
+ preds = Parallel(n_jobs=args.n_cpus)(delayed(convert_pred)(smi, pred) for smi, pred in zip(smiles_list, preds))
129
+ if args.output_file is None:
130
+ for smi, pred in zip(smiles_list, preds):
131
+ print("%s\t%.4f" % (smi, pred))
132
+ else:
133
+ df[coprinet_colname] = list(preds)
134
+ df.to_csv(args.output_file, index=False)
135
+
136
+ if __name__ == '__main__':
137
+ main()
coprinet/pricePrediction/preprocessData/__init__.py ADDED
File without changes
coprinet/pricePrediction/preprocessData/prepareDataMol2Price.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gzip
2
+ import os
3
+ import re
4
+ import sys
5
+ import time
6
+ from functools import reduce
7
+ from itertools import chain
8
+ from multiprocessing import cpu_count
9
+
10
+ import lmdb
11
+ import psutil
12
+ import joblib
13
+ import torch
14
+ from joblib import Parallel, delayed
15
+ import numpy as np
16
+ from pricePrediction import config
17
+ from pricePrediction.config import USE_MMOL_INSTEAD_GRAM
18
+ from pricePrediction.preprocessData.serializeDatapoints import getExampleId, serializeExample
19
+ from pricePrediction.utils import tryMakedir, getBucketRanges, search_buckedId, EncodedDirNamesAndTemplates
20
+ from .smilesToGraph import compute_nodes_degree, fromPerGramToPerMMolPrice
21
+
22
+ if config.USE_FEATURES_NET:
23
+ from .smilesToDescriptors import smiles_to_graph
24
+
25
+ else:
26
+ from .smilesToGraph import smiles_to_graph
27
+
28
+ PER_WORKER_MEMORY_GB = 2
29
+
30
+ class DataBuilder():
31
+
32
+ def __init__(self, n_cpus= config.N_CPUS):
33
+ if n_cpus is None:
34
+ mem_gib = psutil.virtual_memory().available / (1024. ** 3)
35
+ n_cpus = int(max(1, min(cpu_count(), mem_gib // PER_WORKER_MEMORY_GB)))
36
+
37
+ self.n_cpus = n_cpus
38
+
39
+ def processOneFileOfSmiles(self, encodedDir, fileNum, datasetSplit, fname, nrows=None):
40
+ print("processing %s"%fname)
41
+ if fname.endswith(".csv"):
42
+ open_fun = open
43
+ decode_line = lambda line: line
44
+ elif fname.endswith(".csv.gz"):
45
+ open_fun = gzip.open
46
+ decode_line = lambda line : line.decode('utf-8')
47
+ else:
48
+ raise ValueError("Bad file format")
49
+
50
+ cols = ['SMILES','price']
51
+
52
+ names = EncodedDirNamesAndTemplates(encodedDir)
53
+ outFname_base = datasetSplit + "_" + str(fileNum) + "_lmdb"
54
+ outFname = os.path.join(encodedDir, datasetSplit,outFname_base)
55
+ env = lmdb.open(outFname, map_size=10737418240)
56
+ one_example_graph = smiles_to_graph("CCOCCC")
57
+ degs = compute_nodes_degree(None)
58
+ min_in_x = torch.ones(one_example_graph.x.shape[-1])*float('inf')
59
+ max_in_x = -torch.ones(one_example_graph.x.shape[-1])*float('inf')
60
+
61
+ num_examples = 0
62
+ bucket_ranges = getBucketRanges()
63
+ n_per_bucket = np.zeros(len(bucket_ranges), dtype= np.int64)
64
+
65
+ with open_fun(fname) as f_in:
66
+ header = decode_line(f_in.readline()).strip().split(",")
67
+ try:
68
+ smi_index, price_index = [ header.index(col) for col in cols]
69
+ except ValueError:
70
+ smi_index, price_index = 0,1
71
+ with env.begin(write=True) as sink, \
72
+ gzip.open(names.SELECTED_DATAPOINTS_TEMPLATE % (datasetSplit, fileNum), "wt") as f_out:
73
+ f_out.write("SMILES,price\n")
74
+ cur_time = time.time()
75
+ for i, line in enumerate(f_in):
76
+ lineArray = decode_line(line).strip().split(",")
77
+ smi, price = lineArray[smi_index], lineArray[price_index]
78
+ price = float(price)
79
+ graph = smiles_to_graph(smi)
80
+ if graph is None:
81
+ continue
82
+ #Save the original smiles-price
83
+ f_out.write("%s,%s\n"%(smi, price))
84
+
85
+ # Use the per mmol price
86
+ if USE_MMOL_INSTEAD_GRAM:
87
+ price = fromPerGramToPerMMolPrice(price, smi)
88
+ bucketId = search_buckedId( np.log(price), bucket_ranges)
89
+ n_per_bucket[bucketId] += 1
90
+
91
+ degs += compute_nodes_degree([graph])
92
+ min_in_x = torch.stack([min_in_x, torch.min(graph.x, 0)[0]]).min(0)[0]
93
+ max_in_x = torch.stack([max_in_x, torch.max(graph.x, 0)[0]]).max(0)[0]
94
+
95
+ fileId= getExampleId(outFname_base, num_examples)
96
+ sink.put(fileId, serializeExample(price, graph))
97
+ num_examples += 1
98
+ if nrows is not None and num_examples > nrows:
99
+ break
100
+ if i % 10000 == 0 and fileNum % self.n_cpus == 0:
101
+ new_time = time.time()
102
+ print("Current iteration: %d # task: %d (%.2f s) " % (i, fileNum, new_time - cur_time), end="\r")
103
+ cur_time = new_time
104
+ if fileNum % self.n_cpus == 0:
105
+ print()
106
+ return ((outFname, degs, min_in_x, max_in_x, num_examples, n_per_bucket),)
107
+
108
+
109
+ def getNFeatures(self):
110
+ one_graph = smiles_to_graph("CCCCCCO")
111
+ # print(one_graph)
112
+ return dict(nodes_n_features=one_graph["x"].shape[-1], edges_n_features=one_graph["edge_attr"].shape[-1])
113
+
114
+ def prepareDataset(self, inputDir=config.DATASET_DIRNAME, encodedDir=config.ENCODED_DIR, datasetSplit="train",
115
+ nrows=None, **kwargs):
116
+
117
+ assert datasetSplit in ["train", "val", "test"]
118
+ print("Computing %s dataset" % datasetSplit)
119
+ print("Using %d workers for data preparation"%self.n_cpus)
120
+
121
+ # os.environ["OMP_NUM_THREADS"] = "1"
122
+ # os.environ["MKL_NUM_THREADS"] = "1"
123
+
124
+ names = EncodedDirNamesAndTemplates(encodedDir)
125
+
126
+ tryMakedir(encodedDir, remove=False)
127
+ tryMakedir(os.path.join(encodedDir, datasetSplit))
128
+ tryMakedir(names.DIR_RAW_DATA_SELECTED)
129
+
130
+
131
+ fnames = [os.path.join(inputDir, fname) for fname in os.listdir(inputDir) if
132
+ re.match(config.RAW_DATA_FILE_SUFFIX, fname) and datasetSplit in fname]
133
+
134
+ assert len(fnames) > 0
135
+
136
+ results = Parallel(n_jobs=self.n_cpus, batch_size=1,
137
+ verbose=10)(delayed(self.processOneFileOfSmiles)(encodedDir, i, datasetSplit, fname, nrows=nrows)
138
+ for i, fname in enumerate(fnames))
139
+ results = chain.from_iterable(results)
140
+ results = list(results)
141
+ # print( results )
142
+ fnames_list, degrees, min_in_x, max_in_x, sizes_lis, n_per_bucket = zip(*results)
143
+
144
+ degrees = reduce(lambda prev, x: prev + x, degrees).numpy().tolist()
145
+ min_in_x = reduce(lambda prev, x: torch.stack([prev, x]).min(0)[0], min_in_x).numpy().tolist()
146
+ max_in_x = reduce(lambda prev, x: torch.stack([prev, x]).max(0)[0], max_in_x).numpy().tolist()
147
+
148
+ n_per_bucket = reduce(lambda prev, x: prev + x, n_per_bucket)
149
+ metadata_dict = {"name":datasetSplit, "fnames_list":fnames_list, "sizes_list": sizes_lis,
150
+ "total_size": sum(sizes_lis), "feature_stats":{"min":min_in_x, "max":max_in_x}}
151
+ metadata_dict.update(self.getNFeatures())
152
+ # print(metadata_dict)
153
+ joblib.dump(metadata_dict,
154
+ names.DATASET_METADATA_FNAME_TEMPLATE % datasetSplit)
155
+ joblib.dump(degrees, names.DEGREES_FNAME)
156
+
157
+ # print(n_per_bucket)
158
+ joblib.dump({"bucket_ranges": getBucketRanges(), "n_per_bucket":n_per_bucket}, names.BUCKETS_FNAME_TEMPLATE % datasetSplit)
159
+ print("Dataset %s computed" % datasetSplit)
160
+ return fnames_list
161
+
162
+
163
+
164
+ if __name__ == "__main__":
165
+ print( " ".join(sys.argv))
166
+ import argparse
167
+ parser = argparse.ArgumentParser()
168
+ parser.add_argument("-i", "--inputDir", type=str, default=config.DATASET_DIRNAME, help="Directory where smiles-price pairs are located")
169
+ parser.add_argument("-o", "--encodedDir", type=str, default=config.ENCODED_DIR)
170
+ parser.add_argument("-n", "--ncpus", type=int, default=config.N_CPUS)
171
+ parser.add_argument("--nrows", type=int, default=None, help="The number of rows to process in each file")
172
+
173
+ args = vars( parser.parse_args())
174
+ config.N_CPUS = args.get("ncpus", config.N_CPUS)
175
+
176
+ dataBuilder = DataBuilder(n_cpus=config.N_CPUS)
177
+ dataBuilder.prepareDataset(datasetSplit="train", **args)
178
+ dataBuilder.prepareDataset(datasetSplit="val", **args)
179
+ dataBuilder.prepareDataset(datasetSplit="test", **args)
180
+
181
+ '''
182
+ python -m pricePrediction.preprocessData.prepareDataMol2Price
183
+
184
+
185
+ '''
coprinet/pricePrediction/preprocessData/serializeDatapoints.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pickle
2
+ import torch
3
+
4
+
5
+ def getExampleId(outFname_base, num_example):
6
+ return ("%s-%08d" % (outFname_base, num_example)).encode("UTF-8")
7
+
8
+ def serializeExample(price, graph):
9
+ price = torch.tensor(float(price), dtype=torch.float32)
10
+ one_data_serialized = pickle.dumps( {"graph": graph, "price" :price})
11
+ return one_data_serialized
12
+
13
+ def deserializeExample(exampleBytes):
14
+ example_dict = pickle.loads(exampleBytes)
15
+ return example_dict["graph"], example_dict["price"]
coprinet/pricePrediction/preprocessData/smilesToDescriptors.py ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import numpy as np
3
+ from rdkit import Chem
4
+ from rdkit.Chem import rdMolDescriptors
5
+ from rdkit.Chem.Descriptors import ExactMolWt
6
+ from rdkit.ML.Descriptors.CompoundDescriptors import GetAllDescriptorNames
7
+ from torch_geometric.data import Data as GraphData
8
+ from rdkit.ML.Descriptors.MoleculeDescriptors import MolecularDescriptorCalculator
9
+
10
+
11
+
12
+
13
+ calculator = {}
14
+ def smiles_to_graph(smi):
15
+ mol = Chem.MolFromSmiles(smi)
16
+ if mol is None:
17
+ return None
18
+ if not calculator:
19
+ chosen_descriptors = ['BertzCT',
20
+ 'Chi0',
21
+ 'Chi0n',
22
+ 'Chi0v',
23
+ 'Chi1',
24
+ 'Chi1n',
25
+ 'Chi1v',
26
+ 'Chi2n',
27
+ 'Chi2v',
28
+ 'Chi3n',
29
+ 'Chi3v',
30
+ 'Chi4n',
31
+ 'Chi4v',
32
+ 'EState_VSA1',
33
+ 'EState_VSA10',
34
+ 'EState_VSA11',
35
+ 'EState_VSA2',
36
+ 'EState_VSA3',
37
+ 'EState_VSA4',
38
+ 'EState_VSA5',
39
+ 'EState_VSA6',
40
+ 'EState_VSA7',
41
+ 'EState_VSA8',
42
+ 'EState_VSA9',
43
+ 'ExactMolWt',
44
+ 'FpDensityMorgan1',
45
+ 'FpDensityMorgan2',
46
+ 'FpDensityMorgan3',
47
+ 'FractionCSP3',
48
+ 'HallKierAlpha',
49
+ 'HeavyAtomCount',
50
+ 'HeavyAtomMolWt',
51
+ 'Kappa1',
52
+ 'Kappa2',
53
+ 'Kappa3',
54
+ 'LabuteASA',
55
+ 'MaxAbsEStateIndex',
56
+ 'MaxEStateIndex',
57
+ 'MinAbsEStateIndex',
58
+ 'MinAbsPartialCharge',
59
+ 'MinEStateIndex',
60
+ 'MinPartialCharge',
61
+ 'MolLogP',
62
+ 'MolMR',
63
+ 'MolWt',
64
+ 'NHOHCount',
65
+ 'NOCount',
66
+ 'NumAliphaticCarbocycles',
67
+ 'NumAliphaticHeterocycles',
68
+ 'NumAliphaticRings',
69
+ 'NumAromaticCarbocycles',
70
+ 'NumAromaticHeterocycles',
71
+ 'NumAromaticRings',
72
+ 'NumHAcceptors',
73
+ 'NumHDonors',
74
+ 'NumHeteroatoms',
75
+ 'NumRotatableBonds',
76
+ 'NumSaturatedCarbocycles',
77
+ 'NumSaturatedHeterocycles',
78
+ 'NumSaturatedRings',
79
+ 'NumValenceElectrons',
80
+ 'PEOE_VSA1',
81
+ 'PEOE_VSA10',
82
+ 'PEOE_VSA11',
83
+ 'PEOE_VSA12',
84
+ 'PEOE_VSA13',
85
+ 'PEOE_VSA14',
86
+ 'PEOE_VSA2',
87
+ 'PEOE_VSA3',
88
+ 'PEOE_VSA4',
89
+ 'PEOE_VSA5',
90
+ 'PEOE_VSA6',
91
+ 'PEOE_VSA7',
92
+ 'PEOE_VSA8',
93
+ 'PEOE_VSA9',
94
+ 'RingCount',
95
+ 'SMR_VSA1',
96
+ 'SMR_VSA10',
97
+ 'SMR_VSA2',
98
+ 'SMR_VSA3',
99
+ 'SMR_VSA4',
100
+ 'SMR_VSA5',
101
+ 'SMR_VSA6',
102
+ 'SMR_VSA7',
103
+ 'SMR_VSA9',
104
+ 'SlogP_VSA1',
105
+ 'SlogP_VSA10',
106
+ 'SlogP_VSA11',
107
+ 'SlogP_VSA12',
108
+ 'SlogP_VSA2',
109
+ 'SlogP_VSA3',
110
+ 'SlogP_VSA4',
111
+ 'SlogP_VSA5',
112
+ 'SlogP_VSA6',
113
+ 'SlogP_VSA7',
114
+ 'SlogP_VSA8',
115
+ 'TPSA',
116
+ 'VSA_EState1',
117
+ 'VSA_EState10',
118
+ 'VSA_EState2',
119
+ 'VSA_EState3',
120
+ 'VSA_EState4',
121
+ 'VSA_EState5',
122
+ 'VSA_EState6',
123
+ 'VSA_EState7',
124
+ 'VSA_EState8',
125
+ 'VSA_EState9',
126
+ 'fr_Al_COO',
127
+ 'fr_Al_OH',
128
+ 'fr_Al_OH_noTert',
129
+ 'fr_ArN',
130
+ 'fr_Ar_COO',
131
+ 'fr_Ar_N',
132
+ 'fr_Ar_NH',
133
+ 'fr_Ar_OH',
134
+ 'fr_COO',
135
+ 'fr_COO2',
136
+ 'fr_C_O',
137
+ 'fr_C_O_noCOO',
138
+ 'fr_C_S',
139
+ 'fr_HOCCN',
140
+ 'fr_Imine',
141
+ 'fr_NH0',
142
+ 'fr_NH1',
143
+ 'fr_NH2',
144
+ 'fr_N_O',
145
+ 'fr_Ndealkylation1',
146
+ 'fr_Ndealkylation2',
147
+ 'fr_Nhpyrrole',
148
+ 'fr_SH',
149
+ 'fr_aldehyde',
150
+ 'fr_alkyl_carbamate',
151
+ 'fr_alkyl_halide',
152
+ 'fr_allylic_oxid',
153
+ 'fr_amide',
154
+ 'fr_amidine',
155
+ 'fr_aniline',
156
+ 'fr_aryl_methyl',
157
+ 'fr_azide',
158
+ 'fr_azo',
159
+ 'fr_barbitur',
160
+ 'fr_benzene',
161
+ 'fr_benzodiazepine',
162
+ 'fr_bicyclic',
163
+ 'fr_dihydropyridine',
164
+ 'fr_epoxide',
165
+ 'fr_ester',
166
+ 'fr_ether',
167
+ 'fr_furan',
168
+ 'fr_guanido',
169
+ 'fr_halogen',
170
+ 'fr_hdrzine',
171
+ 'fr_hdrzone',
172
+ 'fr_imidazole',
173
+ 'fr_imide',
174
+ 'fr_isothiocyan',
175
+ 'fr_ketone',
176
+ 'fr_ketone_Topliss',
177
+ 'fr_lactam',
178
+ 'fr_lactone',
179
+ 'fr_methoxy',
180
+ 'fr_morpholine',
181
+ 'fr_nitrile',
182
+ 'fr_nitro',
183
+ 'fr_nitro_arom',
184
+ 'fr_nitro_arom_nonortho',
185
+ 'fr_nitroso',
186
+ 'fr_oxazole',
187
+ 'fr_oxime',
188
+ 'fr_para_hydroxylation',
189
+ 'fr_phenol',
190
+ 'fr_phenol_noOrthoHbond',
191
+ 'fr_phos_acid',
192
+ 'fr_phos_ester',
193
+ 'fr_piperdine',
194
+ 'fr_piperzine',
195
+ 'fr_priamide',
196
+ 'fr_pyridine',
197
+ 'fr_quatN',
198
+ 'fr_sulfide',
199
+ 'fr_sulfonamd',
200
+ 'fr_sulfone',
201
+ 'fr_term_acetylene',
202
+ 'fr_tetrazole',
203
+ 'fr_thiazole',
204
+ 'fr_thiocyan',
205
+ 'fr_thiophene',
206
+ 'fr_unbrch_alkane',
207
+ 'fr_urea',
208
+ 'qed']
209
+
210
+ calculator["calculator"] = MolecularDescriptorCalculator(chosen_descriptors)
211
+
212
+ x = np.array(calculator["calculator"].CalcDescriptors(mol))
213
+ x = x.reshape((1,-1))
214
+ x = torch.FloatTensor(x)
215
+ if torch.isnan(x).any():
216
+ return None
217
+ graph = GraphData(x=x, edge_index=torch.zeros(2,0, dtype=torch.long), edge_attr=torch.zeros(0,1))
218
+ return graph
219
+
220
+ def test(smi = "C1=CC=C(C=C1)CC(C(=O)O)N"):
221
+
222
+ mol = Chem.MolFromSmiles(smi)
223
+ # print(mol)
224
+ graph = smiles_to_graph(smi)
225
+ print(graph.keys)
226
+ print(graph.x)
227
+ print(graph.edge_index)
228
+ print( graph.edge_attr )
229
+ print( graph.x.shape, graph.edge_index.shape, graph.edge_attr.shape)
230
+ print(graph.x.max(), graph.x.min())
231
+ for idx in range(mol.GetNumAtoms()):
232
+ mol.GetAtomWithIdx(idx).SetProp('molAtomMapNumber', str(mol.GetAtomWithIdx(idx).GetIdx()))
233
+ from matplotlib import pyplot as plt; from rdkit.Chem import Draw; plt.imshow(Draw.MolsToGridImage([mol], molsPerRow=2)); plt.show()
234
+
235
+
236
+ if __name__ == "__main__":
237
+ smis_list = ["[C@H](C)1CCCO1", "O[C@@H](N)C", "C1=CC=C(C=C1)CC(C(=O)O)N"]
238
+ for smi in smis_list:
239
+ test(smi)
coprinet/pricePrediction/preprocessData/smilesToGraph.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import numpy as np
3
+ from rdkit import Chem
4
+ from rdkit.Chem.Descriptors import ExactMolWt
5
+ from torch_geometric.data import Data as GraphData
6
+
7
+ ALLOWED_ATOMIC_NUMS = {6, 7, 8, 9, 15, 16, 17, 35}
8
+ MIN_NUM_ATOMS = 3
9
+ MAX_NUM_ATOMS = 60
10
+
11
+ def get_node_feats(mol: Chem.Mol, atom: Chem.Atom):
12
+ '''
13
+
14
+ :param mol: The mol from which the node features will be computed. Currently not used
15
+ :param atom: The atom from which the node features will be computed
16
+ :return:
17
+ '''
18
+ atom_num = atom.GetAtomicNum()
19
+ valence = atom.GetTotalValence()
20
+ charge = atom.GetFormalCharge()
21
+ degree = atom.GetDegree()
22
+ is_aromatic = atom.GetIsAromatic()
23
+ return [float(elem) for elem in [atom_num, valence, charge, degree, is_aromatic]]
24
+ # return [float(elem) for elem in [atom_num, valence, charge, is_aromatic]]
25
+
26
+ # results = one_of_k_encoding_unk(
27
+ # atom.GetSymbol(),
28
+ # [
29
+ # 'B',
30
+ # 'C',
31
+ # 'N',
32
+ # 'O',
33
+ # 'F',
34
+ # 'Si',
35
+ # 'P',
36
+ # 'S',
37
+ # 'Cl',
38
+ # 'As',
39
+ # 'Se',
40
+ # 'Br',
41
+ # 'Te',
42
+ # 'I',
43
+ # 'At',
44
+ # 'other'
45
+ # ]) + one_of_k_encoding(atom.GetDegree(),
46
+ # [0, 1, 2, 3, 4, 5]) + \
47
+ # [atom.GetFormalCharge(), atom.GetNumRadicalElectrons()] + \
48
+ # one_of_k_encoding_unk(atom.GetHybridization(), [
49
+ # Chem.rdchem.HybridizationType.SP, Chem.rdchem.HybridizationType.SP2,
50
+ # Chem.rdchem.HybridizationType.SP3, Chem.rdchem.HybridizationType.
51
+ # SP3D, Chem.rdchem.HybridizationType.SP3D2, 'other'
52
+ # ]) + [atom.GetIsAromatic()]
53
+
54
+ def get_bond_feats(mol: Chem.Mol, bond: Chem.Bond):
55
+ '''
56
+
57
+ :param mol: The mol from which the edge features will be computed. Currently not used
58
+ :param bond: The bond from which the edge features will be computed
59
+ :return:
60
+ '''
61
+ if bond.GetIsAromatic():
62
+ bond_num = 1.5
63
+ else:
64
+ bond_num = bond.GetBondType()
65
+ bond_num = float(bond_num)
66
+ if bond_num > 3:
67
+ bond_num = 4
68
+
69
+ # return [bond_num ]
70
+ bondInfo = [bond_num, int(bond.GetIsAromatic()), int(bond.GetIsConjugated()), int(bond.IsInRing()) ]
71
+ # bondInfo += [["STEREONONE", "STEREOANY", "STEREOZ", "STEREOE"].index(str(bond.GetStereo()))]
72
+
73
+ return bondInfo
74
+
75
+ def smiles_to_graph(smi):
76
+ mol = Chem.MolFromSmiles(smi)
77
+ if mol is None:
78
+ return None
79
+
80
+ # remove stereo information, such as inward and outward edges
81
+ Chem.RemoveStereochemistry(mol)
82
+
83
+ nodes = []
84
+ edges_index, edges_attr = [], []
85
+
86
+ cur_atom_id = 0
87
+ rdkit_atomId_to_atom_id = {}
88
+ for bond in mol.GetBonds():
89
+ atom1_idx = bond.GetBeginAtomIdx()
90
+ atom2_idx = bond.GetEndAtomIdx()
91
+ atom1 = mol.GetAtomWithIdx(atom1_idx)
92
+ atom2 = mol.GetAtomWithIdx(atom2_idx)
93
+ atomicNum1 = atom1.GetAtomicNum()
94
+ atomicNum2 = atom2.GetAtomicNum()
95
+
96
+ if atomicNum1 == 0 or atomicNum2 == 0:
97
+ continue
98
+ if atomicNum1 not in ALLOWED_ATOMIC_NUMS or atomicNum2 not in ALLOWED_ATOMIC_NUMS:
99
+ return None
100
+ if atom1_idx not in rdkit_atomId_to_atom_id:
101
+ rdkit_atomId_to_atom_id[atom1_idx] = cur_atom_id
102
+ nodes.append( get_node_feats(mol, atom1) )
103
+ cur_atom_id += 1
104
+ if atom2_idx not in rdkit_atomId_to_atom_id:
105
+ rdkit_atomId_to_atom_id[atom2_idx] = cur_atom_id
106
+ nodes.append( get_node_feats(mol, atom2) )
107
+ cur_atom_id += 1
108
+ idx1, idx2 = rdkit_atomId_to_atom_id[atom1_idx], rdkit_atomId_to_atom_id[atom2_idx]
109
+ # print(atom1_idx, idx1, atom2_idx, idx2)
110
+ edges_index.append([idx1, idx2])
111
+ edges_index.append([idx2, idx1])
112
+
113
+ bond_feats = get_bond_feats(mol, bond)
114
+ edges_attr.extend([bond_feats, bond_feats])
115
+
116
+ x = np.array(nodes, dtype=np.float32)
117
+
118
+ if x.shape[0] < MIN_NUM_ATOMS or x.shape[0] > MAX_NUM_ATOMS:
119
+ return None
120
+ edges_index = np.array( edges_index).T.copy().astype( np.int64 )
121
+ edges_attr = np.array(edges_attr, dtype=np.float32)
122
+ graph_dict = dict(x=x, edge_index=edges_index, edge_attr=edges_attr)
123
+ graph = GraphData(**{key: torch.tensor(val) for key, val in graph_dict.items()} )
124
+ return graph
125
+
126
+ def fromPerGramToPerMMolPrice(price, smi):
127
+ mol = Chem.MolFromSmiles(smi)
128
+ if mol is None:
129
+ return None
130
+ return price * ExactMolWt(mol) / 1000
131
+
132
+ def compute_nodes_degree(graphs, max_degree=7):
133
+ from torch_geometric.utils import degree
134
+ import torch
135
+ deg = torch.zeros(max_degree, dtype=torch.long)
136
+ if graphs is None:
137
+ return deg
138
+ for data in graphs:
139
+ d = degree(data.edge_index[1], num_nodes=data.num_nodes, dtype=torch.long)
140
+ deg += torch.bincount(d, minlength=deg.numel())[:deg.numel()]
141
+ return deg
142
+
143
+ def test(smi = "C1=CC=C(C=C1)CC(C(=O)O)N"):
144
+
145
+ mol = Chem.MolFromSmiles(smi)
146
+ # print(mol)
147
+ graph = smiles_to_graph(smi)
148
+ print(graph.keys)
149
+ print(graph.x)
150
+ print(graph.edge_index)
151
+ print( graph.edge_attr )
152
+ print( graph.x.shape, graph.edge_index.shape, graph.edge_attr.shape)
153
+ for idx in range(mol.GetNumAtoms()):
154
+ mol.GetAtomWithIdx(idx).SetProp('molAtomMapNumber', str(mol.GetAtomWithIdx(idx).GetIdx()))
155
+ from matplotlib import pyplot as plt; from rdkit.Chem import Draw; plt.imshow(Draw.MolsToGridImage([mol], molsPerRow=2)); plt.show()
156
+
157
+ if __name__ == "__main__":
158
+ smis_list = ["[C@H](C)1CCCO1", "O[C@@H](N)C", "C1=CC=C(C=C1)CC(C(=O)O)N"]
159
+ for smi in smis_list:
160
+ test(smi)
coprinet/pricePrediction/selectFromRawData/__init__.py ADDED
File without changes
coprinet/pricePrediction/selectFromRawData/selectFromRawDataNonVirtualAll.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, sys
2
+ from argparse import ArgumentParser
3
+
4
+ from pricePrediction.config import TEST_SIZE
5
+ from pricePrediction.selectFromRawData.selectFromRawDataNonVirtualAll_base import RawDataSelector
6
+ from pricePrediction import config
7
+
8
+ class SelectFromRawDataNonVirtualAll(RawDataSelector):
9
+
10
+ DESIRED_PARAMS_TO_ASK= ['full_dataset_fname', 'computed_datadir', 'test_size', 'nrows']
11
+ def __init__(self, full_dataset_fname: str =
12
+ os.path.join(config.RAW_DATA_DIRNAME, "mcule_purchasable_full_prices_210616_O9Jw1D_only_READILY.csv.gz"),
13
+ computed_datadir: str = config.DATASET_DIRNAME,
14
+ test_size:int=TEST_SIZE, nrows:int=None):
15
+ '''
16
+
17
+ :param str full_dataset_fname: The fname for the whole Mcule csv file (csv.gz)
18
+ :param str bb_fname: The fname for the building blocks csv file (csv.gz)
19
+ :param str computed_datadir: The directory where the selected records will be saved
20
+ :param int test_size: The number of entries to include in the test set
21
+ :param int nrows: The number of rows to process in the full_dataset_fname. If None, process all rows
22
+ '''
23
+ super().__init__(full_dataset_fname, None, computed_datadir, test_size=test_size, nrows=nrows)
24
+
25
+ def operate_data(self, data):
26
+
27
+ # data = data[ ~ data.iloc[:,-1].isna() ]
28
+ data = data[self.COLNAMES[:3]]
29
+ data = data.rename(columns={self.COLNAMES[2]: "price"})
30
+ del data["Mcule ID"]
31
+ return data
32
+
33
+ if __name__ == "__main__":
34
+ parser = ArgumentParser(description="extract from raw data train/test/val partitions")
35
+ SelectFromRawDataNonVirtualAll.addParamsToArgParse(parser)
36
+ args = parser.parse_args()
37
+ SelectFromRawDataNonVirtualAll(**vars(args)).compute()
38
+
39
+ '''
40
+ python -m pricePrediction.selectFromRawData.selectFromRawDataNonVirtualAll
41
+ '''
coprinet/pricePrediction/selectFromRawData/selectFromRawDataNonVirtualAll_base.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import dask.dataframe as dd
3
+ import pandas as pd
4
+
5
+ from pricePrediction import config
6
+ from pricePrediction.ArgParser_base import ArgParseable
7
+ from pricePrediction.config import TEST_SIZE
8
+
9
+
10
+ class RawDataSelector(ArgParseable):
11
+ COLNAMES = "Mcule ID,SMILES,price 1 (USD),amount 1 (mg),delivery time 1 (w.days),available amount 1 (mg)".split(",")
12
+
13
+ DESIRED_PARAMS_TO_ASK= ['full_dataset_fname', 'bb_fname', 'computed_datadir', 'test_size', 'nrows']
14
+ def __init__(self, full_dataset_fname: str, bb_fname: str= config.BUILDING_BLOCKS_FNAME,
15
+ computed_datadir: str= config.DATASET_DIRNAME, test_size:int=TEST_SIZE, nrows:int=None):
16
+ '''
17
+
18
+ :param str full_dataset_fname: The fname for the whole Mcule csv file (csv.gz)
19
+ :param str bb_fname: The fname for the building blocks csv file (csv.gz)
20
+ :param str computed_datadir: The directory where the selected records will be saved
21
+ :param int test_size: The number of entries to include in the test set
22
+ :param int nrows: The number of rows to process in the full_dataset_fname. If None, process all rows
23
+ '''
24
+ self.full_dataset_fname = full_dataset_fname
25
+ self.bb_fname = bb_fname
26
+ self.computed_datadir = computed_datadir
27
+ self.test_size = test_size
28
+ self.nrows = nrows
29
+
30
+ def operate_data(self, data):
31
+ raise NotImplementedError()
32
+
33
+ def compute(self):
34
+ if self.full_dataset_fname.endswith(".gz"):
35
+ data = pd.read_csv(self.full_dataset_fname, usecols=self.COLNAMES, nrows=self.nrows)
36
+ data = dd.from_pandas(data, npartitions=30)
37
+ else:
38
+ data = dd.read_csv(self.full_dataset_fname, usecols=self.COLNAMES,blocksize=None)
39
+ if self.nrows is not None:
40
+ data = data.head(n=self.nrows, compute=False)
41
+
42
+ data = self.operate_data(data)
43
+ self.saveDataSplits(data)
44
+
45
+
46
+ def saveDataSplits(self, data, computed_datadir=None):
47
+ if computed_datadir is None:
48
+ computed_datadir = self.computed_datadir
49
+ print("Saving results to %s"%computed_datadir)
50
+ if isinstance(data, dd.DataFrame):
51
+ n_elems = data.shape[0].compute()
52
+ print("Total size: ", n_elems)
53
+ test_frac = self.test_size/n_elems
54
+ data_train, data_test = data.random_split([1-test_frac, test_frac])
55
+
56
+ del data
57
+
58
+ data_test = data_test.repartition(npartitions= int(n_elems*test_frac//self.test_size)+1)
59
+ data_test.to_csv(os.path.join(computed_datadir, "mcule_full_test.csv_split_*.csv"), index=False)
60
+ del data_test
61
+
62
+ n_elems = data_train.shape[0].compute()
63
+ val_frac = self.test_size/n_elems
64
+ data_train, data_val = data_train.random_split([1-val_frac, val_frac])
65
+ data_val= data_val.repartition(npartitions= int(n_elems*val_frac//self.test_size)+1)
66
+
67
+ data_val.to_csv(os.path.join(computed_datadir, "mcule_full_val.csv_split_*.csv"), index=False)
68
+ del data_val
69
+
70
+ data_train= data_train.repartition(npartitions= int(n_elems*(1-val_frac)//self.test_size)+1)
71
+ data_train.to_csv(os.path.join(computed_datadir, "mcule_full_train.csv_split_*.csv"), index=False)
72
+ with open(os.path.join(computed_datadir,"params.txt"), "w") as f:
73
+ f.write(str(type(self))+"\n" )
74
+ f.write(str(self.full_dataset_fname)+"\n" )
75
+ f.write(str(self.bb_fname)+"\n" )
76
+ f.write(str(self.computed_datadir))
coprinet/pricePrediction/selectFromRawData/selectFromRawDataOnlyVirtualAll.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, sys
2
+ import dask.dataframe as dd
3
+ from argparse import ArgumentParser
4
+
5
+ from pricePrediction.config import TEST_SIZE
6
+ from pricePrediction.selectFromRawData.selectFromRawDataNonVirtualAll_base import RawDataSelector
7
+ import pandas as pd
8
+ from pricePrediction import config
9
+
10
+ class SelectFromRawDataOnlyVirtualAll(RawDataSelector):
11
+ DESIRED_PARAMS_TO_ASK= ['full_dataset_fname', 'bb_fname', 'computed_datadir',]
12
+ def __init__(self, full_dataset_fname: str =
13
+ os.path.join(config.RAW_DATA_DIRNAME, "mcule_purchasable_full_prices_210319_kLp4Zt_march.csv.gz"),
14
+ bb_fname: str = config.BUILDING_BLOCKS_FNAME,
15
+ computed_datadir: str = config.DATASET_DIRNAME,
16
+ max_size:int=10000000,
17
+ test_size:int=TEST_SIZE, nrows:int=None):
18
+ '''
19
+ :param str full_dataset_fname: The fname for the whole Mcule csv file (csv.gz)
20
+ :param str bb_fname: The fname for the building blocks csv file (csv.gz)
21
+ :param str computed_datadir: The directory where the selected records will be saved
22
+ :param int test_size: The number of entries to include in the test set
23
+ :param int nrows: The number of rows to process in the full_dataset_fname. If None, process all rows
24
+ '''
25
+ super().__init__(full_dataset_fname, bb_fname, computed_datadir,test_size=test_size,nrows=nrows)
26
+ self.max_size = max_size
27
+
28
+ def read_csv(self, csv_name):
29
+ data = dd.read_csv(csv_name, usecols=self.COLNAMES,blocksize=None)
30
+ data = data[ data[self.COLNAMES[-1]].isna() ] #To select compounds that are not in stock
31
+ data = data[self.COLNAMES[:3]]
32
+ data = data.rename(columns={self.COLNAMES[2]: "price"})
33
+ return data
34
+
35
+ def operate_data(self, data):
36
+ data = self.read_csv(self.full_dataset_fname)
37
+ size = data.shape[0].compute()
38
+ bb = pd.read_csv(self.bb_fname, sep="\t", header=None)
39
+ bb.columns = ["SMILES", "Mcule ID"]
40
+ del bb["SMILES"]
41
+ data = data[ (~data["Mcule ID"].isin(bb["Mcule ID"])) ]
42
+ size = data.shape[0].compute()
43
+ print("Total size: ",size)
44
+ sampling_size = min(size, self.max_size)
45
+ data = data.sample(frac= sampling_size/size)
46
+ print("Sampled size: ",sampling_size)
47
+ data = data[["SMILES", "price"]]
48
+ del bb
49
+ return data
50
+
51
+ if __name__ == "__main__":
52
+ parser = ArgumentParser(description="extract from raw data train/test/val partitions")
53
+ SelectFromRawDataOnlyVirtualAll.addParamsToArgParse(parser)
54
+ args = parser.parse_args()
55
+ SelectFromRawDataOnlyVirtualAll(**vars(args)).compute()
coprinet/pricePrediction/train/__init__.py ADDED
File without changes
coprinet/pricePrediction/train/trainNet.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import sys, os
3
+
4
+ # import torch
5
+ # torch.multiprocessing.set_start_method('spawn')
6
+ # torch.multiprocessing.set_sharing_strategy('file_system')
7
+
8
+ from pytorch_lightning import seed_everything
9
+ from pytorch_lightning.callbacks import EarlyStopping, ModelCheckpoint, StochasticWeightAveraging
10
+ import pytorch_lightning as pl
11
+
12
+ from pytorch_lightning.plugins import DDPPlugin
13
+
14
+ from pricePrediction import config
15
+ from pricePrediction.ArgParser_base import MyArgParser
16
+ from pricePrediction.dataManager.dataManager import GraphPriceDatamodule
17
+ from pricePrediction.evaluation.evaluation import PlotFigsTensorboardCallback, InferencePlotter
18
+ from pricePrediction.nets.netsGraph import PricePredictorModule
19
+
20
+ def parse_args():
21
+
22
+ parser = MyArgParser(prog=None, usage=None, description=None, )
23
+
24
+ #### Program args ####
25
+ parser.add_argument("-e", "--n_epochs", type=int, help="Number of epochs to use", default=1000)
26
+ parser.add_argument("-m", "--msg", type=str, help="A message describing the run", required=True)
27
+ parser.add_argument("-r", "--restore", type=str, help="Directory to trained checkpoint to continue training", default=None)
28
+ parser.add_argument("-c", "--config", type=str, help="Path to json file with the config arguments to create/update the "
29
+ "model")
30
+ parser.add_argument("-o", "--default_root_dir", type=str, help="The directory where to save "
31
+ "checkpoints and logs", default=None)
32
+ parser.add_argument("-g", "--gpus", type=int, help="Number of gpus to use", default=1)
33
+ parser.add_argument("-n", "--num_nodes", type=int, help="Number of nodes to use", default=None)
34
+ parser.add_argument("-s", "--random_seed", type=int, help="Random seed", default=121)
35
+
36
+ parser.add_argument( "--limit_train_batches", type=float, help="Train only with a fraction of the tranining set", default=None)
37
+ parser.add_argument("--swa", action="store_true", default=False,
38
+ help="Perform Stochastic Weight Averaging")
39
+ #### Model and data args ####
40
+
41
+ group = parser.add_argument_group(title="data")
42
+ GraphPriceDatamodule.addParamsToArgParse(group)
43
+ group = parser.add_argument_group(title="model")
44
+ PricePredictorModule.addParamsToArgParse(group)
45
+
46
+ cmd_args = parser.parse_args()
47
+ del cmd_args['positional arguments']
48
+ cmd_args['main'] = cmd_args['optional arguments']
49
+ del cmd_args['optional arguments']
50
+ return cmd_args
51
+
52
+ if __name__ == "__main__":
53
+ print( " ".join(sys.argv))
54
+ cmd_args = parse_args()
55
+
56
+ if cmd_args.get("config"):
57
+ print("Loading configuration file:", cmd_args.get("config"))
58
+ with open(cmd_args.get("config")) as f:
59
+ args = json.load(f)
60
+
61
+ for groupName in cmd_args:
62
+ args[groupName].update(cmd_args[groupName])
63
+ else:
64
+ args = cmd_args
65
+
66
+ print(args)
67
+
68
+ seed_everything(args["main"]["random_seed"])
69
+
70
+
71
+ if args['main']["restore"]:
72
+ prev_run_dir = os.path.expanduser(args['main']["restore"])
73
+ # hparams = yaml.load( os.path.join(prev_run_dir, "hparams.yaml"))
74
+ checkpointsDir = os.path.join(prev_run_dir, "checkpoints")
75
+ most_recent_checkpoint_fname = max([os.path.join(checkpointsDir, basename) for basename in os.listdir(checkpointsDir)],
76
+ key=os.path.getctime)
77
+
78
+ pl_model = PricePredictorModule.load_from_checkpoint(most_recent_checkpoint_fname, **args["model"])
79
+ data_args = pl_model.hparams.get("data_hparams")
80
+ data_args.update(args["data"])
81
+ dataModule = GraphPriceDatamodule(**data_args)
82
+
83
+ print("Previous model loaded!!")
84
+
85
+ else:
86
+ dataModule = GraphPriceDatamodule(**args["data"])
87
+
88
+ dataModule.prepare_data()
89
+ nodes_n_features, edges_n_features = dataModule.dims
90
+ nodes_degree = dataModule.get_nodes_degree()
91
+ args["model"].update( dict(
92
+ nodes_n_features=nodes_n_features, edges_n_features=edges_n_features,
93
+ deg=nodes_degree, data_hparams= args["data"], logs_only_in_epoch= args["main"].get("gpus", 0) > 1)
94
+ )
95
+
96
+ pl_model = PricePredictorModule( **args["model"] )
97
+
98
+ print( pl_model.hparams )
99
+
100
+ callbacks = [
101
+ EarlyStopping(monitor='val_loss', patience=60),
102
+ ModelCheckpoint(monitor='val_loss', verbose=True),
103
+ PlotFigsTensorboardCallback(frequency=5, save_csv=True)
104
+ ]
105
+
106
+ if args["main"].get("swa", False):
107
+ callbacks += [
108
+ StochasticWeightAveraging(annealing_epochs=config.COSINE_LR_SCHEDULE_N_EPOCHS, swa_lrs= pl_model.hparams.lr)
109
+ ]
110
+
111
+ default_root_dir = args['main'].get('default_root_dir', os.getcwd())
112
+ trainer_args = dict(gpus=1, max_epochs=args['main']["n_epochs"], progress_bar_refresh_rate=20,
113
+ default_root_dir=default_root_dir, auto_lr_find=True,
114
+ callbacks = callbacks)
115
+ if args["main"]["limit_train_batches"]:
116
+ trainer_args["limit_train_batches"] = args["main"]["limit_train_batches"]
117
+
118
+ ngpus = args['main'].get("gpus", -1)
119
+ if ngpus > 1:
120
+ trainer_args["gpus"] = ngpus
121
+ trainer_args["accelerator"] = "ddp"
122
+ trainer_args["plugins"] = DDPPlugin(find_unused_parameters=False),
123
+ os.environ["NCCL_NSOCKS_PERTHREAD"]="4"
124
+ os.environ["NCCL_SOCKET_NTHREADS"] = "2"
125
+ elif ngpus ==0:
126
+ trainer_args["gpus"] = 0
127
+ trainer_args["accelerator"] = "ddp_cpu"
128
+
129
+ if args['main'].get("num_nodes", None):
130
+ trainer_args["num_nodes"] = int(args['main'].get("num_nodes"))
131
+ trainer_args["accelerator"] = "ddp"
132
+ raise NotImplementedError()
133
+ # import torch.distributed as dist
134
+ # dist.init_process_group("gloo", rank=int(os.environ.get("NODE_RANK")), world_size=trainer_args["num_nodes"] )
135
+
136
+ try:
137
+ print(trainer_args)
138
+ trainer = pl.Trainer( **trainer_args )
139
+ trainer.fit(pl_model, dataModule)
140
+ res = trainer.test(pl_model, dataModule.test_dataloader(), verbose=True)[0]
141
+ r, __ = InferencePlotter(pl_module=pl_model).pred_and_plot( mode="test")
142
+ res["test_pearson-r"] = r
143
+ print(res)
144
+ finally:
145
+ if args['main'].get("num_nodes", None):
146
+ # dist.destroy_process_group()
147
+ pass
coprinet/pricePrediction/utils.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import numpy as np
4
+
5
+ from pricePrediction import config
6
+
7
+ def tryMakedir(dirname, remove=False):
8
+ if os.path.isdir(dirname):
9
+ if remove:
10
+ # print("removing %s"%dirname)
11
+ shutil.rmtree(dirname)
12
+ os.mkdir(dirname)
13
+ else:
14
+ os.mkdir(dirname)
15
+
16
+
17
+ def getBucketRanges(x0=1,x1=11, n_buckets=config.NUM_BUCKETS):
18
+ return np.linspace(x0, x1, num=n_buckets)
19
+
20
+
21
+ def search_buckedId(y, bucket_ranges):
22
+ n_buckets = len(bucket_ranges)
23
+ return min(n_buckets - 1, np.searchsorted(bucket_ranges, y))
24
+
25
+
26
+ class EncodedDirNamesAndTemplates(object):
27
+ def __init__(self, encodedDir):
28
+ self.DEGREES_FNAME = os.path.join(encodedDir, "graphs_degrees.joblib.pckl")
29
+ self.BUCKETS_FNAME_TEMPLATE = os.path.join(encodedDir, "n_per_bucket.%s.joblib.pckl")
30
+ self.DIR_RAW_DATA_SELECTED= os.path.join(encodedDir, "selected_smiles_price")
31
+ self.SELECTED_DATAPOINTS_TEMPLATE = os.path.join(self.DIR_RAW_DATA_SELECTED, "selected_smiles_price.%s.%s.csv.gz")
32
+ self.DATASET_METADATA_FNAME_TEMPLATE = os.path.join(encodedDir, "%s_metadata.joblib.pckl")
requirements.txt CHANGED
@@ -19,3 +19,8 @@ uvicorn[standard]>=0.30.0
19
  python-multipart>=0.0.9
20
  supabase>=2.31.0
21
  huggingface_hub>=0.28.0
 
 
 
 
 
 
19
  python-multipart>=0.0.9
20
  supabase>=2.31.0
21
  huggingface_hub>=0.28.0
22
+ pytorch-lightning==1.9.5
23
+ torch-geometric==2.8.0
24
+ torchmetrics==1.8.1
25
+ more-itertools==10.3.0
26
+ tqdm>=4.66.0
tools/online_screening.py CHANGED
@@ -10,7 +10,7 @@ from typing import Any
10
 
11
  import numpy as np
12
  from pymatgen.core import Structure
13
- from rdkit import Chem
14
 
15
  from tools.adsorption import predict_benzene, predict_toluene
16
  from tools.descriptors import compute_scm_eigenvalues
@@ -19,12 +19,7 @@ from tools.toxicity import predict_toxicity
19
 
20
 
21
  ROOT = Path(__file__).resolve().parent.parent
22
- ALL_DES_ROOT = ROOT.parent
23
- DOC_ROOT = ALL_DES_ROOT.parent
24
- SA_DIR = ALL_DES_ROOT / "0629_revise" / "Screening" / "筛选顺序" / "筛选顺序" / "2 配体可合成性"
25
- PRICE_ROOT = DOC_ROOT / "price_filter"
26
- COPRINET_ROOT = PRICE_ROOT / "CoPriNet-main"
27
- PYG201 = PRICE_ROOT / ".deps" / "pyg201_nodeps"
28
 
29
  ACTIVE_TOOLS = [
30
  "parse_cif",
@@ -150,7 +145,7 @@ def check_metals_tool(context: dict[str, Any], cif_path: str) -> dict[str, Any]:
150
 
151
  @lru_cache(maxsize=1)
152
  def _sa_module():
153
- path = SA_DIR / "sascore" / "sascorer.py"
154
  spec = importlib.util.spec_from_file_location("mofscreen_sascorer", path)
155
  if spec is None or spec.loader is None:
156
  raise RuntimeError(f"cannot load sascorer: {path}")
@@ -210,8 +205,6 @@ def predict_aquatic_toxicity_tool(context: dict[str, Any]) -> dict[str, Any]:
210
 
211
  @lru_cache(maxsize=1)
212
  def _price_predictor():
213
- if str(PYG201) not in sys.path:
214
- sys.path.insert(0, str(PYG201))
215
  if str(COPRINET_ROOT) not in sys.path:
216
  sys.path.insert(0, str(COPRINET_ROOT))
217
 
 
10
 
11
  import numpy as np
12
  from pymatgen.core import Structure
13
+ from rdkit import Chem, RDConfig
14
 
15
  from tools.adsorption import predict_benzene, predict_toluene
16
  from tools.descriptors import compute_scm_eigenvalues
 
19
 
20
 
21
  ROOT = Path(__file__).resolve().parent.parent
22
+ COPRINET_ROOT = ROOT / "coprinet"
 
 
 
 
 
23
 
24
  ACTIVE_TOOLS = [
25
  "parse_cif",
 
145
 
146
  @lru_cache(maxsize=1)
147
  def _sa_module():
148
+ path = Path(RDConfig.RDContribDir) / "SA_Score" / "sascorer.py"
149
  spec = importlib.util.spec_from_file_location("mofscreen_sascorer", path)
150
  if spec is None or spec.loader is None:
151
  raise RuntimeError(f"cannot load sascorer: {path}")
 
205
 
206
  @lru_cache(maxsize=1)
207
  def _price_predictor():
 
 
208
  if str(COPRINET_ROOT) not in sys.path:
209
  sys.path.insert(0, str(COPRINET_ROOT))
210