MolecularDiffusion.modules.tasks.diffusion_ligandiff_multi¶
multi-LigandDiff integration with the MolecularDiffusion data pipeline.
multi-LigandDiff (https://github.com/Neon8988/multi_LigandDiff, ChemRxiv 2024)
regenerates several ligands at once around a fixed transition-metal
centre – “partial to total generation”, up to every ligand from a bare metal.
It is a direct extension of LigandDiff, already integrated here as
modules/tasks/diffusion_ligandiff.py.
The network is not re-ported. EDM and Dynamics are imported
unchanged from MolecularDiffusion.modules.models.ligandiff and constructed
with ligand_group_node_nf=7; that reproduces the released checkpoint’s
shapes exactly (strict=True load, 238/238 tensors matched). See
docs/model_integrations/ligandiff_multi/INTEGRATION_PLAN.md for the
experiment. Only the 20-metal geometry helpers are new, in
modules/models/ligandiff_multi/utils.py.
What is new relative to ligandiff:
the conditioning channel is 7 wide,
cat([ligand_group(6), coord_site(1)])(upstreamsrc/lightning.py:126), wherecoord_siteis a per-atom “coordinates the metal” flag.ligand_groupitself is still 6 slots.the row-data contract is therefore 16 columns, not 15.
normalize_factorsis[10, 4, 1], not[1, 4, 10].sample()grows several ligands, picking an octahedral denticity partition fromCN_OCT(upstreamgenerate.py::reform_data).
Two vocabularies, deliberately different widths:
in_node_nf = 8– the model’s / dataset’s one-hot width, upstream’s heavy-atom vocab{C,N,O,S,Br,Cl,P,F}insrc/const.py:11order (byte-identical to LigandDiff’s). The metal carries an all-zero row and its element travels out of band.task.atom_vocab– 28 entries (those 8, then the 20 metals). A decode-side symbol table only; it sizes no tensor. It is mandatory becauserunmodes/generate/tasks_generate.py:1463resolves reference-structure elements withonehot(..., allow_unknown=False).
Attributes¶
Classes¶
Plain |
|
Factory matching |
|
Dense |
Functions¶
|
Flat |
Module Contents¶
- class MolecularDiffusion.modules.tasks.diffusion_ligandiff_multi.LigandDiffMultiTask(in_node_nf: int, n_dims: int, ligand_group_node_nf: int, hidden_nf: int, n_layers: int, activation: str, attention: bool, tanh: bool, norm_constant: float, inv_sublayers: int, sin_embedding: bool, normalization_factor: float, aggregation_method: str, model: str, normalization: str | None, condition_time: bool, drop_rate: float, 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, denticity_split: list | None = None)¶
Bases:
torch.nn.ModulePlain
nn.Moduletask (docs/adding_new_models.md §2.6) wrapping the imported, unmodified LigandDiffEDM.The EDM is held at
self.edmbecause the released checkpoint’s 238 tensors are all namededm.*; any other attribute name makes every key “unexpected” andcli/generate.py:322’sstrict=Falseload drops them all silently.- evaluate(pred: torch.Tensor, target: torch.Tensor) dict¶
Validation metric.
- sample(nodesxsample: torch.Tensor | None = None, batch_size: int | None = None, num_steps: int | None = None, batch: dict | None = None, condition_tensor: torch.Tensor | None = None, condition_mode: str | None = None, outpaint_cfgs: dict | None = None, use_noised_conditioning: bool = False, n_frames: int = 0, n_retrys: int = 0, t_retry: int | None = None, context: torch.Tensor | None = None, **kwargs)¶
Grow several ligands around a fixed metal-complex scaffold.
Dispatched through the bundled
outpaintGenerativeFactory path (configs/interference/gen_outpaint.yaml->tasks_generate.py::structural_guidance), whose mechanism – freeze a reference scaffold, draw a strictly larger total size, grow the difference – is exactly upstream’sgenerate.py::reform_data. The scaffold may be as small as a bare metal (upstream’s[]_[...]total-generation case).Out of scope this pass, accepted and ignored:
batch,num_steps,condition_mode,use_noised_conditioning,n_frames(no trajectory export),n_retrys/t_retry,context(noprop_dist_model).
- T¶
- property atom_count_histogram: dict | None¶
Total-atom-count histogram accumulated from real batches.
- atom_vocab¶
- center_of_mass¶
- denticity_split¶
- property device: torch.device¶
Device the parameters live on.
- edm¶
- in_node_nf¶
- ligand_group_node_nf¶
- loss_type¶
- property model¶
tasks_generate.pyreadsT/norm_valuesoff this.
- n_dims¶
- ndim_extra = 0¶
- property node_dist_model: MolecularDiffusion.modules.models.difflinker.linker_size.DistributionNodes | None¶
Sampler over total molecule sizes.
- norm_values¶
- prop_dist_model = None¶
- class MolecularDiffusion.modules.tasks.diffusion_ligandiff_multi.LigandDiffMultiTaskFactory(task_type: str, in_node_nf: int = 8, n_dims: int = 3, ligand_group_node_nf: int = 7, hidden_nf: int = 192, n_layers: int = 5, activation: str = 'silu', attention: bool = True, tanh: bool = True, norm_constant: float = 1, inv_sublayers: int = 1, sin_embedding: bool = False, normalization_factor: float = 100, aggregation_method: str = 'sum', model: str = 'gvp_dynamics', normalization: str | None = 'batch_norm', condition_time: bool = True, drop_rate: float = 0.2, diffusion_steps: int = 500, diffusion_noise_schedule: str = 'learned', diffusion_noise_precision: float = 1e-05, diffusion_loss_type: str = 'vlb', normalize_factors: tuple = (10, 4, 1), center_of_mass: str = 'context', atom_vocab: list | None = None, denticity_split: list | None = None, **kwargs)¶
Factory matching
cli/train.py’sbuild()instantiation pattern.The trailing
**kwargsis load-bearing, not decoration:cli/train.py:649-651passesnode_feature_dim/extra_norm_valuesinto every factory. Both are meaningless here (the 16 row-data columns are consumed by the adapter, not normalised as extra features) and are deliberately absorbed and ignored.- build() LigandDiffMultiTask¶
Instantiate the task module.
- activation = 'silu'¶
- aggregation_method = 'sum'¶
- atom_vocab¶
- attention = True¶
- center_of_mass = 'context'¶
- condition_time = True¶
- denticity_split¶
- diffusion_loss_type = 'vlb'¶
- diffusion_noise_precision = 1e-05¶
- diffusion_noise_schedule = 'learned'¶
- diffusion_steps = 500¶
- drop_rate = 0.2¶
- in_node_nf = 8¶
- inv_sublayers = 1¶
- kwargs¶
- ligand_group_node_nf = 7¶
- model = 'gvp_dynamics'¶
- n_dims = 3¶
- n_layers = 5¶
- norm_constant = 1¶
- normalization = 'batch_norm'¶
- normalization_factor = 100¶
- normalize_factors = (10, 4, 1)¶
- sin_embedding = False¶
- tanh = True¶
- task_type¶
- class MolecularDiffusion.modules.tasks.diffusion_ligandiff_multi.PointCloudToLigandDiffMultiBatch¶
Dense
(B, N, ·)PointCloud batch -> multi-LigandDiff’s flat layout.Identical in shape to
ligandiff’s adapter, one column wider. The dataset supplies[one_hot(8) | ligand_diff(1) | ligand_group(6) | coord_site(1)]through the existingdata.use_row_data_featuresseam (data/component/dataset.py:1328).The 16 columns are taken from the right-hand end of
node_feature, not from index 0:DataModule.load()’sdata_type: pointcloudbranch (runmodes/train/data.py:294-312) does not forwarduse_ohe_featurethe way itspygsibling at :284 does, so the 28-wide atom OHE is always prepended regardless of the config flag. Slicing from the end is correct either way and needs no change to shared code.There is no synthesis fallback: the Zenodo splits ship every column precomputed, so a missing one means a misconfigured dataset and must raise rather than silently train on a fabricated split.
- MolecularDiffusion.modules.tasks.diffusion_ligandiff_multi.LigandDiffMultiToPointCloud(coords_flat: torch.Tensor, charges_flat: torch.Tensor, batch_seg: torch.Tensor, batch_size: int, atom_vocab: list)¶
Flat
(N_total, ·)output -> dense PointCloud tensors.charges_flatmust already be the final per-atom atomic number: taken from the known context elements forcontextrows (which is what stops the metal’s all-zero one-hot decoding to index 0, i.e. carbon) and from the 8-wayone_hot.argmaxfor generated rows. The returned one-hot is re-expanded from those atomic numbers ontoatom_vocab(28 entries).Returns
(one_hot, charges, coords, node_mask), all dense.
- MolecularDiffusion.modules.tasks.diffusion_ligandiff_multi.LIGANDIFF_ATOM_VOCAB = ['C', 'N', 'O', 'S', 'Br', 'Cl', 'P', 'F']¶
- MolecularDiffusion.modules.tasks.diffusion_ligandiff_multi.LIGANDIFF_MULTI_FULL_VOCAB = ['C', 'N', 'O', 'S', 'Br', 'Cl', 'P', 'F', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn',...¶
- MolecularDiffusion.modules.tasks.diffusion_ligandiff_multi.LIGANDIFF_MULTI_METAL_VOCAB = ['Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Zr', 'Mo', 'Ru', 'Rh', 'Pd', 'Cd', 'W',...¶
- MolecularDiffusion.modules.tasks.diffusion_ligandiff_multi.LIGANDIFF_MULTI_ROW_DATA_COLUMNS¶