MolecularDiffusion.modules.tasks.diffusion_midi¶
MiDi task: joint diffusion over coordinates, atom types, bonds and charges.
MiDi (Vignac et al., ECML 2023, arXiv:2302.09048) is the first model in this platform that generates the molecular graph itself – bond orders and formal charges are diffused jointly with the 3D coordinates, so a sample arrives with an explicit bond table instead of needing post-hoc perception.
Data path: data_type: graph3d with bond_collate: dense.
graph3d_dense_collate already produces MiDi’s exact dense shapes, so the
adapter below is a handful of F.one_hot calls and upstream’s
utils.to_dense (the PyG -> dense bridge) is not needed at all.
Bond classes are the platform’s canonical five (0=none, 1=SINGLE, 2=DOUBLE,
3=TRIPLE, 4=AROMATIC) and MiDi uses exactly those, in the same order – the
mapping is the identity.
Formal charges are stored raw and signed; the offset and class count are
applied here (charge_offset/n_charge_classes: QM9 -> +1/3,
GEOM -> +2/6), never baked into the dataset.
Out of scope this pass (see the integration plan): ExtraFeatures (every
released config sets extra_features: null), the variational-NLL validation
path, MiDi’s own molecular metrics, and the size-aware loader.
Property-conditioning + classifier-free guidance use the same config
signature as configs/tasks/diffusion.yaml (en_diffusion’s formulation):
set condition_names to opt in. The injection point is PlaceHolder.y,
MiDi’s own per-graph global feature, already FiLM-injected into every
transformer layer – see modules/models/midi/transformer_model.py.
Attributes¶
Classes¶
MiDi wrapped in the platform's duck-typed Task contract (§2.1). |
|
Hydra entry point for the MiDi task ( |
Module Contents¶
- class MolecularDiffusion.modules.tasks.diffusion_midi.MidiDiffusionTask(atom_vocab: list, n_layers: int, hidden_mlp_dims: dict, hidden_dims: dict, diffusion_steps: int, diffusion_noise_schedule: str, transition: str, nu: dict, lambda_train: list, charge_offset: int, n_charge_classes: int, sdf_output_path: str | None, x_marginals: torch.Tensor | None, e_marginals: torch.Tensor | None, charges_marginals: torch.Tensor | None, n_atoms_hist: dict, condition_names: list = [], context_mask_rate: float = 0.0, mask_value: float = 0.0, normalize_condition: str | None = None, adapter_conditions: list | None = None, use_adapter_module: bool = False)¶
Bases:
torch.nn.ModuleMiDi wrapped in the platform’s duck-typed Task contract (§2.1).
- evaluate(pred: torch.Tensor, target: torch.Tensor) dict¶
Reduce accumulated losses into the logged validation metric.
- forward(batch: dict) tuple[torch.Tensor, dict]¶
One training step: noise the batch, denoise it, weight the losses.
- predict_and_target(batch: dict) tuple[torch.Tensor, torch.Tensor]¶
Pure-generative stub: the loss is both prediction and target.
- preprocess(train_set=None, valid_set=None, test_set=None)¶
Build self.property_norms for CFG conditioning (train-side only).
Called generically by cli/train.py if this attribute exists. Does NOT touch node_dist_model/n_node_dist – those come from graph3d_stats at __init__ time via ModelTaskFactory, a separate mechanism. Deliberately skips DistributionProperty/prop_dist_model (out of scope – generation always takes an explicit target_value).
- 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, condition: torch.Tensor | None = None, negative_condition: torch.Tensor | None = None, cfg_scale: float = 0.0, cfg_scale_schedule: str | None = None, **kwargs: Any) tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]¶
Sampling, unconditional unless
condition/cfg_scaleare set.Returns the platform’s
(one_hot, charges, coords, node_mask)tuple.chargescarries signed formal charges (FlowMol’s precedent for this slot) – the element identity is inone_hot. The generated bond matrix has no channel 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_guidance_conitional(target_function: Any = None, target_value: list | None = None, negative_target_value: list | None = None, nodesxsample: torch.Tensor | None = None, cfg_scale: float = 1, cfg_scale_schedule: str | None = None, guidance_ver: str = 'cfg', n_frames: int = 0, num_steps: int | None = None) tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]¶
Classifier-free-guidance generation.
Matches the call signature
GenerativeFactory.conditional_generation()hardcodes fortask_type == "cfg", and returns(one_hot, charges, x, node_mask)likesample().
- T¶
- atom_vocab¶
- backbone¶
- charge_offset¶
- condition = []¶
- context_mask_rate = 0.0¶
- property device: torch.device¶
Device of the backbone parameters.
- input_dims¶
- lambda_train¶
- last_bond_types: torch.Tensor | None = None¶
- mask_value = 0.0¶
- property model: MidiDiffusionTask¶
tasks_generate.pyreadstask.model.T; self is the model.
- n_adapter_context¶
- n_atom_types¶
- n_charge_classes¶
- n_concat_context¶
- node_dist_model¶
- normalize_condition = None¶
- output_dims¶
- prop_dist_model = None¶
- property_norms = None¶
- sdf_output_path¶
- task_type = 'diffusion_midi'¶
- class MolecularDiffusion.modules.tasks.diffusion_midi.ModelTaskFactory(task_type: str = 'diffusion_midi', n_layers: int = 12, hidden_mlp_dims: dict | None = None, hidden_dims: dict | None = None, diffusion_steps: int = 500, diffusion_noise_schedule: str = 'cosine', transition: str = 'marginal', nu: dict | None = None, lambda_train: list | None = None, charge_offset: int = 1, n_charge_classes: int = 3, 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 the MiDi task (
configs/tasks/diffusion_midi).Declares
train_setsocli/train.py’s declarative seam injects the training dataset: MiDi’s noise model needs the atom/bond/charge marginals and the size histogram at construction time, and neither is annn.Modulebuffer, so neither survives in a checkpoint.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 – the whole 2D half of the model – is silently dropped.- build() MidiDiffusionTask¶
Construct the task, deriving marginals/size histogram from data.
- atom_vocab¶
- charge_offset = 1¶
- diffusion_noise_schedule = 'cosine'¶
- diffusion_steps = 500¶
- generation_time_keys = ('sdf_output_path',)¶
- kwargs¶
- lambda_train = [3, 0.4, 1, 2, 0]¶
- n_charge_classes = 3¶
- n_layers = 12¶
- nu¶
- sdf_output_path = None¶
- task: MidiDiffusionTask | None = None¶
- task_type = 'diffusion_midi'¶
- train_set = None¶
- transition = 'marginal'¶
- MolecularDiffusion.modules.tasks.diffusion_midi.Chem = None¶
- MolecularDiffusion.modules.tasks.diffusion_midi.logger¶