D4CMPP2 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. D4CMPP2/_Data/AGENTS.md +24 -0
  2. D4CMPP2/_Data/Aqsoldb.csv +9291 -0
  3. D4CMPP2/_Data/BradleyMP.csv +3042 -0
  4. D4CMPP2/_Data/Lipophilicity.csv +1131 -0
  5. D4CMPP2/_Data/README.md +8 -0
  6. D4CMPP2/_Data/__init__.py +26 -0
  7. D4CMPP2/_Data/optical.csv +20237 -0
  8. D4CMPP2/_Data/test.csv +190 -0
  9. D4CMPP2/__init__.py +16 -0
  10. D4CMPP2/__main__.py +5 -0
  11. D4CMPP2/_main.py +500 -0
  12. D4CMPP2/cli.py +7 -0
  13. D4CMPP2/exceptions.py +53 -0
  14. D4CMPP2/grid_search.py +259 -0
  15. D4CMPP2/network_refer.yaml +160 -0
  16. D4CMPP2/networks/AFP_model.py +72 -0
  17. D4CMPP2/networks/AFPwithSolv_model.py +72 -0
  18. D4CMPP2/networks/DMPNN_model.py +90 -0
  19. D4CMPP2/networks/DMPNNwithSolv_model.py +89 -0
  20. D4CMPP2/networks/GAT_model.py +48 -0
  21. D4CMPP2/networks/GATwithSolv_model.py +63 -0
  22. D4CMPP2/networks/GCN_model.py +113 -0
  23. D4CMPP2/networks/GCNwithSolv_model.py +103 -0
  24. D4CMPP2/networks/GC_model.py +122 -0
  25. D4CMPP2/networks/ISATPM_model.py +14 -0
  26. D4CMPP2/networks/ISATPN_model.py +199 -0
  27. D4CMPP2/networks/ISAT_model.py +90 -0
  28. D4CMPP2/networks/MPNN_model.py +56 -0
  29. D4CMPP2/networks/MPNNwithSolv_model.py +72 -0
  30. D4CMPP2/networks/__init__.py +25 -0
  31. D4CMPP2/networks/base.py +250 -0
  32. D4CMPP2/networks/registry.py +187 -0
  33. D4CMPP2/networks/src/AFP.py +118 -0
  34. D4CMPP2/networks/src/BiDropout.py +29 -0
  35. D4CMPP2/networks/src/DMPNN.py +35 -0
  36. D4CMPP2/networks/src/GAT.py +69 -0
  37. D4CMPP2/networks/src/GC.py +85 -0
  38. D4CMPP2/networks/src/GCN.py +71 -0
  39. D4CMPP2/networks/src/ISAT.py +153 -0
  40. D4CMPP2/networks/src/Linear.py +49 -0
  41. D4CMPP2/networks/src/MPNN.py +56 -0
  42. D4CMPP2/networks/src/SolventLayer.py +62 -0
  43. D4CMPP2/networks/src/__init__.py +0 -0
  44. D4CMPP2/networks/src/distGCN.py +21 -0
  45. D4CMPP2/networks/src/pyg_hetero.py +24 -0
  46. D4CMPP2/optimize.py +472 -0
  47. D4CMPP2/src/Analyzer/ISAAnalyzer.py +458 -0
  48. D4CMPP2/src/Analyzer/ISAPNAnalyzer.py +366 -0
  49. D4CMPP2/src/Analyzer/ISAwSAnalyzer.py +117 -0
  50. D4CMPP2/src/Analyzer/MolAnalyzer.py +319 -0
  51. D4CMPP2/src/Analyzer/__init__.py +54 -0
  52. D4CMPP2/src/Analyzer/core.py +480 -0
  53. D4CMPP2/src/Analyzer/factory.py +166 -0
  54. D4CMPP2/src/Analyzer/interpretation.py +232 -0
  55. D4CMPP2/src/Analyzer/results.py +101 -0
  56. D4CMPP2/src/DataManager/Dataset/GraphDataset.py +314 -0
  57. D4CMPP2/src/DataManager/Dataset/ISAGraphDataset.py +384 -0
  58. D4CMPP2/src/DataManager/Dataset/__init__.py +0 -0
  59. D4CMPP2/src/DataManager/GraphGenerator/ISAGraphGenerator.py +223 -0
  60. D4CMPP2/src/DataManager/GraphGenerator/MolGraphGenerator.py +73 -0
  61. D4CMPP2/src/DataManager/GraphGenerator/__init__.py +14 -0
  62. D4CMPP2/src/DataManager/ISADataManager.py +67 -0
  63. D4CMPP2/src/DataManager/MolDataManager.py +735 -0
  64. D4CMPP2/src/DataManager/__init__.py +14 -0
  65. D4CMPP2/src/DataManager/contracts.py +179 -0
  66. D4CMPP2/src/NetworkManager/ISANetworkManager.py +12 -0
  67. D4CMPP2/src/NetworkManager/NetworkManager.py +520 -0
  68. D4CMPP2/src/NetworkManager/__init__.py +14 -0
  69. D4CMPP2/src/PostProcessor.py +160 -0
  70. D4CMPP2/src/TrainManager/ISATrainManager.py +26 -0
  71. D4CMPP2/src/TrainManager/TrainManager.py +254 -0
  72. D4CMPP2/src/TrainManager/__init__.py +14 -0
  73. D4CMPP2/src/TrainManager/callbacks.py +119 -0
  74. D4CMPP2/src/__init__.py +0 -0
  75. D4CMPP2/src/utils/PATH.py +246 -0
  76. D4CMPP2/src/utils/__init__.py +0 -0
  77. D4CMPP2/src/utils/argparser.py +56 -0
  78. D4CMPP2/src/utils/checkpointing.py +90 -0
  79. D4CMPP2/src/utils/config_resolution.py +123 -0
  80. D4CMPP2/src/utils/config_validation.py +370 -0
  81. D4CMPP2/src/utils/csv_validation.py +105 -0
  82. D4CMPP2/src/utils/data_quality.py +181 -0
  83. D4CMPP2/src/utils/featureizer.py +202 -0
  84. D4CMPP2/src/utils/functional_group.csv +169 -0
  85. D4CMPP2/src/utils/graph_cache.py +213 -0
  86. D4CMPP2/src/utils/leaderboard.py +212 -0
  87. D4CMPP2/src/utils/metrics.py +31 -0
  88. D4CMPP2/src/utils/module_loader.py +147 -0
  89. D4CMPP2/src/utils/output.py +80 -0
  90. D4CMPP2/src/utils/reproducibility.py +70 -0
  91. D4CMPP2/src/utils/run_manifest.py +175 -0
  92. D4CMPP2/src/utils/scaler.py +40 -0
  93. D4CMPP2/src/utils/sculptor.py +713 -0
  94. D4CMPP2/src/utils/splitting.py +250 -0
  95. D4CMPP2/src/utils/supportfile_saver.py +94 -0
  96. D4CMPP2/src/utils/tools.py +156 -0
  97. D4CMPP2/src/utils/transfer_learning.py +111 -0
  98. d4cmpp2-0.4.0.dist-info/METADATA +420 -0
  99. d4cmpp2-0.4.0.dist-info/RECORD +103 -0
  100. d4cmpp2-0.4.0.dist-info/WHEEL +5 -0
  101. d4cmpp2-0.4.0.dist-info/entry_points.txt +2 -0
  102. d4cmpp2-0.4.0.dist-info/licenses/LICENSE +21 -0
  103. d4cmpp2-0.4.0.dist-info/top_level.txt +1 -0
