maltefranke commited on
Commit
ab9bea9
·
verified ·
1 Parent(s): 887ceeb

Add ChEMBL3D explicit-H epochs 200/300/400 and implicit-H epochs 100/200/300 with inference bundles

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. README.md +97 -0
  2. chembl3d/explicit_h/atom_tokens.txt +13 -0
  3. chembl3d/explicit_h/charge_tokens.txt +3 -0
  4. chembl3d/explicit_h/config.yaml +329 -0
  5. chembl3d/explicit_h/distributions.json +0 -0
  6. chembl3d/explicit_h/distributions.pt +3 -0
  7. chembl3d/explicit_h/edge_tokens.txt +4 -0
  8. chembl3d/explicit_h/epoch_200.ckpt +3 -0
  9. chembl3d/explicit_h/epoch_300.ckpt +3 -0
  10. chembl3d/explicit_h/epoch_400.ckpt +3 -0
  11. chembl3d/explicit_h/metadata.json +71 -0
  12. chembl3d/explicit_h/runtime/README.md +3 -0
  13. chembl3d/explicit_h/runtime/configs/callbacks/callbacks.yaml +20 -0
  14. chembl3d/explicit_h/runtime/configs/data/chembl3d/chembl3d.yaml +36 -0
  15. chembl3d/explicit_h/runtime/configs/data/chembl3d/max100.yaml +8 -0
  16. chembl3d/explicit_h/runtime/configs/data/geom/geom.yaml +113 -0
  17. chembl3d/explicit_h/runtime/configs/data/geom/scaffold_decoration.yaml +24 -0
  18. chembl3d/explicit_h/runtime/configs/data/qm9/qm9.yaml +106 -0
  19. chembl3d/explicit_h/runtime/configs/data/qm9/scaffold_decoration.yaml +22 -0
  20. chembl3d/explicit_h/runtime/configs/default.yaml +25 -0
  21. chembl3d/explicit_h/runtime/configs/defaults/chembl3d/chembl3d.yaml +5 -0
  22. chembl3d/explicit_h/runtime/configs/defaults/chembl3d/train_2x4090.yaml +41 -0
  23. chembl3d/explicit_h/runtime/configs/defaults/core.yaml +13 -0
  24. chembl3d/explicit_h/runtime/configs/defaults/geom/geom.yaml +6 -0
  25. chembl3d/explicit_h/runtime/configs/defaults/geom/scaffold_decoration.yaml +23 -0
  26. chembl3d/explicit_h/runtime/configs/defaults/qm9/qm9.yaml +6 -0
  27. chembl3d/explicit_h/runtime/configs/defaults/qm9/scaffold_decoration.yaml +11 -0
  28. chembl3d/explicit_h/runtime/configs/embeddings/geom.yaml +28 -0
  29. chembl3d/explicit_h/runtime/configs/embeddings/qm9.yaml +29 -0
  30. chembl3d/explicit_h/runtime/configs/heads/head.yaml +75 -0
  31. chembl3d/explicit_h/runtime/configs/insertion_head/gmm.yaml +16 -0
  32. chembl3d/explicit_h/runtime/configs/insertion_head/unimodal.yaml +13 -0
  33. chembl3d/explicit_h/runtime/configs/integrator/integrator.yaml +30 -0
  34. chembl3d/explicit_h/runtime/configs/logging/wandb_logger.yaml +6 -0
  35. chembl3d/explicit_h/runtime/configs/loss_weights/calibrated_geom.yaml +18 -0
  36. chembl3d/explicit_h/runtime/configs/loss_weights/calibrated_geom_frozen.yaml +25 -0
  37. chembl3d/explicit_h/runtime/configs/loss_weights/tuned_weights.yaml +15 -0
  38. chembl3d/explicit_h/runtime/configs/loss_weights/uniform.yaml +15 -0
  39. chembl3d/explicit_h/runtime/configs/model/backbones/dit.yaml +123 -0
  40. chembl3d/explicit_h/runtime/configs/model/finetune/lora_finetune.yaml +13 -0
  41. chembl3d/explicit_h/runtime/configs/model/finetune/scaffold_finetune.yaml +134 -0
  42. chembl3d/explicit_h/runtime/configs/optimizer/muon_aux_adam.yaml +39 -0
  43. chembl3d/explicit_h/runtime/configs/schedule/beta.yaml +3 -0
  44. chembl3d/explicit_h/runtime/configs/schedule/cubic.yaml +1 -0
  45. chembl3d/explicit_h/runtime/configs/schedule/fast_power.yaml +3 -0
  46. chembl3d/explicit_h/runtime/configs/schedule/linear.yaml +1 -0
  47. chembl3d/explicit_h/runtime/configs/schedule/smoothstep.yaml +2 -0
  48. chembl3d/explicit_h/runtime/configs/trainer/trainer.yaml +37 -0
  49. chembl3d/explicit_h/runtime/pyproject.toml +136 -0
  50. chembl3d/explicit_h/runtime/requirements-tested.txt +19 -0
