MolecularDiffusion.modules.tasks.diffusion_diffdec¶
DiffDec: pocket-aware scaffold decoration with an end-to-end diffusion model.
DiffDec (https://github.com/biomed-AI/DiffDec, Xie et al., J. Chem. Inf. Model. 64(7) 2554-2564, 2024, doi:10.1021/acs.jcim.3c01466) grows an R-group off a named anchor atom of a fixed 3D scaffold, inside a fixed protein pocket. Scaffold and pocket atoms are never noised; only the R-group rows are diffused. See docs/model_integrations/diffdec/INTEGRATION_PLAN.md for the full integration plan (scope, data adapters, task-contract mapping).
No new model package. DiffDec is a fork of DiffLinker, and its
src/edm_single.py, src/noise.py and src/egnn.py are the ported
MolecularDiffusion.modules.models.difflinker modules modulo formatting and
a fragment/linker -> scaffold/rgroup rename (verified by
normalized diff – INTEGRATION_PLAN.md “Repo Inspection”). So this task reuses
modules.models.difflinker rather than copying a fourth near-identical
EDM/EGNN into the tree. That package is frozen ported upstream code now
shared by two tasks (diffusion_difflinker.py and this one) – changing it
changes both.
The mask-name mapping across that seam is:
DiffDec difflinker.EDM / Dynamics
---------------------------------------------------------
scaffold_mask (scaf+pocket) fragment_mask (never noised)
rgroup_mask linker_mask (diffused)
context[..., -2] scaffold_only_mask fragment_only_mask
context[..., -1] pocket_only_mask pocket_only_mask
DynamicsWithPockets is used with graph_type="4A", i.e. a 4 A radius
graph rebuilt each forward pass, which is exactly DiffDec’s
get_dist_edges (egnn.py l. 531-539).
Generation goes through DiffDecScaffoldGenerator, not through
GenerativeFactory: DiffDec has no unconditional mode, and
sample(batch_size, nodesxsample) has no channel for “which scaffold, which
pocket, which anchor”. That is the established pattern for every
pocket-conditioned model here (DiffSBDDPocketGenerator,
gen_kgdiff_pocket.yaml, gen_pmdm_pocket.yaml).
Classes¶
Scaffold decoration inside a fixed pocket. |
|
Plain |
|
Factory matching |
Module Contents¶
- class MolecularDiffusion.modules.tasks.diffusion_diffdec.DiffDecScaffoldGenerator(task: DiffDecTask, data_file: str | None = None, complex_index: int = 0, num_generate: int = 20, batch_size: int = 4, num_steps: int | None = None, output_path: str = 'generated_diffdec', save_reference: bool = True, seed: int = 42, device: str | None = None, **kwargs: Any)¶
Scaffold decoration inside a fixed pocket.
cli/generate.py:673doesinstantiate(cfg.interference, task=task)then.run(), so every key of the interference config lands straight in this__init__– nothing needs registering. Precedent:DiffSBDDPocketGenerator.The scaffold, its anchor atom and the pocket all come from ONE row of upstream’s preprocessed
.pt(data_file+complex_index).num_generateR-groups are then sampled for that one input, which is upstream’s own protocol (sample_single.py:--n_samplesdecorations per complex).- batch_size = 4¶
- complex_index = 0¶
- data_file = None¶
- device = 'cuda'¶
- num_generate = 20¶
- num_steps = None¶
- output_path = 'generated_diffdec'¶
- save_reference = True¶
- seed = 42¶
- task¶
- class MolecularDiffusion.modules.tasks.diffusion_diffdec.DiffDecTask(in_node_nf: int, n_dims: int, hidden_nf: int, activation: str, tanh: bool, n_layers: int, attention: bool, norm_constant: float, inv_sublayers: int, sin_embedding: bool, normalization_factor: float, aggregation_method: str, model: str, normalization: str | None, condition_time: bool, anchors_context: bool, diffusion_steps: int, diffusion_noise_schedule: str, diffusion_noise_precision: float, diffusion_loss_type: str, normalize_factors: tuple, center_of_mass: str, atom_vocab: list | None = None)¶
Bases:
torch.nn.ModulePlain
nn.Moduletask wrapper (TABASCO-style – see docs/adding_new_models.md §2.6) implementing the §2.1 contract around DiffLinker’sEDMdriven by DiffDec’s mask/context convention.self.edmis deliberately named to match upstream’s own attribute path (DDPM.edm), so the releaseddiffdec_single.ckpt’sedm.*keys land without renaming – seedocs/model_integrations/diffdec/scripts/convert_checkpoint.py.- decorate(batch: Dict[str, Any], rgroup_sizes: torch.Tensor | None = None, keep_frames: int = 1)¶
Grow R-groups onto the scaffolds in
batch.Port of
model_single.py::DDPM.sample_chain(l. 289-340). Returns(one_hot, positions, ligand_mask)whereligand_maskisatom_mask - pocket_mask– scaffold + R-group, pocket dropped – matching upstreamsample_single.pyl. 148.Coordinates come back in the centred frame (partial COM removed); the caller adds the offset back, as upstream does at
sample_single.pyl. 146.rgroup_sizesdefaults torgroup_mask.sum(1), i.e. the fixed 10-slot budget – upstream’ssample_fn = Nonepath. Unused slots come back as the fake'#'atom.
- evaluate(pred: torch.Tensor, target: torch.Tensor) dict¶
- sample(*args: Any, **kwargs: Any)¶
Not reachable through
GenerativeFactory– see the module docstring and INTEGRATION_PLAN.md’s Task-contract mapping.The §2.1 signature is kept so the contract check passes, but DiffDec cannot generate without a scaffold, a pocket and an anchor atom, and
sample(batch_size, nodesxsample, ...)carries none of those. UseDiffDecScaffoldGenerator(configs/interference/ gen_diffdec_scaffold.yaml), which callsdecorate().
- anchors_context¶
- atom_vocab¶
- center_of_mass¶
- property device: torch.device¶
- edm¶
- in_node_nf¶
- loss_type¶
- property model¶
- n_dims¶
- ndim_extra = 0¶
- property node_dist_model: MolecularDiffusion.modules.models.difflinker.linker_size.DistributionNodes | None¶
- norm_values¶
- prop_dist_model = None¶
- class MolecularDiffusion.modules.tasks.diffusion_diffdec.DiffDecTaskFactory(task_type: str = 'diffusion_diffdec', in_node_nf: int = 10, n_dims: int = 3, hidden_nf: int = 128, activation: str = 'silu', tanh: bool = False, n_layers: int = 6, attention: bool = False, norm_constant: float = 1e-06, inv_sublayers: int = 2, sin_embedding: bool = False, normalization_factor: float = 100, aggregation_method: str = 'sum', model: str = 'egnn_dynamics', normalization: str | None = 'batch_norm', condition_time: bool = True, anchors_context: bool = True, diffusion_steps: int = 500, diffusion_noise_schedule: str = 'polynomial_2', diffusion_noise_precision: float = 1e-05, diffusion_loss_type: str = 'l2', normalize_factors: tuple = (1, 4, 10), center_of_mass: str = 'anchors', atom_vocab: list | None = None, **kwargs: Any)¶
Factory matching
cli/train.py’stask_module.build()/task_module.taskinstantiation pattern (precedent:diffusion_difflinker.py::DiffLinkerTaskFactory).Defaults follow DiffDec
configs/single.yml+train_single.py’s argparse defaults, cross-checked against the releaseddiffdec_single.ckpt’s ownhyper_parameters.- build() DiffDecTask¶
- activation = 'silu'¶
- aggregation_method = 'sum'¶
- anchors_context = True¶
- atom_vocab¶
- attention = False¶
- center_of_mass = 'anchors'¶
- condition_time = True¶
- diffusion_loss_type = 'l2'¶
- diffusion_noise_precision = 1e-05¶
- diffusion_noise_schedule = 'polynomial_2'¶
- diffusion_steps = 500¶
- in_node_nf = 10¶
- inv_sublayers = 2¶
- kwargs¶
- model = 'egnn_dynamics'¶
- n_dims = 3¶
- n_layers = 6¶
- norm_constant = 1e-06¶
- normalization = 'batch_norm'¶
- normalization_factor = 100¶
- normalize_factors = (1, 4, 10)¶
- sin_embedding = False¶
- tanh = False¶
- task: DiffDecTask | None = None¶
- task_type = 'diffusion_diffdec'¶