D4CMPP2/_main.py ADDED
@@ -0,0 +1,500 @@
1
+ import yaml
2
+ import os
3
+ import traceback
4
+ import importlib
5
+ import warnings
6
+ from pathlib import Path
7
+ from D4CMPP2.src.utils import argparser
8
+ from D4CMPP2.src.utils import PATH
9
+ from D4CMPP2.src.PostProcessor import PostProcessor
10
+ from D4CMPP2.src.utils import module_loader, supportfile_saver
11
+ from D4CMPP2.src.utils.output import get_output
12
+ from D4CMPP2.src.utils.run_manifest import RunManifest
13
+ from D4CMPP2.src.utils.config_resolution import (
14
+ ConfigResolution,
15
+ mark_derived,
16
+ merge_config_layers,
17
+ overlay_config_layer,
18
+ split_runtime_config,
19
+ )
20
+ from D4CMPP2.src.DataManager.contracts import validate_data_manager_contract
21
+ from D4CMPP2.src.utils.reproducibility import (
22
+ capture_backend_determinism,
23
+ configure_reproducibility,
24
+ restore_backend_determinism,
25
+ )
26
+ from D4CMPP2.src.utils.config_validation import (
27
+ validate_entry_args,
28
+ validate_network_entry,
29
+ validate_runtime_environment,
30
+ validate_sculptor_index_argument,
31
+ validate_training_config,
32
+ )
33
+
34
+ config0 = {
35
+ "data": None,
36
+ "target": None,
37
+ "network": None,
38
+
39
+ "scaler": "standard",
40
+ "optimizer": "Adam",
41
+
42
+ "max_epoch": 2000,
43
+ "batch_size": 256,
44
+ "learning_rate": 0.001,
45
+ "weight_decay": 0.0005,
46
+ "lr_patience": 80,
47
+ "early_stopping_patience": 200,
48
+ 'min_lr': 1e-5,
49
+
50
+ "device": "cuda:0",
51
+ "pin_memory": False,
52
+ "target_scaler_fit_scope": "train",
53
+ "legacy_silent_errors": False,
54
+ "random_seed": None,
55
+ "deterministic_algorithms": False,
56
+ "verbose": True,
57
+
58
+ 'hidden_dim': None,
59
+ 'conv_layers':None,
60
+ 'linear_layers': None,
61
+ 'dropout': None,
62
+
63
+ 'solv_hidden_dim': None,
64
+ 'solv_conv_layers': None,
65
+ 'solv_linear_layers': None,
66
+ }
67
+
68
+
69
+ def train(**kwargs):
70
+ """
71
+ Train the network with the given configuration.
72
+
73
+ Args for the training:
74
+ (Required args for the scratch training)
75
+ data : str
76
+ the name or path of the data file as a csv file. you can omit the ".csv" extension.
77
+ target : list[str]
78
+ the name of the target column.
79
+ network : str
80
+ the ID of the network to use. Refer to the networks.yaml file for the available networks ID.
81
+ molecule_columns: list[str]. Default= ['compound'] or ['compound', 'solvent']
82
+ the name of the columns to be used as the molecule columns. Please check the network sorce code for the required molecule columns for each network.
83
+ numeric_input_columns: list[str]. Default= []
84
+ the name of the columns to be used as numeric input features. Please check the network sorce code for the required numeric input columns for each network.
85
+
86
+ (Reqired args for continuing the training)
87
+ LOAD_PATH : str
88
+ the path of the directory that contains the model to continue the training.
89
+
90
+ (Required args for the transfer learning)
91
+ TRANSFER_PATH: str
92
+ the path of the directory that contains the model to transfer the learning.
93
+ lr_dict : dict (optional)
94
+ the dictionary for the learning rate of specific layers. e.g. {'GCNs': 0.0001}
95
+ you can find the layer names in "model_summary.txt" in the model directory.
96
+
97
+
98
+ (Optional args)
99
+ explicit_h_columns: list[str]. Default= []
100
+ the name of the columns to be used as explicit hydrogen features for the nodes.
101
+ scaler : str. Default= "standard",
102
+ ['standard', 'minmax', 'normalizer', 'robust', 'identity']
103
+ optimizer : str. Default= "Adam",
104
+ ['Adam', 'SGD', 'RMSprop', ... supported by torch.optim]
105
+ max_epoch : int. Defualt= 2000,
106
+ batch_size : int. Default= 256,
107
+ learning_rate: float. Defualt= 0.001,
108
+ weight_decay: float. Default= 0.0005,
109
+ lr_patience : int. Defualt= 80,
110
+ the number of epochs with no improvement after which learning rate will be reduced.
111
+ early_stopping_patience: 200,
112
+ the number of epochs with no improvement after which training will be stopped
113
+ min_lr : float. Defult= 1e-5,
114
+ the minimum learning rate
115
+ device : str. Defualt='cuda:0'
116
+ ['cpu', 'cuda:0', 'cuda:1', ...]
117
+ pin_memory : bool. Default= False
118
+ If True, the data loader will copy Tensors into CUDA pinned memory before returning them.
119
+ split_random_seed: int. Default= 42,
120
+ save_prediction: bool. Default= True,
121
+ callbacks : sequence of callback objects. Default= None
122
+ Runtime-only observers for training events. Callback objects
123
+ are not written to config.yaml or checkpoints.
124
+ verbose : bool. Default=True
125
+ Show informational messages, result tables, and progress bars.
126
+ Warnings and errors remain visible when False.
127
+
128
+ DATA_PATH : str. Default= None
129
+ the path of directory that contains the data file. If None, it will walk the subpath.
130
+ NET_REFER : str. Default= "{src}/network_refer.yaml"
131
+ the path to the network reference file.
132
+ MODEL_DIR : str. Default= "./_Models"
133
+ the path to the directory to save the models.
134
+ MODEL_PATH : str. Default= None
135
+ the path to the directory to save the model. If None, it will be created in the MODEL_DIR.
136
+ GRAPH_DIR : str. Default= "./_Graphs"
137
+ the path to the directory to save the graphs.
138
+ FRAG_REF : str. Default= "{src}/utils/functional_group.csv"
139
+ the path to the reference file for the functional groups.
140
+
141
+ Args for the network:
142
+ (The following args will be varied according to the network chosen. Refer to the source code of the network for more details.
143
+ In general, below are the common args for the network)
144
+ hidden_dim : int. Default= 64
145
+ the dimension of the hidden layers in the graph convolutional layers
146
+ conv_layers : int. Default= 6
147
+ the number of graph convolutional layers
148
+ linear_layers : int. Default= 3
149
+ the number of linear layers after the graph convolutional layers
150
+ dropout : float. Default= 0.1
151
+
152
+ --------------------------------------------
153
+ Example:
154
+
155
+ train(data='data.csv', target=['target1','target2'], network='GCN',)
156
+
157
+ train(LOAD_PATH='GCN_model_test_Abs_20240101_000000')
158
+
159
+ train(TRANSFER_PATH='GCN_model_test_Abs_20240101_000000', data='data.csv', target=['target1','target2'], lr_dict={'GCNs': 0.0001})
160
+
161
+ train(data='data.csv', target=['target1','target2'], network='GCN', hidden_dim=32, conv_layers=4, linear_layers=2, dropout=0.1)
162
+
163
+
164
+ """
165
+ callbacks = kwargs.pop("callbacks", None)
166
+ if kwargs.pop('use_argparser', False):
167
+ parsed = {key: value for key, value in vars(argparser.parse_args()).items() if value is not None}
168
+ kwargs.update(parsed)
169
+ kwargs = check_args(**kwargs)
170
+ resolution = resolve_config(**kwargs)
171
+ config = resolution.to_dict()
172
+ if not config.get('loaded',False) and not config.get('full_resume', False):
173
+ supportfile_saver.save_additional_files(config)
174
+ return run(
175
+ config,
176
+ callbacks=callbacks,
177
+ config_provenance=dict(resolution.provenance),
178
+ )
179
+
180
+ def check_args(**kwargs):
181
+ "check the arguments for the training"
182
+ validate_entry_args(kwargs)
183
+ if kwargs.get('LOAD_PATH',None) is not None or kwargs.get('RESUME_PATH',None) is not None:
184
+ return kwargs
185
+ kwargs['target_dim'] = len(kwargs['target'])
186
+ if kwargs['data'][-4:] == '.csv':
187
+ kwargs['data'] = kwargs['data'][:-4]
188
+ return kwargs
189
+
190
+
191
+ def set_config(**kwargs):
192
+ """Return the historical mutable dictionary config contract."""
193
+
194
+ return resolve_config(**kwargs).to_dict()
195
+
196
+
197
+ def resolve_config(**kwargs):
198
+ """Resolve an immutable config snapshot while preserving legacy precedence."""
199
+
200
+ config, provenance = merge_config_layers((
201
+ ("defaults", config0),
202
+ ("api_or_cli", kwargs),
203
+ ))
204
+ config = {k: v for k, v in config.items() if v is not None}
205
+ provenance = {key: provenance[key] for key in config}
206
+
207
+ netrefer_loaded = False
208
+ if config.get('RESUME_PATH', None) is not None:
209
+ resume_overrides = {key: value for key, value in kwargs.items() if value is not None}
210
+ requested = Path(config['RESUME_PATH'])
211
+ if requested.is_file() or requested.suffix == ".ckpt":
212
+ checkpoint = requested
213
+ model_path = checkpoint.parent.parent if checkpoint.parent.name == "checkpoints" else checkpoint.parent
214
+ if not model_path.is_dir():
215
+ raise FileNotFoundError(
216
+ f"Model folder {str(model_path)!r} for RESUME_PATH {str(checkpoint)!r} was not found."
217
+ )
218
+ config['MODEL_PATH'] = str(model_path)
219
+ config['RESUME_PATH'] = str(checkpoint)
220
+ else:
221
+ config['MODEL_PATH'] = PATH.find_model_path(config['RESUME_PATH'], config)
222
+ config['RESUME_PATH'] = config['MODEL_PATH']
223
+ resolved_model_path = config['MODEL_PATH']
224
+ resolved_resume_path = config['RESUME_PATH']
225
+ with open(os.path.join(resolved_model_path, 'config.yaml'), "r") as file:
226
+ saved_config = yaml.safe_load(file)
227
+ for policy_key, legacy_default in (
228
+ ("random_seed", None),
229
+ ("deterministic_algorithms", False),
230
+ ):
231
+ if (
232
+ policy_key in resume_overrides
233
+ and resume_overrides[policy_key] != saved_config.get(policy_key, legacy_default)
234
+ ):
235
+ raise ValueError(
236
+ f"RESUME_PATH cannot change {policy_key}: "
237
+ f"checkpoint run config={saved_config.get(policy_key, legacy_default)!r}, "
238
+ f"requested={resume_overrides[policy_key]!r}. "
239
+ "Full resume uses the saved RNG policy and checkpoint RNG state."
240
+ )
241
+ config, provenance = merge_config_layers((
242
+ ("saved_resume", saved_config),
243
+ ("resume_override", resume_overrides),
244
+ ))
245
+ config['MODEL_PATH'] = resolved_model_path
246
+ provenance['MODEL_PATH'] = "derived_resume_path"
247
+ config['RESUME_PATH'] = resolved_resume_path
248
+ provenance['RESUME_PATH'] = "derived_resume_path"
249
+ config['full_resume'] = True
250
+ provenance['full_resume'] = "derived_mode"
251
+ netrefer_loaded = True
252
+ # overwrite the config with the loaded config if the LOAD_PATH is given
253
+ if config.get('LOAD_PATH',None) is not None:
254
+ config['LOAD_PATH'] = PATH.find_model_path(config['LOAD_PATH'] , config)
255
+ config['MODEL_PATH'] = config.pop('LOAD_PATH')
256
+ config['loaded'] = True
257
+ with open(config['MODEL_PATH']+'/config.yaml', "r") as file:
258
+ _config = yaml.safe_load(file)# _config = yaml.load(open(os.path.join(config['LOAD_PATH'],'config.yaml'), 'r'), Loader=yaml.FullLoader)
259
+ legacy_scaler_scope = (
260
+ 'target_scaler_fit_scope' not in _config
261
+ and 'target_scaler_fit_scope' not in kwargs
262
+ )
263
+ config, provenance = merge_config_layers((
264
+ ("saved_load", _config),
265
+ ("legacy_load_defaults_or_override", config),
266
+ ))
267
+ if legacy_scaler_scope:
268
+ config['target_scaler_fit_scope'] = 'all'
269
+ provenance['target_scaler_fit_scope'] = "legacy_compatibility"
270
+ warnings.warn(
271
+ "The loaded model config predates target_scaler_fit_scope; continuing with "
272
+ "target_scaler_fit_scope='all' to preserve historical full-data scaling. "
273
+ "Set target_scaler_fit_scope='train' explicitly to migrate to leakage-free scaling.",
274
+ UserWarning,
275
+ stacklevel=2,
276
+ )
277
+ netrefer_loaded = True
278
+
279
+ PATH.init_path(config)
280
+
281
+ if config.get('TRANSFER_PATH',None) is not None:
282
+ config['TRANSFER_PATH'] = PATH.find_model_path(config['TRANSFER_PATH'],config)
283
+
284
+ # if the network is not given, load the network from the transfer path
285
+ if config.get('network', None):
286
+ net_config = load_NET_REFER(config)
287
+ config, provenance = overlay_config_layer(
288
+ config,
289
+ provenance,
290
+ net_config,
291
+ source="registry",
292
+ )
293
+
294
+ # load the config from the transfer path
295
+ with open(config['TRANSFER_PATH']+'/config.yaml', "r") as file:
296
+ _config = yaml.safe_load(file)
297
+ _config.pop('MODEL_PATH',None)
298
+ saved_values, saved_provenance = merge_config_layers((
299
+ ("saved_transfer", _config),
300
+ ))
301
+ config, provenance = overlay_config_layer(
302
+ saved_values,
303
+ saved_provenance,
304
+ config,
305
+ layer_provenance=provenance,
306
+ )
307
+ config['MODEL_PATH'] = PATH.get_model_path(config)
308
+ provenance['MODEL_PATH'] = "derived_transfer_path"
309
+ netrefer_loaded = True
310
+
311
+ if not netrefer_loaded:
312
+ net_config = load_NET_REFER(config)
313
+ config, provenance = overlay_config_layer(
314
+ config,
315
+ provenance,
316
+ net_config,
317
+ source="registry",
318
+ )
319
+ config['MODEL_PATH'] = PATH.get_model_path(config)
320
+ provenance['MODEL_PATH'] = "derived_model_path"
321
+
322
+ if config.get('sculptor_index',None) is not None and type(config['sculptor_index']) is tuple:
323
+ validate_sculptor_index_argument(config['sculptor_index'])
324
+ config['sculptor_s'] = config['sculptor_index'][0]
325
+ config['sculptor_c'] = config['sculptor_index'][1]
326
+ config['sculptor_a'] = config['sculptor_index'][2]
327
+ sculptor_source = provenance.get('sculptor_index', "derived")
328
+ provenance['sculptor_s'] = sculptor_source
329
+ provenance['sculptor_c'] = sculptor_source
330
+ provenance['sculptor_a'] = sculptor_source
331
+ config.pop('sculptor_index')
332
+ provenance.pop('sculptor_index', None)
333
+ import torch
334
+ optimizer_names = {
335
+ name for name in dir(torch.optim)
336
+ if isinstance(getattr(torch.optim, name), type)
337
+ }
338
+ validate_training_config(config, optimizer_names=optimizer_names)
339
+ validate_runtime_environment(config, backend="pyg", torch_module=torch)
340
+ PATH.check_path(config)
341
+ mark_derived(provenance, config)
342
+ return ConfigResolution.from_working(config, provenance)
343
+
344
+ def load_NET_REFER(config):
345
+ from D4CMPP2.networks.registry import get_model, registered_models
346
+
347
+ with open(PATH.get_network_refer(config), "r") as file:
348
+ network_refer = yaml.safe_load(file)
349
+ definition = None
350
+ try:
351
+ definition = get_model(config["network"])
352
+ except ValueError:
353
+ pass
354
+ net_config = network_refer.get(config['network'],None)
355
+ if net_config is None:
356
+ if definition is not None:
357
+ net_config = definition.training_config()
358
+ else:
359
+ validate_network_entry(
360
+ config["network"],
361
+ None,
362
+ [*network_refer, *registered_models()],
363
+ )
364
+ validate_network_entry(config['network'], net_config, [
365
+ *network_refer,
366
+ *registered_models(),
367
+ ])
368
+ net_config = dict(net_config)
369
+ net_config["network_id"] = (
370
+ definition.name if definition is not None else config["network"]
371
+ )
372
+ return net_config
373
+
374
+
375
+ def run(config, callbacks=None, config_provenance=None):
376
+ "the main function to run the training"
377
+ config, runtime = split_runtime_config(config)
378
+ output = get_output(config)
379
+ tf_path = runtime.transfer_path
380
+ resume_path = runtime.resume_path
381
+ is_loaded = runtime.loaded
382
+ is_resumed = runtime.full_resume
383
+ mode = "resume" if is_resumed else "transfer" if tf_path else "legacy_continue" if is_loaded else "scratch"
384
+ previous_backend_determinism = capture_backend_determinism()
385
+ config["effective_reproducibility"] = configure_reproducibility(
386
+ config,
387
+ resume=is_resumed,
388
+ )
389
+ if tf_path:
390
+ output.info(
391
+ f"[Training] Starting transfer learning. Source model: {tf_path!r}; "
392
+ f"output directory: {config['MODEL_PATH']!r}."
393
+ )
394
+ elif is_resumed:
395
+ output.info(
396
+ "[Training] Resuming optimizer and scheduler state. "
397
+ f"Model directory: {config['MODEL_PATH']!r}."
398
+ )
399
+ elif is_loaded:
400
+ output.info(
401
+ "[Training] Continuing from saved model weights. "
402
+ f"Model directory: {config['MODEL_PATH']!r}."
403
+ )
404
+ else:
405
+ output.info(
406
+ "[Training] Starting a new run. "
407
+ f"Output directory: {config['MODEL_PATH']!r}."
408
+ )
409
+
410
+ manifest = RunManifest(config, mode)
411
+ if config_provenance is not None:
412
+ manifest.update(config_provenance=config_provenance)
413
+ config["run_id"] = manifest.run_id
414
+ try:
415
+ dm = module_loader.load_data_manager(config)(config)
416
+ data_contract = validate_data_manager_contract(dm, config)
417
+ if data_contract is not None:
418
+ manifest.update(
419
+ data_manager_contract={
420
+ "manager": data_contract.manager,
421
+ "graph_type": data_contract.graph_type,
422
+ "dataset": data_contract.dataset,
423
+ "unwrapper": data_contract.unwrapper,
424
+ "feature_dimensions": dict(data_contract.feature_dimensions),
425
+ "batch": {
426
+ "name": data_contract.batch.name,
427
+ "required_keys": data_contract.batch.required_keys,
428
+ "optional_keys": data_contract.batch.optional_keys,
429
+ "molecule_suffixes": data_contract.batch.molecule_suffixes,
430
+ "numeric_suffix": data_contract.batch.numeric_suffix,
431
+ },
432
+ }
433
+ )
434
+ network_manager_kwargs = {
435
+ "tf_path": tf_path,
436
+ "unwrapper": dm.unwrapper,
437
+ }
438
+ if resume_path is not None:
439
+ network_manager_kwargs["resume_path"] = resume_path
440
+ nm = module_loader.load_network_manager(config)(
441
+ config,
442
+ **network_manager_kwargs,
443
+ )
444
+ if tf_path and hasattr(nm, "transfer_report"):
445
+ manifest.update(transfer=nm.transfer_report)
446
+ if is_resumed:
447
+ manifest.update(parent_run_id=getattr(nm, "parent_run_id", None))
448
+ tm = module_loader.load_train_manager(config)(config)
449
+ if callbacks is not None:
450
+ set_callbacks = getattr(tm, "set_callbacks", None)
451
+ if not callable(set_callbacks):
452
+ raise TypeError(
453
+ f"Train manager {type(tm).__name__!r} does not support callbacks. "
454
+ "Implement set_callbacks(callbacks), or omit the callbacks argument."
455
+ )
456
+ set_callbacks(callbacks)
457
+ dm.init_data()
458
+ supportfile_saver.save_config(config)
459
+ train_loaders, val_loaders, test_loaders = dm.get_Dataloaders()
460
+ manifest.update(
461
+ split={
462
+ "train": len(dm.train_dataset),
463
+ "val": len(dm.val_dataset),
464
+ "test": len(dm.test_dataset),
465
+ }
466
+ )
467
+ tm.train(nm, train_loaders, val_loaders)
468
+ if tm.train_error is not None:
469
+ raise tm.train_error
470
+ pp = PostProcessor(config)
471
+ pp.postprocess(dm, nm, tm, train_loaders, val_loaders, test_loaders)
472
+ except KeyboardInterrupt as exc:
473
+ manifest.finish("interrupted", error=exc)
474
+ restore_backend_determinism(previous_backend_determinism)
475
+ raise
476
+ except Exception as exc:
477
+ manifest.finish("failed", error=exc)
478
+ restore_backend_determinism(previous_backend_determinism)
479
+ if config.get('legacy_silent_errors', False):
480
+ output.error("[Error] Training failed in legacy silent-error mode.")
481
+ output.error(traceback.format_exc())
482
+ return None
483
+ raise
484
+ manifest.finish(
485
+ "completed",
486
+ completed_epoch=nm.completed_epoch,
487
+ best_epoch=nm.best_epoch,
488
+ best_metric=nm.best_loss,
489
+ final_learning_rate=nm.get_lr(),
490
+ checkpoints={
491
+ "latest": os.path.join(config["MODEL_PATH"], "checkpoints", "latest.ckpt"),
492
+ "best": os.path.join(config["MODEL_PATH"], "checkpoints", "best.ckpt"),
493
+ "final": os.path.join(config["MODEL_PATH"], "final.pth"),
494
+ },
495
+ )
496
+ restore_backend_determinism(previous_backend_determinism)
497
+ return config['MODEL_PATH']
498
+
499
+ if __name__ == "__main__":
500
+ train(use_argparser=True)
D4CMPP2/cli.py ADDED
@@ -0,0 +1,7 @@
1
+ """Command-line entry point using the same training path as the Python API."""
2
+
3
+ from D4CMPP2._main import train
4
+
5
+
6
+ def main():
7
+ train(use_argparser=True)
D4CMPP2/exceptions.py ADDED
@@ -0,0 +1,53 @@
1
+ """Public, compatibility-preserving D4CMPP2 exception categories."""
2
+
3
+
4
+ class D4CMPPError(Exception):
5
+ """Base class for categorized package failures."""
6
+
7
+
8
+ class ConfigError(ValueError, D4CMPPError):
9
+ """Invalid or incompatible configuration."""
10
+
11
+
12
+ class DataError(ValueError, D4CMPPError):
13
+ """Invalid tabular or aligned input data."""
14
+
15
+
16
+ class GraphError(ValueError, D4CMPPError):
17
+ """Invalid graph data, graph schema, or graph generation state."""
18
+
19
+
20
+ class ModelError(RuntimeError, D4CMPPError):
21
+ """Model construction, execution, or state-loading failure."""
22
+
23
+
24
+ class DependencyError(RuntimeError, D4CMPPError):
25
+ """Missing or incompatible runtime dependency."""
26
+
27
+
28
+ class CheckpointError(D4CMPPError):
29
+ """Base category for checkpoint failures."""
30
+
31
+
32
+ class CheckpointNotFoundError(FileNotFoundError, CheckpointError):
33
+ """A requested checkpoint does not exist."""
34
+
35
+
36
+ class CheckpointFormatError(ValueError, CheckpointError):
37
+ """A checkpoint payload or schema is invalid."""
38
+
39
+
40
+ class CheckpointLoadError(RuntimeError, CheckpointError):
41
+ """A checkpoint exists but could not be deserialized."""
42
+
43
+
44
+ class CheckpointIOError(OSError, CheckpointError):
45
+ """A checkpoint could not be committed to storage."""
46
+
47
+
48
+ class ModuleLoadError(ImportError, D4CMPPError):
49
+ """A custom module exists but failed to import or initialize."""
50
+
51
+
52
+ class ManagerNotFoundError(FileNotFoundError, D4CMPPError):
53
+ """A configured manager module or class could not be located."""