README.md ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: pytorch
3
+ tags:
4
+ - chemistry
5
+ - molecular-generation
6
+ - flow-matching
7
+ - chembl3d
8
+ ---
9
+
10
+ # Morph ChEMBL3D checkpoints
11
+
12
+ Six checkpoints for unconditional 3D molecular generation, with matching
13
+ vocabularies, training distributions, portable inference configurations and
14
+ the frozen Morph runtime from each training run.
15
+
16
+ | Variant | Completed training epochs | Directory |
17
+ | --- | --- | --- |
18
+ | Explicit hydrogens | 200, 300, 400 | [chembl3d/explicit_h](chembl3d/explicit_h) |
19
+ | Implicit hydrogens (heavy-atom graphs) | 100, 200, 300 | [chembl3d/implicit_h](chembl3d/implicit_h) |
20
+
21
+ The `epoch_200.ckpt` name means **200 completed epochs**. The original Lightning
22
+ checkpoint stores `epoch=199`. Files are byte-for-byte copies of the selected
23
+ training checkpoints, including current weights, EMA weights, and optimizer
24
+ state. Sampling uses EMA weights by default.
25
+
26
+ Each variant contains:
27
+
28
+ - `epoch_NNN.ckpt`: the three selected checkpoints.
29
+ - `atom_tokens.txt`, `edge_tokens.txt`, `charge_tokens.txt`: ordered vocabularies.
30
+ - `distributions.pt`: the original training statistics, including atom counts,
31
+ type frequencies, coordinate scale, and geometry histograms.
32
+ - `distributions.json`: a readable export of the same statistics.
33
+ - `config.yaml`: portable sampling config with dataset loading disabled.
34
+ - `training_config.yaml`: the original Hydra training configuration, for provenance.
35
+ - `runtime/`: frozen source/configs and dependency specifications for this run.
36
+ - `metadata.json`: epochs, optimizer steps, original filenames, and SHA-256 hashes.
37
+ - `validation.json`: strict checkpoint loading, current/EMA forward checks and
38
+ a one-molecule end-to-end sampling check. This is a functionality check, not
39
+ a validity benchmark.
40
+
41
+ ## Download and generate
42
+
43
+ Download the variant you need (authenticate with `hf auth login` if required):
44
+
45
+ ```bash
46
+ hf download maltefranke/morph --local-dir morph-models \
47
+ --include 'chembl3d/implicit_h/*' --include sample.py --include README.md --include manifest.json
48
+ cd morph-models
49
+ ```
50
+
51
+ Install that variant's runtime in an environment with Python >=3.11 and a
52
+ compatible PyTorch 2.9 installation. A CUDA-capable PyTorch build is needed to
53
+ use `--device cuda`. The tested package versions are recorded in
54
+ `runtime/requirements-tested.txt`; the runtime's `pyproject.toml` specifies the
55
+ dependency constraints.
56
+
57
+ ```bash
58
+ python -m pip install ./chembl3d/implicit_h/runtime
59
+ python sample.py --variant implicit_h --epoch 300 \
60
+ --num-mols 64 --batch-size 16 --device cuda --output implicit_h.sdf
61
+ ```
62
+
63
+ For explicit hydrogens, download `chembl3d/explicit_h/*` instead, install its
64
+ runtime, and run:
65
+
66
+ ```bash
67
+ python sample.py --variant explicit_h --epoch 400 \
68
+ --num-mols 64 --batch-size 16 --device cuda --output explicit_h.sdf
69
+ ```
70
+
71
+ The script imports the matching bundled source automatically. Each invocation
72
+ uses one variant in a fresh process. No ChEMBL3D dataset or cluster paths are
73
+ needed. `--device cpu` is supported, but use a compute worker for substantial
74
+ sampling. Compilation is disabled by default; attention uses PyTorch SDPA.
75
+
76
+ Generated coordinates are in Angstrom. The SDF contains valid molecules; the
77
+ adjacent JSON records every attempted molecule and its validity result, including
78
+ failures. Generation uses 100 integration steps and seed 42 by default.
79
+
80
+ ## Hydrogen handling and training data
81
+
82
+ Both variants use the same split membership: 1,474,008 training, 184,295 validation,
83
+ and 184,242 test molecules. The dataset was filtered to **at most 100 total atoms,
84
+ including hydrogens**, before removing hydrogens for the implicit variant.
85
+
86
+ The explicit variant predicts H atoms as graph vertices. The implicit variant
87
+ predicts heavy atoms, bonds and formal charges; RDKit infers hydrogens during
88
+ conversion and validity checking. Its bundled runtime preserves that behavior.
89
+ Do not mix vocabularies or distributions between variants. Geometry statistics
90
+ and coordinate normalization were recomputed for the heavy-atom representation.
91
+
92
+ The 100-atom cap describes the training filter, not a hard limit imposed on
93
+ generated molecules. Sample counts follow the saved training prior settings.
94
+
95
+ `manifest.json` lists file sizes and SHA-256 hashes. The original training configs
96
+ contain historical cluster paths and should be treated as provenance; use
97
+ `sample.py` with the portable `config.yaml` for inference.
chembl3d/explicit_h/atom_tokens.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ B
2
+ Br
3
+ C
4
+ Cl
5
+ F
6
+ H
7
+ I
8
+ N
9
+ O
10
+ P
11
+ S
12
+ Se
13
+ Si
chembl3d/explicit_h/charge_tokens.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ -1
2
+ 0
3
+ 1
chembl3d/explicit_h/config.yaml ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ representation: molecule
2
+ seed: 42
3
+ heads:
4
+ heads:
5
+ _target_: morph.model.layers.heads.MultiHeadModule
6
+ output_init_std: ${model.head_initialization.output_std}
7
+ independent_adapters: ${oc.select:model.head_independent_adapters,false}
8
+ heads_configs:
9
+ graph_aggregation: mean
10
+ node_heads:
11
+ atom_type_head:
12
+ input_dim: ${model.head_node_dim}
13
+ output_dim: ${len:${data.vocab.atom_tokens}}
14
+ adapter_dim: ${model.head_node_adapter_dim}
15
+ group: state
16
+ charge_head:
17
+ input_dim: ${model.head_node_dim}
18
+ output_dim: ${len:${data.vocab.charge_tokens}}
19
+ adapter_dim: ${model.head_node_adapter_dim}
20
+ group: state
21
+ ins_rate_head:
22
+ input_dim: ${model.head_node_dim}
23
+ output_dim: 1
24
+ adapter_dim: ${model.head_node_adapter_dim}
25
+ group: edit
26
+ initial_rate: ${model.head_initialization.ins_rate}
27
+ do_del_head:
28
+ input_dim: ${model.head_node_dim}
29
+ output_dim: 1
30
+ adapter_dim: ${model.head_node_adapter_dim}
31
+ group: edit
32
+ initial_probability: ${model.head_initialization.do_del_probability}
33
+ do_sub_a_head:
34
+ input_dim: ${model.head_node_dim}
35
+ output_dim: 1
36
+ adapter_dim: ${model.head_node_adapter_dim}
37
+ group: edit
38
+ initial_probability: ${model.head_initialization.do_sub_a_probability}
39
+ edge_heads:
40
+ edge_type_head:
41
+ input_dim: ${model.head_edge_dim}
42
+ output_dim: ${len:${data.vocab.edge_tokens}}
43
+ adapter_dim: ${model.head_edge_adapter_dim}
44
+ group: state
45
+ do_sub_e_head:
46
+ input_dim: ${model.head_edge_dim}
47
+ output_dim: 1
48
+ adapter_dim: ${model.head_edge_adapter_dim}
49
+ group: state
50
+ initial_probability: ${model.head_initialization.do_sub_e_probability}
51
+ ins_edge_head:
52
+ _target_: morph.model.layers.heads.InsertionEdgeHead
53
+ _recursive_: false
54
+ hidden_dim: ${model.head_node_dim}
55
+ readout_dim: ${model.head_insertion_edge_dim}
56
+ output_init_std: ${model.head_initialization.output_std}
57
+ n_edge_types: ${len:${data.vocab.edge_tokens}}
58
+ n_atom_types: ${len:${data.vocab.atom_tokens}}
59
+ n_charge_types: ${len:${data.vocab.charge_tokens}}
60
+ rbf_embedding_args:
61
+ _target_: morph.model.layers.embedding.RBFEmbedding
62
+ num_rbf: 128
63
+ rbf_dmax: 3.5
64
+ out_dim: 64
65
+ dropout: 0.0
66
+ trainable: false
67
+ insertion_head:
68
+ mode: unimodal
69
+ dimension: 3
70
+ num_components: 1
71
+ model:
72
+ _target_: morph.model.layers.heads.UnimodalInsertionHead
73
+ hidden_dim: ${model.head_node_dim}
74
+ readout_dim: ${model.head_insertion_adapter_dim}
75
+ output_init_std: ${model.head_initialization.output_std}
76
+ independent_adapters: ${oc.select:model.head_independent_adapters,false}
77
+ N_a: ${len:${data.vocab.atom_tokens}}
78
+ N_c: ${len:${data.vocab.charge_tokens}}
79
+ model:
80
+ module:
81
+ optimizer_config:
82
+ implementation: torch
83
+ optimizer:
84
+ execution: ${oc.env:MORPH_MUON_EXECUTION,native}
85
+ lr: 0.01
86
+ momentum: 0.95
87
+ weight_decay: 0.0
88
+ nesterov: true
89
+ ns_steps: 5
90
+ eps: 1.0e-07
91
+ adjust_lr_fn: original
92
+ aux_optimizer:
93
+ lr: 0.0003
94
+ betas:
95
+ - 0.9
96
+ - 0.95
97
+ eps: 1.0e-10
98
+ weight_decay: 0.0
99
+ gradient_clip_val: 1.0
100
+ gradient_clip_algorithm: norm
101
+ scheduler:
102
+ _target_: morph.utils.lr_schedulers.MultiGroupCosineWarmupLR
103
+ warmup_steps: 2000
104
+ total_steps: 200000
105
+ min_lr_fraction: 0.05
106
+ last_epoch: -1
107
+ interval: step
108
+ monitor: null
109
+ loss_weights:
110
+ do_sub_a: 0.2
111
+ sub_a_class: 0.03
112
+ do_sub_e: 0.25
113
+ sub_e_class: 0.5
114
+ do_del: 0.4
115
+ ins_rate: 0.15
116
+ ins_state: 0.01
117
+ ins_e: 0.4
118
+ x: 2.0
119
+ c: 0.5
120
+ ins_e_ii: 0.03
121
+ _target_: morph.model.lightning.base.LightningModuleRates
122
+ insertion_head_mode: ${insertion_head.mode}
123
+ ins_noise_scale: ${data.ins_noise_scale}
124
+ ema_decay: 0.999
125
+ ema_warmup_steps: 5000
126
+ use_ema_for_eval: true
127
+ time_dist: ${if:${eq:${model.sample_time_strategy}, log}, ${model.beta_sched},
128
+ ${model.linear_sched}}
129
+ integrator: ${model.integrator}
130
+ vocab: ${data.vocab}
131
+ use_learnable_loss_weights: false
132
+ use_time_weights: false
133
+ model:
134
+ _target_: morph.model.backbones.dit.DiTBackboneWithHeads
135
+ _recursive_: false
136
+ atom_type_embedding_args: ${embeddings.atom_type_embedding}
137
+ edge_type_embedding_args: ${embeddings.edge_type_embedding}
138
+ time_embedding_args: ${embeddings.time_embedding}
139
+ node_count_embedding_args: ${embeddings.node_count_embedding}
140
+ incident_bond_embedding: false
141
+ backbone_model_args: ${model.backbone}
142
+ heads_args: ${heads.heads}
143
+ insertion_head_args: ${insertion_head.model}
144
+ ins_edge_head_args: ${heads.ins_edge_head}
145
+ rotate: true
146
+ n_augmentations: 1
147
+ integrator: ${integrator.integrator}
148
+ beta_sched:
149
+ _target_: torch.distributions.Beta
150
+ concentration1: 2.0
151
+ concentration0: 1.0
152
+ linear_sched:
153
+ _target_: torch.distributions.Uniform
154
+ low: 0.0
155
+ high: 1.0
156
+ sample_time_strategy: linear
157
+ head_node_dim: ${model.backbone.out_node_nf}
158
+ head_edge_dim: ${model.backbone.out_edge_nf}
159
+ head_node_adapter_dim: 256
160
+ head_edge_adapter_dim: 256
161
+ head_insertion_adapter_dim: 256
162
+ head_insertion_edge_dim: 256
163
+ head_independent_adapters: true
164
+ head_initialization:
165
+ output_std: 0.001
166
+ coordinate_output_std: 0.0
167
+ do_del_probability: 0.5
168
+ do_sub_a_probability: 0.5
169
+ do_sub_e_probability: 0.5
170
+ ins_rate: 0.6931471805599453
171
+ token_weighting:
172
+ type_loss_token_weights: uniform
173
+ weight_alpha: 0.4
174
+ lora:
175
+ enabled: false
176
+ rank: 8
177
+ alpha: 8.0
178
+ dropout: 0.0
179
+ targets:
180
+ - qkv
181
+ - proj
182
+ - mlp.gate_value
183
+ - mlp.out_proj
184
+ freeze_base: true
185
+ backbone:
186
+ _target_: morph.model.backbones.dit.DiTBackbone
187
+ _recursive_: false
188
+ d_input: ${embeddings.atom_type_embedding.out_dim}
189
+ d_cond: ${oc.eval:"${embeddings.time_embedding.out_dim} + ${embeddings.node_count_embedding.out_dim}"}
190
+ d_model: 512
191
+ out_node_nf: 256
192
+ out_edge_nf: 256
193
+ num_layers: 8
194
+ nhead: 16
195
+ mlp_ratio: 4.0
196
+ ffn_hidden_dim: null
197
+ ffn_multiple_of: 64
198
+ qk_norm: true
199
+ coordinate_output_init_std: ${model.head_initialization.coordinate_output_std}
200
+ center_coordinate_output: true
201
+ in_edge_nf: ${embeddings.edge_type_embedding.out_dim}
202
+ edge_pair_dim: 256
203
+ pair_bias_hidden_dim: 32
204
+ num_atom_types: ${len:${data.vocab.atom_tokens}}
205
+ lora: ${model.lora}
206
+ rbf_embedding_args:
207
+ _target_: morph.model.layers.embedding.RBFEmbedding
208
+ num_rbf: 128
209
+ rbf_dmax: 3.5
210
+ out_dim: 64
211
+ dropout: 0.0
212
+ trainable: false
213
+ schedule:
214
+ _target_: morph.flow_matching.schedules.SmoothstepSchedule
215
+ shift: 0.8
216
+ trainer:
217
+ trainer:
218
+ deterministic: warn
219
+ devices: 1
220
+ accelerator: auto
221
+ strategy: auto
222
+ precision: 32-true
223
+ max_epochs: -1
224
+ accumulate_grad_batches: 1
225
+ num_sanity_val_steps: 0
226
+ gradient_clip_val: null
227
+ gradient_clip_algorithm: norm
228
+ check_val_every_n_epoch: 5
229
+ num_val_samples: 4096
230
+ limit_val_batches: null
231
+ limit_predict_batches: 4
232
+ max_time: null
233
+ do_train: false
234
+ do_validate: false
235
+ do_predict: true
236
+ checkpoint_path: null
237
+ final_checkpoint_path: final.ckpt
238
+ callbacks: {}
239
+ logging:
240
+ enabled: false
241
+ project: morph-${data.dataset_name}
242
+ name: chembl3d-explicit_h
243
+ save_dir: .
244
+ offline: false
245
+ integrator:
246
+ integrator:
247
+ _target_: morph.flow_matching.integration.RateIntegrator
248
+ vocab: ${data.vocab}
249
+ distributions: None
250
+ insertion_head_mode: ${insertion_head.mode}
251
+ insertion_dimension: ${insertion_head.dimension}
252
+ insertion_num_components: ${insertion_head.num_components}
253
+ time_strategy: log
254
+ time_beta_alpha: 2.0
255
+ time_beta_beta: 1.0
256
+ num_integration_steps: 100
257
+ ins_noise_scale: ${data.ins_noise_scale}
258
+ renoise_insertions: true
259
+ exact_schedule_integration: true
260
+ del_schedule: ${data.interpolator.del_schedule}
261
+ ins_schedule: ${data.interpolator.ins_schedule}
262
+ sub_schedule: ${data.interpolator.sub_schedule}
263
+ sub_e_schedule: ${data.interpolator.sub_e_schedule}
264
+ data:
265
+ dataset_name: chembl3d
266
+ root_path: .
267
+ preprocess_workers: 0
268
+ n_atoms_strategy: uniform
269
+ uniform_n_atoms_padding: 5
270
+ optimal_transport: equivariant
271
+ ins_noise_scale: 0.65
272
+ move_noise_scale: 0.5
273
+ allow_charged: true
274
+ preprocessing:
275
+ _target_: morph.dataset.core.preprocessing.Preprocessing
276
+ _recursive_: false
277
+ root: .
278
+ train_dataset: null
279
+ atom_tokens_path: ${data.preprocessing.root}/atom_tokens.txt
280
+ edge_tokens_path: ${data.preprocessing.root}/edge_tokens.txt
281
+ charge_tokens_path: ${data.preprocessing.root}/charge_tokens.txt
282
+ distributions_path: ${data.preprocessing.root}/distributions.pt
283
+ num_workers: 0
284
+ vocab: null
285
+ interpolator:
286
+ _target_: morph.flow_matching.interpolation.Interpolator
287
+ distributions: None
288
+ optimal_transport: ${data.optimal_transport}
289
+ ins_noise_scale: ${data.ins_noise_scale}
290
+ move_noise_scale: ${data.move_noise_scale}
291
+ atom_type_noise_prob: 0.0
292
+ pre_align_iters: 2
293
+ c_move: 1.0
294
+ c_sub: 0.0
295
+ c_ins: 6.125
296
+ c_del: 6.125
297
+ del_schedule: ${schedule}
298
+ ins_schedule: ${schedule}
299
+ sub_schedule:
300
+ _target_: morph.flow_matching.schedules.SmoothstepSchedule
301
+ shift: 1.5
302
+ sub_e_schedule:
303
+ _target_: morph.flow_matching.schedules.SmoothstepSchedule
304
+ shift: 1.5
305
+ datamodule: null
306
+ source_dir: null
307
+ max_total_atoms: 100
308
+ embeddings:
309
+ atom_type_embedding:
310
+ _target_: morph.model.layers.embedding.Embedding
311
+ num_embeddings: ${len:${data.vocab.atom_tokens}}
312
+ embedding_dim: 64
313
+ out_dim: 128
314
+ edge_type_embedding:
315
+ _target_: morph.model.layers.embedding.Embedding
316
+ num_embeddings: ${len:${data.vocab.edge_tokens}}
317
+ embedding_dim: 64
318
+ out_dim: 128
319
+ time_embedding:
320
+ _target_: morph.model.layers.embedding.TimeEmbedding
321
+ embedding_dim: 128
322
+ out_dim: 128
323
+ node_count_embedding:
324
+ _target_: morph.model.layers.embedding.ExtrapolatableCountEmbedding
325
+ embedding_dim: 64
326
+ out_dim: 128
327
+ min_period: 128.0
328
+ max_period: 1024.0
329
+ max_count: 96.0
chembl3d/explicit_h/distributions.json ADDED
The diff for this file is too large to render. See raw diff
 
