MolecularDiffusion.runmodes.generate.tasks_gcdm_optimize¶
GCDM property optimization – refine molecules you already have towards a property target.
This is an ADDITIVE sibling of GenerativeFactory, not a modification of
it. cli/generate.py does
hydra.utils.instantiate(cfg.interference, task=task) followed by
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_.
WHAT IT DOES (and what it is NOT)
Port of GCDM’s
mol_gen_optimize(others/bio-diffusion/src/models/components/variational_diffusion.py:1416), driven fromsrc/mol_gen_eval_optimization_qm9.py:152.It is a time-compressed reverse loop seeded with a real molecule:
The clean molecule is normalized, then forward-diffused to
t0 = noise_levelviaq(z_t0 | x)– SDEdit-style. See DEVIATION below for why this step exists.The ordinary reverse loop then runs for
num_optimization_timesteps(K = 100) steps, with the time axis rescaled by K rather than by the model’s T = 1000 and truncated att0– so step K-1 is presented to the network ast = t0. Upstream calls the rescalingnorm_with_original_timesteps=Falseand its own comment marks it as “important”: dividing byself.model.Tinstead would compress the trajectory into the last 10% of the schedule and produce a near-no-op.The context vector is the desired property value, so the reverse loop pulls the molecule towards it.
The whole thing repeats
iterationstimes, each round re-seeded with the previous round’s output. Upstream applies no filtering or selection between rounds and neither does this.DEVIATION FROM UPSTREAM (deliberate, attempt 2)
Upstream hands the clean normalized latent straight to
sample_p_zs_given_ztatt = 1.0(variational_diffusion.py:1455-1470); the caller (mol_gen_eval_optimization_qm9.py:161-171) does no forward noising either, so upstream really has noq(z_t | x)step. That is a signal/noise-scale mismatch: over the whole chain the reverse process multiplies the latent by1 / alpha(t=1) ~ 300, which is only cancelled when the seed is drawn fromN(0, I). Reproduced faithfully in attempt 1 it blew molecules apart (135 A median nearest-neighbour distance, 0/20 connected), while the same checkpoint through the ordinary conditional sampler from a Gaussian seed gave 8/8 connected at 1.03 A. So the loop and the weights are fine and the seed is not. Forward-diffusing tot0puts the seed on the scale the network was trained to see att0, which is the standard way to edit an existing sample with a diffusion model.noise_level = 1.0restores upstream’s literal behaviour (full noise, i.e. the seed only sets the atom count).The default 0.5 remains UNVALIDATED. It is a reasonable middle value chosen to make the mode usable (
alpha(0.5) = 0.75, so the seed keeps most of its signal), not a tuned optimum. A 0.1/0.3/0.5/0.7 sweep has since been run (attempt-2 smoke test) – its main result was thatnoise_levelwas NOT the cause of the failure being chased at the time, since every value failed identically. On the corrected code path a follow-up at K=100 measured 0.3 as marginally better than 0.5 (4/4 vs 3/4 connected, comparable ~1.02 A median nearest-neighbour distance), but that is n=4 on one property with one checkpoint – far too weak to call an optimum, so 0.5 stands as the default. Do not read either number as a result. Sweep it for your property before quoting anything that depends on it.No property classifier is loaded. GCDM’s EGNN classifiers only score finished samples for the paper’s table (
src/__init__.py:208); no gradient ever flows through them into the loop. Scoring the result is out of scope here – the platform already has a scoring seam atGenerativeFactory.property_prediction.
- REQUIREMENTS
A property-conditional checkpoint (GCDM ships six for QM9: alpha, gap, homo, lumo, mu, Cv) and a directory of
.xyzstarting molecules. Nothing generates those here on purpose – feed it the output of an ordinaryMolCraftDiff generaterun, exactly as GCDM’s own README does in two stages (README:181 then :184).
Classes¶
Interference-side entry point for GCDM property optimization. |
Module Contents¶
- class MolecularDiffusion.runmodes.generate.tasks_gcdm_optimize.GCDMOptimizeFactory(task, condition_configs: dict | None = None, target_values: Sequence[float] = (), property_names: Sequence[str] = (), iterations: int = 1, num_optimization_timesteps: int = 100, noise_level: float = 0.5, batch_size: int = 8, seed: int = 86, output_path: str = 'gcdm_optimized', save_every_iteration: bool = True, **kwargs)¶
Interference-side entry point for GCDM property optimization.
- Parameters:
task – The loaded
GeomMolecularGenerativetask (injected bycli/generate.py).condition_configs –
input_mols_dir(required), plusiterationsandnum_optimization_timestepsif not given at the top level.property_names (target_values /) – The property target, in the property’s own units. Normalized through the task’s existing
prop_dist_modelexactly asGenerativeFactorydoes.iterations – Optimization rounds (upstream default 1 –
mol_gen_eval_optimization_qm9.yaml:27).num_optimization_timesteps – K, the compressed trajectory length (upstream default 100 –
mol_gen_eval_optimization_qm9.yaml:28; thenum_timesteps: 10two lines above it belongs to the initial unconditional generation atmol_gen_eval_optimization_qm9.py:335, not to this loop). Note K is deliberately NOT the model’s T.noise_level –
t0in(0, 1]– how far the input is forward-diffused before the reverse walk starts. Low = stays close to the input molecule, 1.0 = full noise (the input then only fixes the atom count). See the module docstring’s DEVIATION note.
- batch_size = 8¶
- condition_configs¶
- device¶
- input_mols_dir¶
- iterations¶
- noise_level¶
- num_optimization_timesteps¶
- output_path = 'gcdm_optimized'¶
- property_names = []¶
- save_every_iteration = True¶
- seed = 86¶
- target_values = []¶
- task¶