MolecularDiffusion.modules.tasks.diffusion_jodo¶
JODO task: joint continuous diffusion over coordinates, atom types, formal charges and an explicit bond tensor.
JODO (Huang et al., NeurIPS 2023, arXiv:2305.12347) diffuses the 2D molecular graph and the 3D geometry together in one continuous VP process: a sample arrives with real bond orders and formal charges, no geometry-based perception needed. What separates it from MiDi – the platform’s other bond-generating model – is that bonds are continuous here, not categorical: a bond is two (or three) float channels that get thresholded back to a class at decode time.
Data path: data_type: graph3d with bond_collate: dense.
graph3d_dense_collate already emits the padded (B,N,N) integer bond
matrix, so the adapter below is a handful of vectorized lines – upstream’s
collate_edge / EdgeComCondTransform are not needed at all.
Bond mapping (identity on the class ids, then JODO’s compressed encoding):
canonical JODO class channels (exist, order[, aromatic])
0 = none 0 (0, 0 [, 0])
1 = SINGLE 1 (1, 1/3 [, 0])
2 = DOUBLE 2 (1, 2/3 [, 0])
3 = TRIPLE 3 (1, 1 [, 0])
4 = AROMATIC 4 (1, 0 , 1) -- only when edge_ch == 3
edge_ch: 2 (QM9) has no aromatic channel, so the data config MUST set
kekulize: true; an aromatic bond would otherwise silently become “no bond”
(upstream datasets/build_dataset.py:151). edge_ch: 3 (GEOM) keeps
aromatic and takes kekulize: false.
Formal charges are the one place JODO differs from MiDi: there is no
categorical charge head. The raw signed charge goes into a single float
channel divided by fc_charge_norm and is rounded back at decode.
Conditional generation rides the platform’s existing
GenerativeFactory.conditional_generation seam – condition: [gap]
selects Cond_DGT_concat and this class implements sample_conditonal.
Out of scope this pass (see the integration plan): multi-property conditioning, the EGNN property classifiers (an evaluation tool, not the model), 2D-only mode, DPM-solver fast sampling, and trajectories/inpainting.
Attributes¶
Classes¶
JODO wrapped in the platform's duck-typed Task contract (§2.1). |
|
Hydra entry point for JODO ( |
Functions¶
|
|
|
Rotate the clean positions onto the noisy ones ( |
|
Batched Kabsch rotation, port of upstream |
Module Contents¶
- class MolecularDiffusion.modules.tasks.diffusion_jodo.JodoDiffusionTask(atom_vocab: list, nf: int, n_layers: int, n_heads: int, n_extra_heads: int, dropout: float, mlp_ratio: int, spatial_cut_off: float, edge_ch: int, include_fc_charge: bool, cond_time: bool, dist_gbf: bool, gbf_name: str, trans_name: str, softmax_inf: bool, edge_quan_th: float, com: bool, pred_data: bool, self_cond: bool, noise_align: bool, centered: bool, normalize_factors: list, loss_weights: list, reduce_mean: bool, noise_schedule: str, beta_0: float, beta_1: float, sampling_steps: int, condition: list, normalize_condition: str, sdf_output_path: str | None, n_atoms_hist: dict, prop_dist_model: MolecularDiffusion.modules.models.en_diffusion.DistributionProperty | None = None, property_norms: dict | None = None)¶
Bases:
torch.nn.ModuleJODO wrapped in the platform’s duck-typed Task contract (§2.1).
- evaluate(pred: torch.Tensor, target: torch.Tensor) dict¶
- forward(batch: dict) tuple[torch.Tensor, dict]¶
One training step: port of
losses.py:285-382.
- predict_and_target(batch: dict) tuple[torch.Tensor, torch.Tensor]¶
Pure-generative stub: the loss is both prediction and target.
- sample(batch_size: int | None = None, nodesxsample: torch.Tensor | None = None, num_steps: int | None = None, batch: dict | None = None, mode: str | None = None, n_frames: int = 0, context: torch.Tensor | None = None, **kwargs: Any) tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]¶
Ancestral sampling; port of
sampling.py:518-597+post_process.Returns the platform’s
(one_hot, charges, coords, node_mask).chargescarries signed formal charges (FlowMol/MiDi precedent). The generated bond matrix has no slot in that tuple, so it is stashed onself.last_bond_typesand, whensdf_output_pathis set, written as an.sdfsidecar alongside the platform’s.xyz.
- sample_conditonal(nodesxsample: torch.Tensor = None, target_value: list | None = None, n_frames: int = 0, mode: str | None = None, num_steps: int | None = None, **kwargs: Any)¶
Property-conditional sampling.
Called by
GenerativeFactory.conditional_generation(runmodes/generate/tasks_generate.py:536) – the misspelling is the platform’s, not a typo here. Turns raw target values into the normalizedcontextthe conditional backbone was trained on.
- T¶
- atom_vocab¶
- backbone¶
- centered¶
- condition¶
- property device: torch.device¶
- edge_ch¶
- include_fc_charge¶
- last_bond_types: torch.Tensor | None = None¶
- loss_weights¶
- property model: JodoDiffusionTask¶
tasks_generate.pyreadstask.model.T; self is the model.
- n_atom_types¶
- node_dist_model¶
- noise_align¶
- noise_scheduler¶
- normalize_condition¶
- pred_data¶
- prop_dist_model = None¶
- property_norms = None¶
- reduce_mean¶
- sdf_output_path¶
- self_cond¶
- class MolecularDiffusion.modules.tasks.diffusion_jodo.ModelTaskFactory(task_type: str = 'diffusion_jodo', nf: int = 256, n_layers: int = 8, n_heads: int = 16, n_extra_heads: int = 2, dropout: float = 0.1, mlp_ratio: int = 2, spatial_cut_off: float = 2.0, edge_ch: int = 2, include_fc_charge: bool = True, cond_time: bool = True, dist_gbf: bool = True, gbf_name: str = 'CondGaussianLayer', trans_name: str = 'TransMixLayer', softmax_inf: bool = True, edge_quan_th: float = 0.0, com: bool = True, pred_data: bool = True, self_cond: bool = True, noise_align: bool = True, centered: bool = True, normalize_factors: list | None = None, loss_weights: list | None = None, reduce_mean: bool = False, noise_schedule: str = 'cosine', beta_0: float = 0.1, beta_1: float = 20.0, sampling_steps: int = 1000, condition: list | None = None, normalize_condition: str = 'mad', sdf_output_path: str | None = None, atom_vocab: list | None = None, train_set: torch.utils.data.Dataset | None = None, **kwargs: Any)¶
Hydra entry point for JODO (
configs/tasks/diffusion_jodo.yaml).Declares
train_setsocli/train.py’s declarative seam injects the training dataset: the molecule-size histogram (and, for the conditional variant, the property distribution + mean/MAD normalizer) is needed at construction time and is not annn.Modulebuffer.sdf_output_pathis declared generation-time (docs §2.5b): the task is rebuilt from the checkpoint’s training-time config, where it isnull, so without this declaration the generate config’s value never arrives and the bond sidecar – JODO’s whole 2D half – is silently dropped.- build() JodoDiffusionTask¶
Construct the task, deriving size/property priors from the data.
- atom_vocab¶
- beta_0 = 0.1¶
- beta_1 = 20.0¶
- centered = True¶
- com = True¶
- cond_time = True¶
- condition = []¶
- dist_gbf = True¶
- dropout = 0.1¶
- edge_ch = 2¶
- edge_quan_th = 0.0¶
- gbf_name = 'CondGaussianLayer'¶
- generation_time_keys = ('sdf_output_path',)¶
- include_fc_charge = True¶
- kwargs¶
- loss_weights = [1.0, 0.25, 0.1]¶
- mlp_ratio = 2¶
- n_extra_heads = 2¶
- n_heads = 16¶
- n_layers = 8¶
- nf = 256¶
- noise_align = True¶
- noise_schedule = 'cosine'¶
- normalize_condition = 'mad'¶
- normalize_factors = [1, 4, 4, 1]¶
- pred_data = True¶
- reduce_mean = False¶
- sampling_steps = 1000¶
- sdf_output_path = None¶
- self_cond = True¶
- softmax_inf = True¶
- spatial_cut_off = 2.0¶
- task: JodoDiffusionTask | None = None¶
- task_type = 'diffusion_jodo'¶
- train_set = None¶
- trans_name = 'TransMixLayer'¶
- MolecularDiffusion.modules.tasks.diffusion_jodo.expand_dims(v: torch.Tensor, dims: int) torch.Tensor¶
vof shape[N]->[N, 1, ..., 1]withdimsdimensions.
- MolecularDiffusion.modules.tasks.diffusion_jodo.get_align_position(z_t: torch.Tensor, xh: torch.Tensor) torch.Tensor¶
Rotate the clean positions onto the noisy ones (
losses.py:403).
- MolecularDiffusion.modules.tasks.diffusion_jodo.kabsch_batch(coords_pred: torch.Tensor, coords_tar: torch.Tensor) torch.Tensor¶
Batched Kabsch rotation, port of upstream
losses.py:424.
- MolecularDiffusion.modules.tasks.diffusion_jodo.Chem = None¶
- MolecularDiffusion.modules.tasks.diffusion_jodo.logger¶