chembl3d/explicit_h/distributions.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c92bdce3e70763f23b3132a910a9939791fd147f98e11f7d6ea9c0e6ef9622b4
3
+ size 105093
chembl3d/explicit_h/edge_tokens.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ <NO_BOND>
2
+ 1
3
+ 2
4
+ 3
chembl3d/explicit_h/epoch_200.ckpt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:087f67faf38847d34780d061b0d192e61f8b4365d50cab6c32e0cc7dc5368743
3
+ size 509357065
chembl3d/explicit_h/epoch_300.ckpt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ad7b4535cf8d40ead915c59e525fbe3ec555e6f03885b78ed54c45497793f32b
3
+ size 509357129
chembl3d/explicit_h/epoch_400.ckpt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:efb5c2f85972df19eca85d2d69cb56f6f52453d91ee26293b3e68b299e2bac8f
3
+ size 509357129
chembl3d/explicit_h/metadata.json ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "dataset": "ChEMBL3D",
3
+ "variant": "explicit_h",
4
+ "includes_explicit_hydrogens": true,
5
+ "source_max_total_atoms_including_h": 100,
6
+ "training_run": "chembl3d-max100-2x4090-15313268",
7
+ "source_snapshot": "chembl3d_recovery_20260927_v2",
8
+ "checkpoint_format": "Original Lightning checkpoint, current + EMA + optimizer state",
9
+ "epoch_numbering": "Completed epochs (one-based); original checkpoint epoch is zero-based",
10
+ "split_molecules": {
11
+ "train": 1474008,
12
+ "val": 184295,
13
+ "test": 184242
14
+ },
15
+ "checkpoints": [
16
+ {
17
+ "filename": "epoch_200.ckpt",
18
+ "original_filename": "epoch=199-step=460600.ckpt",
19
+ "completed_epochs": 200,
20
+ "checkpoint_epoch_index": 199,
21
+ "bytes": 509357065,
22
+ "sha256": "087f67faf38847d34780d061b0d192e61f8b4365d50cab6c32e0cc7dc5368743",
23
+ "global_step": 460600
24
+ },
25
+ {
26
+ "filename": "epoch_300.ckpt",
27
+ "original_filename": "epoch=299-step=690900.ckpt",
28
+ "completed_epochs": 300,
29
+ "checkpoint_epoch_index": 299,
30
+ "bytes": 509357129,
31
+ "sha256": "ad7b4535cf8d40ead915c59e525fbe3ec555e6f03885b78ed54c45497793f32b",
32
+ "global_step": 690900
33
+ },
34
+ {
35
+ "filename": "epoch_400.ckpt",
36
+ "original_filename": "epoch=399-step=921200.ckpt",
37
+ "completed_epochs": 400,
38
+ "checkpoint_epoch_index": 399,
39
+ "bytes": 509357129,
40
+ "sha256": "efb5c2f85972df19eca85d2d69cb56f6f52453d91ee26293b3e68b299e2bac8f",
41
+ "global_step": 921200
42
+ }
43
+ ],
44
+ "coordinate_std_angstrom": 2.707319498062134,
45
+ "atom_tokens": [
46
+ "B",
47
+ "Br",
48
+ "C",
49
+ "Cl",
50
+ "F",
51
+ "H",
52
+ "I",
53
+ "N",
54
+ "O",
55
+ "P",
56
+ "S",
57
+ "Se",
58
+ "Si"
59
+ ],
60
+ "edge_tokens": [
61
+ "<NO_BOND>",
62
+ "1",
63
+ "2",
64
+ "3"
65
+ ],
66
+ "charge_tokens": [
67
+ "-1",
68
+ "0",
69
+ "1"
70
+ ]
71
+ }
chembl3d/explicit_h/runtime/README.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Morph runtime for ChEMBL3D explicit_h
2
+
3
+ Frozen training source. See the model repository README and sample.py.
chembl3d/explicit_h/runtime/configs/callbacks/callbacks.yaml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package _global_
2
+
3
+ ## Put your callback args here
4
+ callbacks:
5
+ monitor_metric: ${if:${oc.eval:"'${representation}' in ('geometric_graph', 'molecule')"},val/validity,val/batch/pair_dist_l1}
6
+ monitor_metric_mode: ${if:${oc.eval:"'${representation}' in ('geometric_graph', 'molecule')"},max,min}
7
+
8
+ # model_checkpoints -> best-by-metric (top-1, monitored)
9
+ # every_n_epochs_checkpoint -> latest (no monitor; overwrites every epoch)
10
+ model_checkpoints:
11
+ save_top_k: 1
12
+ save_last: False
13
+ verbose: False
14
+
15
+ every_n_epochs_checkpoint:
16
+ every_n_epochs: 1
17
+ save_top_k: 1
18
+ save_last: False
19
+ verbose: False
20
+ save_on_train_epoch_end: True # Force per-train-epoch firing
chembl3d/explicit_h/runtime/configs/data/chembl3d/chembl3d.yaml ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LoQI Release 2 -> Morph LMDB, using the shared drug-like training setup.
2
+ defaults:
3
+ - /data/geom/geom@_here_
4
+ - _self_
5
+
6
+ dataset_name: chembl3d
7
+ # Optional directory with the downloaded train_h.pt / val_h.pt / test_h.pt.
8
+ # When unset, missing files are downloaded to <root>/raw/.
9
+ source_dir: ${oc.env:CHEMBL3D_SOURCE_DIR,null}
10
+ vocab: null
11
+
12
+ preprocessing:
13
+ num_workers: ${data.preprocess_workers}
14
+ root: ${data.root_path}/chembl3d
15
+
16
+ datamodule:
17
+ distributions: null
18
+ datasets:
19
+ train:
20
+ _target_: morph.dataset.unconditional.chembl3d.FlowMatchingChEMBL3DDataset
21
+ source_dir: ${data.source_dir}
22
+ distributions: null
23
+ val:
24
+ - _target_: morph.dataset.unconditional.chembl3d.FlowMatchingChEMBL3DDataset
25
+ root: ${data.preprocessing.root}
26
+ source_dir: ${data.source_dir}
27
+ preprocess_workers: ${data.preprocess_workers}
28
+ vocab: ${data.vocab}
29
+ distributions: null
30
+ test:
31
+ - _target_: morph.dataset.unconditional.chembl3d.FlowMatchingChEMBL3DDataset
32
+ root: ${data.preprocessing.root}
33
+ source_dir: ${data.source_dir}
34
+ preprocess_workers: ${data.preprocess_workers}
35
+ vocab: ${data.vocab}
36
+ distributions: null
chembl3d/explicit_h/runtime/configs/data/chembl3d/max100.yaml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # Dataset cache built by morph.dataset.unconditional.chembl3d_filter.
2
+ defaults:
3
+ - /data/chembl3d/chembl3d@_here_
4
+ - _self_
5
+
6
+ max_total_atoms: 100 # Inclusive, with explicit hydrogens retained.
7
+ preprocessing:
8
+ root: ${data.root_path}/chembl3d_max100
chembl3d/explicit_h/runtime/configs/data/geom/geom.yaml ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## GEOM-Drugs data and preprocessing configuration.
2
+
3
+ dataset_name: geom
4
+ root_path: ${oc.env:PROJECT_ROOT}/data
5
+ preprocess_workers: 10
6
+
7
+ n_atoms_strategy: "uniform" # "approx", "empirical", "learned"
8
+ # Uniform source-count support padding; explicit so inference and training can
9
+ # be matched or ablated without code changes.
10
+ uniform_n_atoms_padding: 5
11
+ optimal_transport: "equivariant"
12
+ # Physical Angstroms (converted to normalized coordinates internally). These
13
+ # reproduce the former normalized 0.25 / 0.2 scales at GEOM's training-set std.
14
+ # Physical Angstroms. Insertion bridge noise vanishes as (1-t); move noise is
15
+ # the persistent training-time jitter floor for matched atoms and insertions
16
+ # that are already present.
17
+ ins_noise_scale: 0.65
18
+ move_noise_scale: 0.5
19
+
20
+ # GEOM-Drugs contains charged species; allow them when scoring validity.
21
+ allow_charged: true
22
+
23
+
24
+ # Preprocessing configuration - computes tokens from data and distributions from training dataset only
25
+ preprocessing:
26
+ _target_: morph.dataset.core.preprocessing.Preprocessing
27
+ _recursive_: false
28
+ root: ${data.root_path}/geom
29
+ train_dataset: ${data.datamodule.datasets.train}
30
+ atom_tokens_path: ${data.preprocessing.root}/processed/atom_tokens.txt
31
+ edge_tokens_path: ${data.preprocessing.root}/processed/edge_tokens.txt
32
+ charge_tokens_path: ${data.preprocessing.root}/processed/charge_tokens.txt
33
+ distributions_path: ${data.preprocessing.root}/processed/distributions.pt
34
+ # Tokens and distributions will be discovered from data and saved to files
35
+ # On subsequent runs, they will be loaded from the files
36
+
37
+ vocab: None
38
+
39
+ interpolator:
40
+ _target_: morph.flow_matching.interpolation.Interpolator
41
+ distributions: None
42
+ optimal_transport: ${data.optimal_transport}
43
+ ins_noise_scale: ${data.ins_noise_scale}
44
+ move_noise_scale: ${data.move_noise_scale}
45
+ # Training-only categorical corruption; pairs are scaled per molecule to
46
+ # match the expected number of changed atoms. Clean targets stay unchanged.
47
+ atom_type_noise_prob: 0.0
48
+ # OT matching (cost always squared-Euclidean; move cost in Angstrom). c_move
49
+ # scales the squared move distance (A^2); c_ins/c_del/c_sub are fixed A^2 costs,
50
+ # directly comparable. At 6.125 each, delete+insert costs 3.5^2, so atoms
51
+ # farther than 3.5 A apart are left unmatched by default.
52
+ pre_align_iters: 2 # equivariant-OT inner loop (Hungarian+Kabsch) iters
53
+ c_move: 1.0
54
+ c_sub: 0.0
55
+ c_ins: 6.125
56
+ c_del: 6.125
57
+ del_schedule: ${schedule}
58
+ ins_schedule: ${schedule}
59
+ sub_schedule:
60
+ _target_: morph.flow_matching.schedules.SmoothstepSchedule
61
+ shift: 1.5
62
+ sub_e_schedule:
63
+ _target_: morph.flow_matching.schedules.SmoothstepSchedule
64
+ shift: 1.5
65
+
66
+ datamodule:
67
+ _target_: morph.dataset.core.lightning_datamodule.LightningDataModule
68
+ # tokens and distributions will be passed from preprocessing in run.py
69
+
70
+ vocab: ${data.vocab}
71
+ distributions: None
72
+ representation: ${representation}
73
+
74
+ datasets:
75
+ train:
76
+ _target_: morph.dataset.unconditional.geom.FlowMatchingGEOMDataset
77
+ root: ${data.preprocessing.root}
78
+ preprocess_workers: ${data.preprocess_workers}
79
+ vocab: ${data.vocab}
80
+ distributions: None
81
+
82
+ val:
83
+ - _target_: morph.dataset.unconditional.geom.FlowMatchingGEOMDataset
84
+ root: ${data.preprocessing.root}
85
+ preprocess_workers: ${data.preprocess_workers}
86
+ vocab: ${data.vocab}
87
+ distributions: None
88
+
89
+ test:
90
+ - _target_: morph.dataset.unconditional.geom.FlowMatchingGEOMDataset
91
+ root: ${data.preprocessing.root}
92
+ preprocess_workers: ${data.preprocess_workers}
93
+ vocab: ${data.vocab}
94
+ distributions: None
95
+
96
+ interpolator: ${data.interpolator}
97
+
98
+ rotate: ${model.rotate}
99
+ n_augmentations: ${model.n_augmentations}
100
+
101
+ num_workers:
102
+ train: 10
103
+ val: 0
104
+ test: 0
105
+
106
+ batch_size:
107
+ train: 320
108
+ val: 128
109
+ test: 64
110
+
111
+ n_atoms_strategy: ${data.n_atoms_strategy}
112
+ uniform_n_atoms_padding: ${data.uniform_n_atoms_padding}
113
+ time_dist: ${model.module.time_dist}
chembl3d/explicit_h/runtime/configs/data/geom/scaffold_decoration.yaml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # GEOM-Drugs data config for the **scaffold decoration** task.
2
+ #
3
+ # Mirrors configs/data/qm9/scaffold_decoration.yaml but on GEOM-Drugs: source
4
+ # is the selected scaffold of each target; target is the full molecule. The model
5
+ # learns to grow it by inserting decoration atoms around the fixed scaffold.
6
+ #
7
+ # Inherits from configs/data/geom/geom.yaml and overrides:
8
+ # - datamodule._target_ -> ScaffoldLightningDataModule
9
+ #
10
+ # The scaffold cache is built lazily into ``scaffold_groups_dir`` on first run
11
+ # (one file per split and scaffold kind) and reused afterwards.
12
+ defaults:
13
+ - /data/geom/geom@_here_
14
+ - _self_
15
+
16
+ scaffold_groups_dir: ${data.preprocessing.root}/processed/scaffold_groups
17
+ scaffold_kind: rgroup # murcko, rgroup (BRICS), heavy (generate H), linker
18
+ # Evaluate each distinct molecule/scaffold pair; training always draws randomly.
19
+ enumerate_slicings: false
20
+ datamodule:
21
+ _target_: morph.dataset.conditional.scaffold.datamodule.ScaffoldLightningDataModule
22
+ scaffold_groups_dir: ${data.scaffold_groups_dir}
23
+ scaffold_kind: ${data.scaffold_kind}
24
+ enumerate_slicings: ${data.enumerate_slicings}
chembl3d/explicit_h/runtime/configs/data/qm9/qm9.yaml ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## QM9 data and preprocessing configuration.
2
+
3
+ dataset_name: qm9
4
+ root_path: ${oc.env:PROJECT_ROOT}/data
5
+ preprocess_workers: 0
6
+
7
+ n_atoms_strategy: "uniform" # "approx", "empirical", "learned"
8
+ # Expand both ends of the empirical count support for the uniform flow source.
9
+ # QM9's minimum is already the hard lower bound of 3, so positive padding is
10
+ # necessarily asymmetric; set to 0 for an unpadded 3--29 diagnostic prior.
11
+ uniform_n_atoms_padding: 5
12
+ optimal_transport: "equivariant"
13
+ # Physical Angstroms (converted to normalized coordinates internally). Insertion
14
+ # bridge noise vanishes as (1-t); move noise is the persistent training-time
15
+ # jitter floor for matched atoms and insertions that are already present.
16
+ ins_noise_scale: 0.35
17
+ move_noise_scale: 0.3
18
+
19
+ # QM9 contains only neutral molecules; charged species are treated as invalid.
20
+ allow_charged: false
21
+
22
+
23
+ # Preprocessing configuration - computes tokens from data and distributions from training dataset only
24
+ preprocessing:
25
+ _target_: morph.dataset.core.preprocessing.Preprocessing
26
+ _recursive_: false
27
+ root: ${data.root_path}/qm9
28
+ train_dataset: ${data.datamodule.datasets.train}
29
+ # Canonical (representation-agnostic) cache. One preprocessing per dataset;
30
+ # representation-specific projection happens at runtime in the wrapper.
31
+ atom_tokens_path: ${data.preprocessing.root}/processed/atom_tokens.txt
32
+ edge_tokens_path: ${data.preprocessing.root}/processed/edge_tokens.txt
33
+ charge_tokens_path: ${data.preprocessing.root}/processed/charge_tokens.txt
34
+ distributions_path: ${data.preprocessing.root}/processed/distributions.pt
35
+
36
+ vocab: None
37
+
38
+ interpolator:
39
+ _target_: morph.flow_matching.interpolation.Interpolator
40
+ distributions: None
41
+ optimal_transport: ${data.optimal_transport}
42
+ ins_noise_scale: ${data.ins_noise_scale}
43
+ move_noise_scale: ${data.move_noise_scale}
44
+ # OT matching (cost always squared-Euclidean; move cost in Angstrom). c_move
45
+ # scales the squared move distance (A^2); c_ins/c_del/c_sub are fixed A^2 costs,
46
+ # directly comparable. At 6.125 each, delete+insert costs 3.5^2, so atoms
47
+ # farther than 3.5 A apart are left unmatched by default.
48
+ pre_align_iters: 2 # equivariant-OT inner loop (Hungarian+Kabsch) iters
49
+ c_move: 1.0
50
+ c_sub: 0.0
51
+ c_ins: 6.125
52
+ c_del: 6.125
53
+ del_schedule: ${schedule}
54
+ ins_schedule: ${schedule}
55
+ sub_schedule:
56
+ _target_: morph.flow_matching.schedules.SmoothstepSchedule
57
+ shift: 1.5
58
+ sub_e_schedule:
59
+ _target_: morph.flow_matching.schedules.SmoothstepSchedule
60
+ shift: 1.5
61
+
62
+ datamodule:
63
+ _target_: morph.dataset.core.lightning_datamodule.LightningDataModule
64
+ # tokens and distributions will be passed from preprocessing in run.py
65
+
66
+ vocab: ${data.vocab}
67
+ distributions: None
68
+ representation: ${representation}
69
+
70
+ datasets:
71
+ train:
72
+ _target_: morph.dataset.unconditional.qm9.FlowMatchingQM9Dataset
73
+ root: ${data.root_path}/qm9
74
+ vocab: ${data.vocab}
75
+ distributions: None
76
+
77
+ val:
78
+ - _target_: morph.dataset.unconditional.qm9.FlowMatchingQM9Dataset
79
+ root: ${data.root_path}/qm9
80
+ vocab: ${data.vocab}
81
+ distributions: None
82
+
83
+ test:
84
+ - _target_: morph.dataset.unconditional.qm9.FlowMatchingQM9Dataset
85
+ root: ${data.root_path}/qm9
86
+ vocab: ${data.vocab}
87
+ distributions: None
88
+
89
+ interpolator: ${data.interpolator}
90
+
91
+ rotate: ${model.rotate}
92
+ n_augmentations: ${model.n_augmentations}
93
+
94
+ num_workers:
95
+ train: 10
96
+ val: 0
97
+ test: 0
98
+
99
+ batch_size:
100
+ train: 1024
101
+ val: 128
102
+ test: 32
103
+
104
+ n_atoms_strategy: ${data.n_atoms_strategy}
105
+ uniform_n_atoms_padding: ${data.uniform_n_atoms_padding}
106
+ time_dist: ${model.module.time_dist}
chembl3d/explicit_h/runtime/configs/data/qm9/scaffold_decoration.yaml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # QM9 data config for the **scaffold decoration** task.
2
+ #
3
+ # Source is the selected scaffold of each target; target is the full molecule.
4
+ # The model learns to grow the molecule by inserting decoration atoms
5
+ # around the fixed scaffold.
6
+ #
7
+ # Inherits from configs/data/qm9/qm9.yaml and overrides:
8
+ # - datamodule._target_ → ScaffoldLightningDataModule
9
+ #
10
+ defaults:
11
+ - /data/qm9/qm9@_here_
12
+ - _self_
13
+
14
+ scaffold_groups_dir: ${data.root_path}/qm9/processed/scaffold_groups
15
+ scaffold_kind: rgroup # murcko, rgroup (BRICS), heavy (generate H), linker
16
+ # Evaluate each distinct molecule/scaffold pair; training always draws randomly.
17
+ enumerate_slicings: false
18
+ datamodule:
19
+ _target_: morph.dataset.conditional.scaffold.datamodule.ScaffoldLightningDataModule
20
+ scaffold_groups_dir: ${data.scaffold_groups_dir}
21
+ scaffold_kind: ${data.scaffold_kind}
22
+ enumerate_slicings: ${data.enumerate_slicings}
chembl3d/explicit_h/runtime/configs/default.yaml ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defaults:
2
+ - _self_
3
+ # Single-knob presets bundle
4
+ # `data` + `embeddings` (with dataset-tuned ExtrapolatableCount params)
5
+ # plus the shared `core` group selections. Override individual groups as
6
+ # usual, e.g. `defaults=geom/geom model=backbones/dit embeddings=qm9`.
7
+ - defaults: qm9/qm9
8
+
9
+ # What the model trains on. Datasets declare their CAPABILITIES; run.py validates
10
+ # the requested mode against them before preprocessing.
11
+ # pointcloud — x, a only (charges and edges are dummy singletons)
12
+ # charged_pointcloud — x, a, c (edges are dummy singletons)
13
+ # geometric_graph — x, a, edge_index, e (charges are dummy neutral)
14
+ # molecule — full chemistry (current behavior)
15
+ representation: molecule
16
+ seed: 42
17
+
18
+ # Run directory: <root>/<dataset>/<cfg_method>/<date>/<wandb_id>. Root is
19
+ # MORPH_RUN_ROOT (scratch, set in .env) so every run's outputs/checkpoints/wandb
20
+ # land off-repo; falls back to <cwd>/outputs for ad-hoc runs with no env set.
21
+ # wandb id is MORPH_RUN_ID (same as WANDB_RUN_ID). Set in the shell before
22
+ # torchrun, or set automatically for single-process runs in run.py before Hydra composes.
23
+ hydra:
24
+ run:
25
+ dir: ${oc.env:MORPH_RUN_ROOT,${hydra:runtime.cwd}/outputs}/${hydra:runtime.choices.data}/${now:%Y-%m-%d}/${oc.env:MORPH_RUN_ID}
chembl3d/explicit_h/runtime/configs/defaults/chembl3d/chembl3d.yaml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # @package _global_
2
+ defaults:
3
+ - core
4
+ - /data: chembl3d/chembl3d
5
+ - /embeddings: geom
chembl3d/explicit_h/runtime/configs/defaults/chembl3d/train_2x4090.yaml ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package _global_
2
+ defaults:
3
+ - core
4
+ - /data: chembl3d/max100
5
+ - /embeddings: geom
6
+ - _self_
7
+
8
+ # Preserve the base recipe's effective global batch: 2 GPUs * 80 * 4 = 640.
9
+ data:
10
+ datamodule:
11
+ train_prefetch_factor: 1
12
+ batch_size:
13
+ train: 80
14
+ val: 32
15
+ test: 32
16
+ num_workers:
17
+ train: 20
18
+ val: 2
19
+ test: 2
20
+
21
+ trainer:
22
+ trainer:
23
+ accelerator: gpu
24
+ devices: 2
25
+ accumulate_grad_batches: 4
26
+ max_epochs: -1
27
+ check_val_every_n_epoch: 5
28
+ num_val_samples: 4096
29
+ # Validation runs during fit; leave wall-clock headroom for final checkpointing.
30
+ do_validate: false
31
+ do_predict: false
32
+ final_checkpoint_path: final.ckpt
33
+
34
+ callbacks:
35
+ runtime_monitor:
36
+ _target_: morph.utils.training_monitor.TrainingMonitor
37
+ every_n_batches: 100
38
+ every_n_epochs_checkpoint:
39
+ every_n_epochs: 5
40
+ save_top_k: -1
41
+ save_last: link
chembl3d/explicit_h/runtime/configs/defaults/core.yaml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package _global_
2
+ # Shared default groups that don't change between QM9 / GEOM runs.
3
+ # Dataset-specific presets (defaults/qm9/qm9.yaml, defaults/geom/geom.yaml) inherit
4
+ # from this and only add `data` + `embeddings`.
5
+ defaults:
6
+ - /heads: head
7
+ - /insertion_head: unimodal
8
+ - /model: backbones/dit
9
+ - /schedule: smoothstep
10
+ - /trainer: trainer
11
+ - /callbacks: callbacks
12
+ - /logging: wandb_logger
13
+ - /integrator: integrator
chembl3d/explicit_h/runtime/configs/defaults/geom/geom.yaml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # @package _global_
2
+ # GEOM preset: shared core + GEOM data and embeddings.
3
+ defaults:
4
+ - core
5
+ - /data: geom/geom
6
+ - /embeddings: geom
chembl3d/explicit_h/runtime/configs/defaults/geom/scaffold_decoration.yaml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package _global_
2
+ # GEOM-Drugs **scaffold decoration** fine-tune preset.
3
+ #
4
+ # Mirrors defaults/qm9/scaffold_decoration.yaml on GEOM-Drugs. Bare-scaffold
5
+ # source -> full-molecule target: the model learns to grow a molecule by
6
+ # inserting decoration atoms around the fixed scaffold.
7
+ #
8
+ # Differences from the QM9 scaffold preset:
9
+ # - data -> geom/scaffold_decoration (GEOM base datasets)
10
+ # - embeddings -> geom
11
+ #
12
+ # Reuses the scaffold_finetune model config (loads the pretrained unconditional
13
+ # DiT into the clean lane + adds the node/edge conditioning and the gated
14
+ # cross-attn blocks).
15
+ #
16
+ # Pair with a GEOM unconditional DiT checkpoint via
17
+ # ``model.module.pretrained_ckpt=...``.
18
+ defaults:
19
+ - core
20
+ - /data: geom/scaffold_decoration
21
+ - /embeddings: geom
22
+ - override /model: finetune/scaffold_finetune
23
+ - _self_
chembl3d/explicit_h/runtime/configs/defaults/qm9/qm9.yaml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # @package _global_
2
+ # QM9 preset: shared core + QM9 data and embeddings.
3
+ defaults:
4
+ - core
5
+ - /data: qm9/qm9
6
+ - /embeddings: qm9
chembl3d/explicit_h/runtime/configs/defaults/qm9/scaffold_decoration.yaml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package _global_
2
+ # QM9 **scaffold decoration** fine-tune preset.
3
+ #
4
+ # Bare-scaffold source → full-molecule target. The model learns to grow
5
+ # a molecule by inserting decoration atoms around the fixed scaffold.
6
+ defaults:
7
+ - core
8
+ - /data: qm9/scaffold_decoration
9
+ - /embeddings: qm9
10
+ - override /model: finetune/scaffold_finetune
11
+ - _self_
chembl3d/explicit_h/runtime/configs/embeddings/geom.yaml ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Shared embedding stack tuned for GEOM-Drugs.
2
+ # - node_count_embedding uses ExtrapolatableCountEmbedding with periods/max_count
3
+ # scaled up for the much larger atom counts in GEOM-Drugs.
4
+
5
+ atom_type_embedding:
6
+ _target_: morph.model.layers.embedding.Embedding
7
+ num_embeddings: ${len:${data.vocab.atom_tokens}}
8
+ embedding_dim: 64
9
+ out_dim: 128
10
+
11
+ edge_type_embedding:
12
+ _target_: morph.model.layers.embedding.Embedding
13
+ num_embeddings: ${len:${data.vocab.edge_tokens}}
14
+ embedding_dim: 64
15
+ out_dim: 128
16
+
17
+ time_embedding:
18
+ _target_: morph.model.layers.embedding.TimeEmbedding
19
+ embedding_dim: 128
20
+ out_dim: 128
21
+
22
+ node_count_embedding:
23
+ _target_: morph.model.layers.embedding.ExtrapolatableCountEmbedding
24
+ embedding_dim: 64
25
+ out_dim: 128
26
+ min_period: 128.0
27
+ max_period: 1024.0
28
+ max_count: 96.0
chembl3d/explicit_h/runtime/configs/embeddings/qm9.yaml ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Shared embedding stack tuned for QM9.
2
+ # - node_count_embedding uses ExtrapolatableCountEmbedding so the model can
3
+ # generalise slightly outside the training atom-count support.
4
+ # - Defaults sized for QM9 (training counts in [3, 29]).
5
+
6
+ atom_type_embedding:
7
+ _target_: morph.model.layers.embedding.Embedding
8
+ num_embeddings: ${len:${data.vocab.atom_tokens}}
9
+ embedding_dim: 64
10
+ out_dim: 128
11
+
12
+ edge_type_embedding:
13
+ _target_: morph.model.layers.embedding.Embedding
14
+ num_embeddings: ${len:${data.vocab.edge_tokens}}
15
+ embedding_dim: 64
16
+ out_dim: 128
17
+
18
+ time_embedding:
19
+ _target_: morph.model.layers.embedding.TimeEmbedding
20
+ embedding_dim: 128
21
+ out_dim: 128
22
+
23
+ node_count_embedding:
24
+ _target_: morph.model.layers.embedding.ExtrapolatableCountEmbedding
25
+ embedding_dim: 64
26
+ out_dim: 128
27
+ min_period: 64.0
28
+ max_period: 512.0
29
+ max_count: 32.0
chembl3d/explicit_h/runtime/configs/heads/head.yaml ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Standard molecular DiT readouts. The insertion-content head is an independent
2
+ # Hydra choice (``insertion_head=unimodal`` by default, or ``=gmm``).
3
+
4
+ heads:
5
+ _target_: morph.model.layers.heads.MultiHeadModule
6
+ output_init_std: ${model.head_initialization.output_std}
7
+ independent_adapters: ${oc.select:model.head_independent_adapters,false}
8
+
9
+ heads_configs:
10
+ graph_aggregation: mean
11
+
12
+ node_heads:
13
+ atom_type_head:
14
+ input_dim: ${model.head_node_dim}
15
+ output_dim: ${len:${data.vocab.atom_tokens}}
16
+ adapter_dim: ${model.head_node_adapter_dim}
17
+ group: state
18
+
19
+ charge_head:
20
+ input_dim: ${model.head_node_dim}
21
+ output_dim: ${len:${data.vocab.charge_tokens}}
22
+ adapter_dim: ${model.head_node_adapter_dim}
23
+ group: state
24
+
25
+ ins_rate_head:
26
+ input_dim: ${model.head_node_dim}
27
+ output_dim: 1
28
+ adapter_dim: ${model.head_node_adapter_dim}
29
+ group: edit
30
+ initial_rate: ${model.head_initialization.ins_rate}
31
+
32
+ do_del_head:
33
+ input_dim: ${model.head_node_dim}
34
+ output_dim: 1
35
+ adapter_dim: ${model.head_node_adapter_dim}
36
+ group: edit
37
+ initial_probability: ${model.head_initialization.do_del_probability}
38
+
39
+ do_sub_a_head:
40
+ input_dim: ${model.head_node_dim}
41
+ output_dim: 1
42
+ adapter_dim: ${model.head_node_adapter_dim}
43
+ group: edit
44
+ initial_probability: ${model.head_initialization.do_sub_a_probability}
45
+
46
+ edge_heads:
47
+ edge_type_head:
48
+ input_dim: ${model.head_edge_dim}
49
+ output_dim: ${len:${data.vocab.edge_tokens}}
50
+ adapter_dim: ${model.head_edge_adapter_dim}
51
+ group: state
52
+
53
+ do_sub_e_head:
54
+ input_dim: ${model.head_edge_dim}
55
+ output_dim: 1
56
+ adapter_dim: ${model.head_edge_adapter_dim}
57
+ group: state
58
+ initial_probability: ${model.head_initialization.do_sub_e_probability}
59
+
60
+ ins_edge_head:
61
+ _target_: morph.model.layers.heads.InsertionEdgeHead
62
+ _recursive_: false
63
+ hidden_dim: ${model.head_node_dim}
64
+ readout_dim: ${model.head_insertion_edge_dim}
65
+ output_init_std: ${model.head_initialization.output_std}
66
+ n_edge_types: ${len:${data.vocab.edge_tokens}}
67
+ n_atom_types: ${len:${data.vocab.atom_tokens}}
68
+ n_charge_types: ${len:${data.vocab.charge_tokens}}
69
+ rbf_embedding_args:
70
+ _target_: morph.model.layers.embedding.RBFEmbedding
71
+ num_rbf: 128
72
+ rbf_dmax: 3.5
73
+ out_dim: 64
74
+ dropout: 0.0
75
+ trainable: false
chembl3d/explicit_h/runtime/configs/insertion_head/gmm.yaml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Optional mixture-density insertion-content head.
2
+ mode: gmm
3
+ dimension: 3
4
+ num_components: 10
5
+
6
+ model:
7
+ _target_: morph.model.layers.heads.GMMInsertionHead
8
+ hidden_dim: ${model.head_node_dim}
9
+ readout_dim: ${model.head_insertion_adapter_dim}
10
+ output_init_std: ${model.head_initialization.output_std}
11
+ num_components: ${insertion_head.num_components}
12
+ sigma_min: 0.05
13
+ sigma_max: 2.0
14
+ sigma_init: 0.5
15
+ N_a: ${len:${data.vocab.atom_tokens}}
16
+ N_c: ${len:${data.vocab.charge_tokens}}
chembl3d/explicit_h/runtime/configs/insertion_head/unimodal.yaml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Default insertion-content head: one nearest pending atom per spawning node.
2
+ mode: unimodal
3
+ dimension: 3
4
+ num_components: 1
5
+
6
+ model:
7
+ _target_: morph.model.layers.heads.UnimodalInsertionHead
8
+ hidden_dim: ${model.head_node_dim}
9
+ readout_dim: ${model.head_insertion_adapter_dim}
10
+ output_init_std: ${model.head_initialization.output_std}
11
+ independent_adapters: ${oc.select:model.head_independent_adapters,false}
12
+ N_a: ${len:${data.vocab.atom_tokens}}
13
+ N_c: ${len:${data.vocab.charge_tokens}}
chembl3d/explicit_h/runtime/configs/integrator/integrator.yaml ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ integrator:
2
+ _target_: morph.flow_matching.integration.RateIntegrator
3
+ vocab: ${data.vocab}
4
+ distributions: None
5
+ insertion_head_mode: ${insertion_head.mode}
6
+ insertion_dimension: ${insertion_head.dimension}
7
+ insertion_num_components: ${insertion_head.num_components}
8
+ # strategy used for sampling time during **integration**
9
+ # NOTE can be different from the time strategy used for the model (see below)
10
+ time_strategy: log # "linear", "log", or "beta"
11
+ # shape params for time_strategy: beta (Beta inverse-CDF spacing).
12
+ # defaults match the model's beta_sched (concentration1=2.0, concentration0=1.0)
13
+ time_beta_alpha: 2.0
14
+ time_beta_beta: 1.0
15
+ num_integration_steps: 100
16
+ ins_noise_scale: ${data.ins_noise_scale}
17
+ # When true, freshly-inserted nodes are pushed to the t+dt marginal (kappa-mix
18
+ # on atom/bond types, (1-t)*ins_noise on x) to match training. Charge remains
19
+ # a clean prediction target rather than an interpolated state. When false,
20
+ # insertions enter at the model's clean prediction.
21
+ renoise_insertions: true
22
+ # Integrate the known categorical schedule hazard over every time interval
23
+ # instead of using its left-endpoint Euler approximation.
24
+ # With either method, the final interval always completes pending discrete
25
+ # transitions using the learned edit probabilities (no extra model call).
26
+ exact_schedule_integration: true
27
+ del_schedule: ${data.interpolator.del_schedule}
28
+ ins_schedule: ${data.interpolator.ins_schedule}
29
+ sub_schedule: ${data.interpolator.sub_schedule}
30
+ sub_e_schedule: ${data.interpolator.sub_e_schedule}
chembl3d/explicit_h/runtime/configs/logging/wandb_logger.yaml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ ## Put your model args here
2
+ enabled: true
3
+ project: morph-${data.dataset_name}
4
+ name: ${oc.env:MORPH_RUN_ID,base}
5
+ save_dir: .
6
+ offline: false
chembl3d/explicit_h/runtime/configs/loss_weights/calibrated_geom.yaml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package model.module.loss_weights
2
+ # Balance-only (geometric-mean=1) calibration weights, scaled down by a global
3
+ # factor of 0.2 (1/5). At the original geometric-mean=1 scale the realized
4
+ # full-model grad-norm was ~5, far above gradient_clip_val=1.0, so clipping
5
+ # fired on every step. The 0.2 factor brings the realized norm to ~1.0 so the
6
+ # clip becomes a spike safety-valve again. Re-running calibration now applies
7
+ # this scaling automatically (see trainer.target_total_grad_norm).
8
+ c: 0.164
9
+ do_del: 0.20
10
+ do_sub_a: 0.30
11
+ do_sub_e: 0.24
12
+ ins_e: 0.21
13
+ ins_e_ii: 0.22
14
+ ins_state: 0.14
15
+ ins_rate: 0.20
16
+ sub_a_class: 0.15
17
+ sub_e_class: 0.19
18
+ x: 0.22
chembl3d/explicit_h/runtime/configs/loss_weights/calibrated_geom_frozen.yaml ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package model.module.loss_weights
2
+ # Balance-only (geometric-mean = 1) loss weights — ratios only. The learning
3
+ # rate owns step size and gradient_clip_val stays a pure spike catch, so these
4
+ # are NOT scaled to a target grad-norm (unlike the older calibrated_geom.yaml,
5
+ # which baked in a ~0.2 global scale to hit the clip target).
6
+ #
7
+ # Source: frozen init-state calibration (trainer.calibrate_mode=loss,
8
+ # calibrate_freeze_model=true), geom / DiT / uncond,
9
+ # run 2026-06-07 (outputs/geom/uncond/cal_then_lr/2026-06-07_12-29-25/calibrate).
10
+ # Because the model is held at init, every probe batch is an unbiased snapshot of
11
+ # the initial per-term losses — so the three binary do_* losses come out equal
12
+ # (all BCE at p=0.5 = ln2), unlike the previous 2500-step *training* probe
13
+ # (calibrated_geom.yaml) which gave them different weights purely from differing
14
+ # training rates. See notebooks/lr_calibration_analysis.ipynb for the comparison.
15
+ c: 0.745146
16
+ do_del: 1.730107
17
+ do_sub_a: 1.730107
18
+ do_sub_e: 1.730106
19
+ ins_e: 0.867232
20
+ ins_e_ii: 0.867232
21
+ ins_state: 0.519379
22
+ ins_rate: 1.128104
23
+ sub_a_class: 0.522109
24
+ sub_e_class: 0.867232
25
+ x: 1.298792
chembl3d/explicit_h/runtime/configs/loss_weights/tuned_weights.yaml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package model.module.loss_weights
2
+ # Canonical tuned weights for molecular training. Backbones can select this
3
+ # preset explicitly, and task-specific experiments may override the group. The
4
+ # package directive mounts the values under model.module.loss_weights.
5
+ do_sub_a: 0.2
6
+ sub_a_class: 0.03
7
+ do_sub_e: 0.25
8
+ sub_e_class: 0.5
9
+ do_del: 0.4
10
+ ins_rate: 0.15
11
+ ins_state: 0.01
12
+ ins_e: 0.4
13
+ x: 2.0 # was 1.0
14
+ c: 0.5
15
+ ins_e_ii: 0.03
chembl3d/explicit_h/runtime/configs/loss_weights/uniform.yaml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package model.module.loss_weights
2
+ # Uniform 1.0 weight per loss component. The key set matches
3
+ # LightningModuleRates.LOSS_GROUPS exactly (the 11 actual loss terms
4
+ # emitted by shared_step).
5
+ do_sub_a: 1.0
6
+ sub_a_class: 1.0
7
+ do_sub_e: 1.0
8
+ sub_e_class: 1.0
9
+ do_del: 1.0
10
+ ins_rate: 1.0
11
+ ins_state: 1.0
12
+ ins_e: 1.0
13
+ ins_e_ii: 1.0
14
+ x: 1.0
15
+ c: 1.0
chembl3d/explicit_h/runtime/configs/model/backbones/dit.yaml ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Molecular DiT: the repository's first-class generative backbone.
2
+ ##
3
+ ## This intentionally keeps charge out of the state and adds neither an
4
+ ## equivariant lane nor an evolving dense pair state. It retains the atom-count
5
+ ## condition: time plus current graph size disambiguates otherwise identical
6
+ ## local states and is especially useful while insertion/deletion is active.
7
+ defaults:
8
+ - /optimizer: muon_aux_adam
9
+ - /loss_weights: tuned_weights
10
+ - _self_
11
+
12
+ rotate: true
13
+ n_augmentations: 1
14
+
15
+ integrator: ${integrator.integrator}
16
+
17
+ beta_sched:
18
+ _target_: torch.distributions.Beta
19
+ concentration1: 2.0
20
+ concentration0: 1.0
21
+
22
+ linear_sched:
23
+ _target_: torch.distributions.Uniform
24
+ low: 0.0
25
+ high: 1.0
26
+
27
+ sample_time_strategy: linear
28
+
29
+ module:
30
+ _target_: morph.model.lightning.base.LightningModuleRates
31
+ insertion_head_mode: ${insertion_head.mode}
32
+ ins_noise_scale: ${data.ins_noise_scale}
33
+ ema_decay: 0.999
34
+ ema_warmup_steps: 5000
35
+ use_ema_for_eval: true
36
+ time_dist: ${if:${eq:${model.sample_time_strategy}, log}, ${model.beta_sched}, ${model.linear_sched}}
37
+ integrator: ${model.integrator}
38
+ vocab: ${data.vocab}
39
+ use_learnable_loss_weights: false
40
+ use_time_weights: false
41
+
42
+ model:
43
+ _target_: morph.model.backbones.dit.DiTBackboneWithHeads
44
+ _recursive_: false
45
+
46
+ atom_type_embedding_args: ${embeddings.atom_type_embedding}
47
+ edge_type_embedding_args: ${embeddings.edge_type_embedding}
48
+ time_embedding_args: ${embeddings.time_embedding}
49
+ node_count_embedding_args: ${embeddings.node_count_embedding}
50
+ # Sum the existing incident edge embeddings into each atom's input, excluding
51
+ # NO_BOND, through a zero-initialized projection. Uses the existing heads.
52
+ incident_bond_embedding: false
53
+ backbone_model_args: ${model.backbone}
54
+ heads_args: ${heads.heads}
55
+ insertion_head_args: ${insertion_head.model}
56
+ ins_edge_head_args: ${heads.ins_edge_head}
57
+
58
+ head_node_dim: ${model.backbone.out_node_nf}
59
+ head_edge_dim: ${model.backbone.out_edge_nf}
60
+
61
+ # Shared adapters avoid seven separate full-width MLPs. At the >150M preset,
62
+ # node/insertion adapters widen while pair readouts remain compact.
63
+ head_node_adapter_dim: 256
64
+ head_edge_adapter_dim: 256
65
+ head_insertion_adapter_dim: 256
66
+ head_insertion_edge_dim: 256
67
+ head_independent_adapters: true
68
+
69
+ # Categorical biases are replaced at module construction with exact dataset
70
+ # marginals. There is no dataset-level transition-frequency statistic for the
71
+ # binary/rate heads, so their defaults deliberately preserve the old neutral
72
+ # predictions instead of inventing a corpus-independent edit prior.
73
+ head_initialization:
74
+ output_std: 1.0e-3
75
+ coordinate_output_std: 0.0
76
+ do_del_probability: 0.5
77
+ do_sub_a_probability: 0.5
78
+ do_sub_e_probability: 0.5
79
+ ins_rate: 0.6931471805599453
80
+
81
+ token_weighting:
82
+ type_loss_token_weights: uniform
83
+ weight_alpha: 0.4
84
+
85
+ # Optional low-rank adaptation of the attention and SwiGLU projections. The
86
+ # adapter residual is zero at initialization, so enabling this while loading a
87
+ # pretrained checkpoint preserves its outputs exactly.
88
+ lora:
89
+ enabled: false
90
+ rank: 8
91
+ alpha: 8.0
92
+ dropout: 0.0
93
+ targets: [qkv, proj, mlp.gate_value, mlp.out_proj]
94
+ freeze_base: true
95
+
96
+ backbone:
97
+ _target_: morph.model.backbones.dit.DiTBackbone
98
+ _recursive_: false
99
+ d_input: ${embeddings.atom_type_embedding.out_dim}
100
+ d_cond: ${oc.eval:"${embeddings.time_embedding.out_dim} + ${embeddings.node_count_embedding.out_dim}"}
101
+ d_model: 512
102
+ out_node_nf: 256
103
+ out_edge_nf: 256
104
+ num_layers: 8
105
+ nhead: 16
106
+ mlp_ratio: 4.0
107
+ ffn_hidden_dim: null
108
+ ffn_multiple_of: 64
109
+ qk_norm: true
110
+ coordinate_output_init_std: ${model.head_initialization.coordinate_output_std}
111
+ center_coordinate_output: true
112
+ in_edge_nf: ${embeddings.edge_type_embedding.out_dim}
113
+ edge_pair_dim: 256
114
+ pair_bias_hidden_dim: 32
115
+ num_atom_types: ${len:${data.vocab.atom_tokens}}
116
+ lora: ${model.lora}
117
+ rbf_embedding_args:
118
+ _target_: morph.model.layers.embedding.RBFEmbedding
119
+ num_rbf: 128
120
+ rbf_dmax: 3.5
121
+ out_dim: 64
122
+ dropout: 0.0
123
+ trainable: false
chembl3d/explicit_h/runtime/configs/model/finetune/lora_finetune.yaml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Parameter-efficient fine-tuning of the unconditional DiT.
2
+ defaults:
3
+ - /model/backbones/dit@_here_
4
+ - _self_
5
+
6
+ lora:
7
+ enabled: true
8
+
9
+ module:
10
+ _target_: morph.model.finetune.base.LightningModuleRatesFinetune
11
+ pretrained_ckpt: ${oc.env:MORPH_PRETRAINED_CKPT}
12
+ pretrained_prefer_ema: true
13
+ freeze_pretrained_backbone: true
chembl3d/explicit_h/runtime/configs/model/finetune/scaffold_finetune.yaml ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DiT backbone augmented with Flamingo-style gated cross-attention for
2
+ # scaffold-decoration fine-tuning.
3
+ #
4
+ # Conditioning (all derived from mol_t.keep_mask):
5
+ # - node_cond : per-atom scaffold membership (always on)
6
+ # - edge_cond : per-edge scaffold class (always on)
7
+ # - cross-attn: scaffold subgraph encoded by a shared LightweightContextEncoder
8
+ # (OPTIONAL — toggle via backbone.use_cross_attn):
9
+ # * false → option 1: node+edge only
10
+ # * true → option 2: node+edge+cross-attn
11
+ #
12
+ # Inherits everything from configs/model/backbones/dit.yaml and overrides:
13
+ # - module._target_ → LightningModuleRatesScaffoldFinetune (loads a pretrained
14
+ # unconditional DiT checkpoint and adds scaffold loss/inference masking)
15
+ # - model._target_ → ScaffoldConditionalDiTBackboneWithHeads (auto-builds the
16
+ # node/edge cond and, when use_cross_attn, the scaffold cross-attn tokens)
17
+ # - backbone._target_ → ConditionalDiTBackbone (DiT + gated cross-attn blocks)
18
+ #
19
+ # Provides a ``pretrained_ckpt`` field for the lightning module to load.
20
+ defaults:
21
+ - /model/backbones/dit@_here_
22
+ - _self_
23
+
24
+ module:
25
+ _target_: morph.model.finetune.scaffold.LightningModuleRatesScaffoldFinetune
26
+ # Path to the unconditional DiT checkpoint to load weights from.
27
+ # ConditionalDiT keys are auto-retargeted (backbone.* → backbone.dit.*).
28
+ pretrained_ckpt: ${oc.env:MORPH_PRETRAINED_CKPT}
29
+ pretrained_prefer_ema: true
30
+ # If true, dispatches to ``self.model.freeze_pretrained()`` — for the
31
+ # cross-attn scaffold model this freezes the entire clean lane
32
+ # (embedding, ``backbone.dit``, all heads). Only the gated cross-attn
33
+ # blocks and edge gate plus the scaffold encoder train.
34
+ freeze_pretrained_backbone: true
35
+ optimizer_config:
36
+ optimizer:
37
+ lr: 1e-3
38
+ aux_optimizer:
39
+ lr: 1e-4
40
+ # Whether to exclude scaffold atoms from sub / x losses. These atoms are
41
+ # frozen at inference, so training them adds no useful signal.
42
+ exclude_scaffold_from_sub_loss: true
43
+ exclude_scaffold_from_x_loss: true
44
+ # Pin kept coordinates during integration and restore the input coordinate
45
+ # frame on output. To train flowing scaffold geometry, set this and the x-loss
46
+ # exclusion above to false.
47
+ freeze_keep_positions: true
48
+
49
+ # Override the model target to the scaffold-aware cross-attn wrapper.
50
+ model:
51
+ _target_: morph.model.finetune.scaffold.ScaffoldConditionalDiTBackboneWithHeads
52
+ _recursive_: false
53
+
54
+ atom_type_embedding_args: ${embeddings.atom_type_embedding}
55
+ edge_type_embedding_args: ${embeddings.edge_type_embedding}
56
+ time_embedding_args: ${embeddings.time_embedding}
57
+ node_count_embedding_args: ${embeddings.node_count_embedding}
58
+
59
+ backbone_model_args: ${model.backbone}
60
+
61
+ heads_args: ${heads.heads}
62
+ insertion_head_args: ${insertion_head.model}
63
+ ins_edge_head_args: ${heads.ins_edge_head}
64
+
65
+ # Cross-attention context encoder (option 2). Required because the backbone
66
+ # below has use_cross_attn: true. For option 1 (node+edge only), set
67
+ # backbone.use_cross_attn=false AND null this (the two must agree).
68
+ xattn_encoder_args: ${model.xattn_encoder}
69
+ # Geometric correspondence prior (query atom ↔ co-located scaffold token).
70
+ use_distance_bias: true
71
+
72
+ # Cross-attention context encoder (option 2): encodes the scaffold subgraph of
73
+ # mol_t (extracted via filter_nodes) into context tokens. ctx_dim must match
74
+ # model.backbone.ctx_dim.
75
+ # Node/edge conditioning is NOT configured here — the wrapper builds those directly
76
+ # from backbone.d_model / backbone.in_edge_nf. Null this (and set
77
+ # backbone.use_cross_attn=false) for node+edge-only scaffold conditioning.
78
+ xattn_encoder:
79
+ _target_: morph.model.finetune.base.LightweightContextEncoder
80
+ num_atom_types: ${len:${data.vocab.atom_tokens}}
81
+ num_charges: ${len:${data.vocab.charge_tokens}}
82
+ atom_embed_dim: 64
83
+ charge_embed_dim: 16
84
+ pos_embed_dim: 32
85
+ ctx_dim: ${model.backbone.ctx_dim}
86
+ num_attn_layers: 4
87
+ nhead: 8
88
+ mlp_ratio: 4.0
89
+ dropout: 0.0
90
+ # Edge-based pair bias on the encoder's self-attention (RBF distance +
91
+ # edge-type of the scaffold's own bonds) so tokens mix with geometry/topology
92
+ # awareness, like the DiT backbone. num_edge_types/edge_embed_dim only used here.
93
+ use_pair_bias: true
94
+ num_edge_types: ${len:${data.vocab.edge_tokens}}
95
+ edge_embed_dim: 32
96
+ num_rbf: 32
97
+ rbf_dmax: 5.0
98
+ pair_bias_hidden_dim: 32
99
+
100
+ # Override the backbone to use ConditionalDiTBackbone.
101
+ backbone:
102
+ _target_: morph.model.backbones.conditional_dit.ConditionalDiTBackbone
103
+ _recursive_: false
104
+ d_input: ${embeddings.atom_type_embedding.out_dim}
105
+ d_cond: ${oc.eval:"${embeddings.time_embedding.out_dim} + ${embeddings.node_count_embedding.out_dim}"}
106
+ d_model: 512
107
+ out_node_nf: 256
108
+ out_edge_nf: 256
109
+ num_layers: 8
110
+ nhead: 16
111
+ mlp_ratio: 4.0
112
+ ffn_hidden_dim: null
113
+ ffn_multiple_of: 64
114
+ qk_norm: true
115
+ coordinate_output_init_std: ${model.head_initialization.coordinate_output_std}
116
+ center_coordinate_output: true
117
+ in_edge_nf: ${embeddings.edge_type_embedding.out_dim}
118
+ edge_pair_dim: 256
119
+ pair_bias_hidden_dim: 32
120
+ num_atom_types: ${len:${data.vocab.atom_tokens}}
121
+ lora: ${model.lora}
122
+ ctx_dim: 128
123
+ use_cross_attn: true # cross-attention (cond_tokens) port
124
+ # Per-node conditioning: scaffold membership (which atoms to keep).
125
+ use_node_cond: true
126
+ # Per-edge conditioning: the scaffold encoder emits edge_cond at in_edge_nf.
127
+ use_edge_cond: true
128
+ rbf_embedding_args:
129
+ _target_: morph.model.layers.embedding.RBFEmbedding
130
+ num_rbf: 128
131
+ rbf_dmax: 3.5
132
+ out_dim: 64
133
+ dropout: 0.0
134
+ trainable: false
chembl3d/explicit_h/runtime/configs/optimizer/muon_aux_adam.yaml ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package model.module.optimizer_config
2
+ # Muon + auxiliary AdamW preset for the molecular DiT. Per-run overrides
3
+ # go on ``model.module.optimizer_config``:
4
+ # model.module.optimizer_config.optimizer.lr=0.005 ...
5
+ implementation: torch
6
+
7
+ optimizer:
8
+ # no _target_ -> triggers Muon path in configure_optimizers
9
+ # The RTX 4090 Euler template selects native-arithmetic CUDA graph replay.
10
+ execution: ${oc.env:MORPH_MUON_EXECUTION,native}
11
+ lr: 0.01
12
+ momentum: 0.95
13
+ weight_decay: 0.0
14
+ nesterov: true
15
+ ns_steps: 5
16
+ eps: 1e-7
17
+ adjust_lr_fn: original
18
+
19
+ aux_optimizer:
20
+ lr: 3e-4
21
+ betas: [0.9, 0.95]
22
+ eps: 1e-10
23
+ weight_decay: 0.0
24
+
25
+ # Lightning's two-optimizer path uses manual optimization, so clipping is
26
+ # configured here and applied once to the full model gradient.
27
+ gradient_clip_val: 1.0
28
+ gradient_clip_algorithm: norm
29
+
30
+ scheduler:
31
+ _target_: morph.utils.lr_schedulers.MultiGroupCosineWarmupLR
32
+ # configure_optimizers injects each native optimizer here.
33
+ warmup_steps: 2000 # Typically 2-5% of total_steps
34
+ total_steps: 200000 # The total number of training iterations
35
+ min_lr_fraction: 0.05 # Decays to 5% of each optimizer's peak LR
36
+ last_epoch: -1
37
+
38
+ interval: step
39
+ monitor: null
chembl3d/explicit_h/runtime/configs/schedule/beta.yaml ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ _target_: morph.flow_matching.schedules.BetaSchedule
2
+ k_alpha: 1.0
3
+ k_beta: 1.5
chembl3d/explicit_h/runtime/configs/schedule/cubic.yaml ADDED
@@ -0,0 +1 @@
 
 
1
+ _target_: morph.flow_matching.schedules.CubicSchedule
chembl3d/explicit_h/runtime/configs/schedule/fast_power.yaml ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Default kappa schedule: FastPowerSchedule with beta=2.5
2
+ _target_: morph.flow_matching.schedules.FastPowerSchedule
3
+ beta: 2.0
chembl3d/explicit_h/runtime/configs/schedule/linear.yaml ADDED
@@ -0,0 +1 @@
 
 
1
+ _target_: morph.flow_matching.schedules.LinearSchedule
chembl3d/explicit_h/runtime/configs/schedule/smoothstep.yaml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ _target_: morph.flow_matching.schedules.SmoothstepSchedule
2
+ shift: 0.8
chembl3d/explicit_h/runtime/configs/trainer/trainer.yaml ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Put your trainer args here
2
+ trainer:
3
+ deterministic: warn
4
+ devices: auto
5
+ accelerator: auto
6
+ #precision: 32
7
+ strategy: ddp_find_unused_parameters_false
8
+ precision: "bf16-mixed"
9
+ max_epochs: 3000
10
+ # Effective global batch = devices * batch_size.train * accumulation. The
11
+ # native optimizer mixin handles this because it steps Muon + AdamW.
12
+ accumulate_grad_batches: 1
13
+ # A validation batch performs full generative integration, so Lightning's
14
+ # pre-training sanity pass is expensive and does not exercise optimization.
15
+ num_sanity_val_steps: 0
16
+ # Muon + AdamW use Lightning manual optimization; clipping is configured in
17
+ # configs/optimizer/muon_aux_adam.yaml and applied to both branches together.
18
+ gradient_clip_val: null
19
+ gradient_clip_algorithm: norm
20
+
21
+ # Validation performs full generative integration. The established QM9
22
+ # training recipes use this cadence so sampling does not dominate fitting.
23
+ check_val_every_n_epoch: 50
24
+ # profiler: simple
25
+
26
+ # Fixed GLOBAL validation population. run.py converts this to a per-rank
27
+ # limit, giving the same 4096 molecules on 1, 2 or 4 GPUs.
28
+ num_val_samples: 4096
29
+ # Explicit profiling escape hatch; a non-null value takes precedence over
30
+ # num_val_samples and is interpreted by Lightning per rank.
31
+ limit_val_batches: null
32
+ limit_predict_batches: 4
33
+
34
+ do_train: true
35
+ do_validate: true
36
+ do_predict: true
37
+ checkpoint_path: null
chembl3d/explicit_h/runtime/pyproject.toml ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "morph"
3
+ version = "0.1.0"
4
+ description = "Flow-matching molecular generation and scaffold decoration with a molecular DiT"
5
+ readme = "README.md"
6
+ keywords = ["molecular-generation", "flow-matching", "scaffold-decoration"]
7
+ requires-python = ">=3.11"
8
+ dependencies = [
9
+ "click>=8.3.1",
10
+ "hydra-core>=1.3.2",
11
+ "lightning>=2.5.5",
12
+ "omegaconf>=2.3.0",
13
+ "numpy>=2.0",
14
+ "pandas>=2.3.3",
15
+ "rdkit>=2025.9.1",
16
+ "scipy>=1.15.0",
17
+ # Pre-release allowed: the NVIDIA 25.10 container ships a CUDA-enabled
18
+ # 2.9.0a0 build. Preserve that container build instead of replacing it with
19
+ # a mismatched PyPI Torch/torchvision pair.
20
+ "torch>=2.9.0a0, <2.9.1",
21
+ "torch-geometric>=2.7.0",
22
+ "torchmetrics>=1.8.0",
23
+ "wandb>=0.22.2",
24
+ "posebusters>=0.6.0",
25
+ "matplotlib>=3.10.8",
26
+ "lmdb>=2.2.0",
27
+ "py3dmol>=2.5.4",
28
+ "ipykernel>=7.2.0",
29
+ "ase>=3.28.0",
30
+ ]
31
+
32
+ [dependency-groups]
33
+ dev = ["pytest>=9.1.1", "ruff>=0.16.5"]
34
+
35
+ [project.optional-dependencies]
36
+ flash-attn = ["flash-attn>=2.8.3, <2.8.4"]
37
+
38
+ [project.scripts]
39
+ morph = "morph.cli:cli"
40
+
41
+ [tool.uv.extra-build-dependencies]
42
+ flash-attn = [{ requirement = "torch", match-runtime = true }]
43
+
44
+ [build-system]
45
+ requires = ["hatchling"]
46
+ build-backend = "hatchling.build"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/morph"]
50
+
51
+ [tool.hatch.build.targets.wheel.force-include]
52
+ "configs" = "morph/configs"
53
+
54
+ [tool.ruff]
55
+ # Allow lines to be as long as 88.
56
+ line-length = 88
57
+ # Keep attributed third-party implementations close to their upstream sources.
58
+ extend-exclude = ["src/morph/_vendor"]
59
+
60
+ [tool.ruff.format]
61
+ docstring-code-format = true
62
+
63
+ # Correctness, readability, and public API documentation.
64
+ [tool.ruff.lint]
65
+ # consider selecting "ISC", for implicit string concat
66
+ # consider selecting "T201", for print in production code
67
+ select = [
68
+ "B", # flake8-bugbear
69
+ "C4", # flake8-comprehensions
70
+ "D", # pydocstyle
71
+ "E", # pycodestyle error
72
+ "E501", # line too long
73
+ "EXE", # flake8-executable
74
+ "F", # pyflakes
75
+ "FLY", # flynt
76
+ "I", # isort
77
+ "ICN", # flake8-import-conventions
78
+ "PD", # pandas-vet
79
+ "PERF", # perflint
80
+ "PIE", # flake8-pie
81
+ "PL", # pylint
82
+ "PT", # flake8-pytest-style
83
+ "PYI", # flakes8-pyi
84
+ "Q", # flake8-quotes
85
+ "RSE", # flake8-raise
86
+ "RUF", # Ruff-specific rules
87
+ "SIM", # flake8-simplify
88
+ "SLOT", # flakes8-slot
89
+ "TCH", # flake8-type-checking
90
+ "TID", # flake8-tidy-imports
91
+ "UP", # pyupgrade
92
+ "W", # pycodestyle warning
93
+ "W505", # line too long
94
+ "YTT", # flake8-2020
95
+ ]
96
+ # also consider ignoring "C901", for high mccabe function complexity
97
+ # also consider ignoring "ERA001", for found commented out code
98
+ ignore = [
99
+ "EXE002", # shebang is not a python interpreter
100
+ "RUF012", # missing classvar type
101
+ "RUF013", # Optional prohibited by PEP484
102
+ "B006", # Do not use mutable data structures for argument defaults
103
+ "ANN003", # missing type annotations for kwargs
104
+ "B019", # Use of functools.lru_cache on methods can lead to memory leaks
105
+ "C408", # unnecessary-collection-call
106
+ "COM812", # trailing comma missing
107
+ "D205", # 1 blank line required between summary line and description
108
+ "D415", # first line of docstring must end with punctuation
109
+ "DTZ005", # use of datetime.now() without timezone
110
+ "E731", # do not assign a lambda expression, use a def
111
+ "EM", # flake8-errmsg
112
+ "FBT001", # Boolean positional argument in function
113
+ "FBT002", # Boolean keyword argument in function
114
+ "NPY002", # TODO replace legacy np.random.seed
115
+ "PLR", # pylint refactor
116
+ "PLW2901", # Outer for loop variable overwritten by inner assignment target
117
+ "PT006", # pytest-parametrize-names-wrong-type
118
+ "PT011", # pytest-raises-too-broad
119
+ "PT013", # pytest-incorrect-pytest-import
120
+ "PT019", # pytest-fixture-param-without-value
121
+ "PTH", # prefer Path to os.path
122
+ "S301", # pickle can be unsafe
123
+ "S310", # url open functions can be unsafe
124
+ "TRY003", # long exception messages not defined in the exception class itself
125
+ "UP015", # unnecessary "r" in open call
126
+ "D107", # docstring for __init__
127
+ "D212", # remove whitespace after opening quotes
128
+ "D202", # no blank lines after docstring
129
+ "D301",
130
+ "D100",
131
+ "D103",
132
+ ]
133
+ pydocstyle.convention = "google"
134
+
135
+ [env]
136
+ PROJECT_ROOT = "."
chembl3d/explicit_h/runtime/requirements-tested.txt ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch==2.9.0
2
+ numpy==2.4.2
3
+ rdkit==2025.9.6
4
+ lightning==2.6.1
5
+ pytorch-lightning==2.6.1
6
+ torch-geometric==2.7.0
7
+ torchmetrics==1.8.2
8
+ hydra-core==1.3.2
9
+ omegaconf==2.3.0
10
+ scipy==1.17.1
11
+ pandas==3.0.1
12
+ wandb==0.25.0
13
+ posebusters==0.6.5
14
+ matplotlib==3.10.9
15
+ lmdb==2.2.0
16
+ click==8.3.1
17
+ py3dmol==2.5.4
18
+ ipykernel==7.2.0
19
+ ase==3.28.0