MolecularDiffusion.runmodes.generate.tasks_conformer¶
Conformer generation – k 3D shapes of each molecule you already have.
This is an ADDITIVE sibling of GenerativeFactory, not a modification of
it, following the same route as tasks_gcdm_optimize.py:
cli/generate.py does hydra.utils.instantiate(cfg.interference,
task=task) then generator.run(), the GenerativeFactory annotation on
that line is cosmetic, and there is no registry – so a new class in a new
file plugs in purely through its _target_. Nothing in
tasks_generate.py is touched.
WHY IT EXISTS
A conformer generator is handed a molecule and returns 3D shapes OF THAT MOLECULE. Run through
gen_unconditionalthat is mis-modelled in four ways:
mol_sizechooses a molecule SIZE, but a conformer generator’s size is dictated by its input. For LoQI the drawn size is only used to snap to the nearest pool molecule, somol_sizesilently selects WHICH molecule you get.
num_generateis a flat sample count, so “20 conformers of THIS molecule” is inexpressible.Output is
molecule_0.xyz,molecule_1.xyz… with nothing recording which input each conformer belongs to – and.xyzhas no bond channel, so that mapping is the only link back to the chemistry.De-novo metrics (uniqueness, novelty) are ~0 BY CONSTRUCTION here.
This mode asks for
conformers_per_molecule, refusesmol_sizerather than accepting-and-ignoring it, groups output per input molecule, and scores with conformer metrics.
TASK CONTRACT (duck-typed, no registry, no base class)
A task works with this mode if it exposes ONE of:
generate_conformers(items, **sampler_kwargs)– returns(positions, batch_segments), flat and concatenated, coordinates only. Preferred, and what a model whose de-novosample()cannot exist implements (DiTMC, NExT-Mol);
sample(mols=[...])– one conformer per entry ofmols, returning the platform tuple(one_hot, charges, coords, node_mask)(LoQI).Anything else raises a clear TypeError at construction.
The input molecules come from either:
interference.sample_input– THE standard: a.sdf, a.smi/.txt, a graph3d ASE.db, or an inline SMILES list, loaded byutils/conformer_pool.load_conditioning_pool. A model needs no pool code of its own; or
conditioning_pool() -> list[Data]on the task – the older seam, still honoured (LoQI takes its pool fromtasks.sample_inputso it can also fall back to the training set).
sampler_kwargsis an opaque dict forwarded togenerate_conformers. Per-model knobs (num_steps,free_guidance_scale, …) go there, so this config never accretes the union of every model’s hyperparameters.
OUTPUT LAYOUT
- output_path/
- mol_0000/conformer_000.xyz … <- one .xyz per conformer (what every
analyze/ command already reads)
- mol_0000/conformers.sdf <- the SAME conformers WITH bonds and
formal charges, multi-conformer sdf
mol_0000/reference.sdf <- the input molecule + its geometry conformers.csv <- one row per conformer: which input it
came from, its SMILES, its metrics
The per-molecule directory is the mapping (3), the .sdf carries the bond channel .xyz cannot (also 3), and the .csv is the machine-readable index. Molecule building reuses
build_rdkit_molfrom the graph3d dataset and coordinate writing reusessave_xyz_file; no parallel writer.
METRICS (and what is deliberately NOT computed)
Per conformer, against the INPUT molecule’s own geometry:
rmsd– heavy-atom symmetry-corrected RMSD (GetBestRMSafterRemoveHs), the GEOM/conformer-benchmark convention.
energy– xTB single-point, opt-in (xtb_energy: true) and only if thextbbinary is on PATH; reusesanalyze.compare_to_optimized .get_xtb_energy. Otherwise the column is empty, never a made-up number.Aggregated per molecule and logged:
rmsd_min(the MAT-style matching number) andcoverage(fraction of conformers withinrmsd_threshold, precision-style).NOT computed: recall-side COV-R / MAT-R. Those need a reference ENSEMBLE of experimentally/CREST-derived conformers per molecule, and the input pool carries exactly one geometry per molecule. Reporting them off a single reference would be a meaningless number.
HONESTY CAVEAT:
rmsdis RMSD to the input geometry. That is a true reference only when the input carried real 3D (.sdf, or a graph3d .db). A SMILES input has an ETKDG conformer embedded for it, so the “reference” is itself a prediction – the run logs a warning when it detects this.
Attributes¶
Classes¶
Generate |
Module Contents¶
- class MolecularDiffusion.runmodes.generate.tasks_conformer.ConformerFactory(task: Any, conformers_per_molecule: int = 10, batch_size: int = 8, max_molecules: int = 0, rmsd_threshold: float = 0.5, xtb_energy: bool = False, seed: int | None = None, output_path: str = 'generated_conformers', sample_input: Any = None, indices: Any = None, sampler_kwargs: Any = None, **kwargs: Any)¶
Generate
conformers_per_moleculeconformers of every input molecule.Deliberately does NOT accept
mol_sizeornum_generate: the input molecule dictates its own atom count, and the request here is per-molecule, not a flat total. Passing either raises rather than being ignored.- batch_size¶
- conformers_per_molecule = 10¶
- indices = None¶
- max_molecules = 0¶
- output_path = 'generated_conformers'¶
- rmsd_threshold¶
- sample_input = None¶
- sampler_kwargs¶
- seed = None¶
- task¶
- xtb_energy = False¶
- MolecularDiffusion.runmodes.generate.tasks_conformer.logger¶