MolecularDiffusion.modules.tasks.diffusion_diffsbdd¶
DiffSBDD task: pocket-conditioned (and joint) 3D ligand diffusion.
Four objects, mirroring the platform’s usual layout:
DiffSBDDTask– the duck-typed Task (docs/adding_new_models.md Section 2.1) wrapping eitherConditionalDDPMorEnVariationalDiffusion, selected bymode.ModelTaskFactory– the_target_ofconfigs/tasks/diffusion_diffsbdd.yaml(and of its 4-linediffusion_diffsbdd_joint.yamlsibling, which only overridesmode).DiffSBDDPocketGenerator– the_target_of BOTHconfigs/interference/gen_diffsbdd_pocket.yamlandgen_diffsbdd_inpaint.yaml; they differ only in whetherfixed_atom_indicesis null.GenerativeFactory’ssample(batch_size, nodesxsample, ...)has no channel for “which pocket”, so pocket-conditioned models get their own generator behind their own_target_– the established in-tree pattern (KGDiffPocketGenerator,PMDMPocketGenerator), needing no core change:cli/generate.pyonly doesinstantiate(cfg.interference, task=task)then.run().LigandPocketSizePrior– the ligand-size marginal of the model’s 2D joint(n_lig, n_pocket)histogram, wrapped to satisfy thenode_dist_modelcontract.
Deviations from the generic contract, both shared with the other pocket models in-tree:
sample()requires a pocket.ConditionalDDPM.sample()itself raises without one, sointerference: gen_unconditionaldoes not apply here.Sampled coordinates come back in the input pocket’s frame – the generator restores the pocket CoM exactly as
lightning_modules.py:845-852does.
## The mode guard (read this before touching mode_id)
Both modes share task_type: diffusion_diffsbdd AND – verified against the
released weights – identical state-dict keys and shapes (122 tensors
each; update_pocket_coords is a flag, not a parameter). The integration
plan expected a shape mismatch to catch a joint checkpoint loaded under a
pocket_conditioning config; there is none, and cli/generate.py:322
loads with strict=False, so such a mix-up would run silently on the wrong
sampler. The mode_id buffer below is the fix: it round-trips through the
checkpoint, and DiffSBDDTask._check_mode() refuses to train or sample
when the loaded value disagrees with the configured mode.
Out of scope this pass (see the integration plan): optimize.py /
diversify(), SimpleConditionalDDPM, CA pocket representation, virtual
nodes, the auxiliary Lennard-Jones loss, trajectory export, Vina docking, and
EnVariationalDiffusion.sample() (the fully unconditional path that
hallucinates a pocket).
Attributes¶
Classes¶
Pocket-conditioned generation and inpainting. |
|
Task contract around |
|
Ligand-size prior over the model's 2D joint histogram. |
|
Hydra entry point for |
Module Contents¶
- class MolecularDiffusion.modules.tasks.diffusion_diffsbdd.DiffSBDDPocketGenerator(task, pocket_db: str | None = None, pocket_index: int = 0, num_generate: int = 20, batch_size: int = 4, num_steps: int | None = None, mol_size: list | None = None, fixed_atom_indices: list | None = None, add_n_nodes: int | None = None, resamplings: int = 1, center: str = 'ligand', jump_length: int = 1, output_path: str = 'generated_diffsbdd', seed: int = 42, device: str | None = None, **kwargs: Any)¶
Bases:
MolecularDiffusion.modules.tasks.pocket_generator.PocketGeneratorPocket-conditioned generation and inpainting.
One class behind both interference configs – they differ only in whether
fixed_atom_indicesis null, and aDiffSBDDInpaintGeneratorwould be an interface with one implementation.The pocket (and, for inpainting, the reference ligand that defined it) comes from one row of the converted ASE db, read with
center=Falseso sampled ligands come back in that pocket’s own frame.Fixed atoms are selected by index into the stored reference ligand (
fixed_atom_indices: [0, 1, 2, 3]) rather than by PDB atom name (upstream’s--fix_atoms "C1 N6 C5"): the db has no atom names, and the samelig_fixedtensor reachesinpaint()either way.The sampling loop itself lives in
PocketGenerator.- add_n_nodes = None¶
- center = 'ligand'¶
- db_required_msg = 'interference.pocket_db is required: DiffSBDD has no unconditional mode. Point it at a converted...¶
- fixed_atom_indices¶
- jump_length = 1¶
- resamplings = 1¶
- tag = 'diffsbdd'¶
- class MolecularDiffusion.modules.tasks.diffusion_diffsbdd.DiffSBDDTask(model: torch.nn.Module, mode: str = 'pocket_conditioning', atom_vocab: List[str] | None = None)¶
Bases:
torch.nn.ModuleTask contract around
ConditionalDDPM/EnVariationalDiffusion.- evaluate(pred, target)¶
- forward(batch)¶
Loss assembly ported from
lightning_modules.py:236-302.Mode-independent: both models return the same keys, the conditional one substituting a constant 0 for the pocket terms.
l2with per-example dimension normalisation while training, the full VLB otherwise.
- predict_and_target(batch)¶
- sample(batch_size=None, nodesxsample=None, num_steps=None, batch=None, ligand=None, lig_fixed=None, resamplings: int = 1, center: str = 'ligand', jump_length: int = 1, **kwargs)¶
Sample ligands inside the pocket carried by
batch.The signature deliberately deviates from Section 2.1: it takes a pocket.
batchmust holdpocket_coords/pocket_one_hot/pocket_mask/num_pocket_nodesfrom a collated DiffSBDD batch (DiffSBDDPocketGeneratorbuilds it).The whole cost of joint mode + inpainting is the three-way branch below; the last two rows of the plan’s table are literally the same call, which is upstream’s own design (
lightning_modules.py:814-834), not a shortcut:mode
lig_fixed
call
pocket_conditioning
None
sample_given_pocketpocket_conditioning
given
ConditionalDDPM.inpaintjoint
None->zeros
EnVariationalDiffusion.inpaintjoint
given
same call
NB the
modekwarg thatGenerativeFactorypasses ("ddpm"/"ddim") is swallowed by**kwargsand is NOT this task’s generativeself.mode.Returns
(one_hot, charges, coords, node_mask)padded to(B, N, .)in the INPUT POCKET’s frame.chargesis zeros: DiffSBDD has no charge channel.
- atom_vocab¶
- property device¶
- mode = 'pocket_conditioning'¶
- model¶
- property node_dist_model: LigandPocketSizePrior¶
- prop_dist_model = None¶
- split = 'train'¶
- class MolecularDiffusion.modules.tasks.diffusion_diffsbdd.LigandPocketSizePrior(histogram: torch.Tensor)¶
Ligand-size prior over the model’s 2D joint histogram.
.sample(n)returns the ligand-axis marginal, which is what the Section 2.1node_dist_modelcontract asks for. The pocket generator does not use it: it callssample_conditional(n2=pocket_sizes)on the model’s ownDistributionNodes, which is the native behaviour and the only one that respects pocket size.- sample(n_samples: int) torch.Tensor¶
- marginal¶
- n_node_dist¶
- class MolecularDiffusion.modules.tasks.diffusion_diffsbdd.ModelTaskFactory(task_type: str = 'diffusion_diffsbdd', mode: str = 'pocket_conditioning', atom_nf: int = NUM_ATOM_CLASSES, residue_nf: int = NUM_ATOM_CLASSES, n_dims: int = 3, joint_nf: int = 32, hidden_nf: int = 128, n_layers: int = 5, inv_sublayers: int = 1, attention: bool = True, tanh: bool = True, norm_constant: float = 1, sin_embedding: bool = False, normalization_factor: float = 100, aggregation_method: str = 'sum', reflection_equivariant: bool = False, edge_cutoff_ligand: float | None = None, edge_cutoff_pocket: float | None = 5.0, edge_cutoff_interaction: float | None = 5.0, edge_embedding_dim: int | None = None, diffusion_steps: int = 500, diffusion_noise_schedule: str = 'polynomial_2', diffusion_noise_precision: float = 0.0005, normalize_factors: Sequence[float] = (1.0, 4.0), max_n_lig: int = 107, max_n_pocket: int = 1671, atom_vocab: List[str] | None = None, train_set: Any = None, **kwargs: Any)¶
Hydra entry point for
configs/tasks/diffusion_diffsbdd*.yaml.train_setis declared socli/train.py’s declarative seam (docs/adding_new_models.md Section 2.5) injects the training set: the 2D joint size histogram is a genuine dataset statistic and is needed at train time too, for thelog_pNVLB term. It is stored as a buffer on the model so it survives the checkpoint and generation can rebuild withtrain_set=None. The buffer’s shape is fixed bymax_n_lig/max_n_pocket, so checkpoint shapes never depend on which db was used; the defaults are the released CrossDocked histogram’s own shape.- build() DiffSBDDTask¶
- atom_vocab¶
- ddpm_kwargs¶
- dynamics_kwargs¶
- max_n_lig = 107¶
- max_n_pocket = 1671¶
- mode = 'pocket_conditioning'¶
- task: DiffSBDDTask | None = None¶
- task_type = 'diffusion_diffsbdd'¶
- train_set = None¶
- MolecularDiffusion.modules.tasks.diffusion_diffsbdd.FLOAT_TYPE¶
- MolecularDiffusion.modules.tasks.diffusion_diffsbdd.INT_TYPE¶