sinAE is a single, non-equivariant Transformer framework that generates 3D atomic structures across multiple modalities with one shared model:
data_type |
Modality | Coordinates | Extra tokens |
|---|---|---|---|
0 |
Molecules | Cartesian | — |
1 |
Crystals / Materials | Fractional | 3 lattice tokens |
2 |
Proteins (Cα backbone) | Cartesian | — |
Generation is a two-stage pipeline:
- Autoencoder (AE) — a flow-matching VAE (
FlowMatchingModel) that encodes a structure into a continuous latent and reconstructs coordinates + atom types. - Latent Diffusion (LDM) — a DiT denoiser (
LatentDiffusionLitModule) trained on the frozen AE latents for unconditional generation, with classifier-free guidance conditioned on the modality.
All modalities share one vocabulary of 140 atom types (120 chemical symbols + 20
amino acids) and are dispatched at runtime by an integer data_type field, so a single
network can be trained on any subset of modalities — including mixed batches.
- Single non-equivariant Transformer — no explicit bonds, no equivariant layers
- One model, three modalities (molecules, crystals, proteins) via
data_typedispatch - Modular flow-matching
Interpolantclasses (Cartesian / torus / lattice / discrete) - Two-stage AE + latent-diffusion training
- Built on the lightning-hydra-template
This repo follows the lightning-hydra-template;
training and model behaviour are configured through Hydra under configs/.
conda env create -f environment.yaml
conda activate mol_vq
# register the `sinae` package (editable)
pip install -e .Datasets are stored as LMDB. On first run, a dataset is built once from a preprocessed
.pt file into an lmdb_* directory under data/ (this can take a while); subsequent
runs load the prebuilt LMDB and its *_stats.yaml. Paths are set per experiment under
configs/experiment/ and configs/datamodule/.
- Molecules (QM9 / GEOM-Drugs) and crystals (MP-20) use preprocessed
.ptsplits placed indata/. - Proteins use Cα-coordinate LMDBs; the joint/protein configs point at the prebuilt
protein LMDB (
/scratch/yuxuan.ren/protein/data/lmdb_pdb, schemacoords_ca+x).
Run all commands from the repository root.
# single modality
python src/train.py experiment=hot_qm9 trainer=gpu # molecules
python src/train.py experiment=hot_mp_20 trainer=gpu # crystals
python src/train.py experiment=hot_protein trainer=gpu # proteins
# joint molecule + crystal (+ protein)
python src/train.py experiment=hot_joint trainer=gpuAvailable AE experiments: hot_qm9, hot_geom, mild_geom, spicy_geom, hot_mp_20,
hot_protein, hot_joint, hot_joint_kl{4,6,8,16,32}.
Trains the DiT denoiser on top of a (frozen) autoencoder checkpoint. The latent
dimension is selected by the diffusion_experiment config (ldm_kl{4,8,16,32}) and the
modality mix by the experiment config.
# joint molecule + crystal + protein latent diffusion
python src/train_diffusion.py experiment=hot_joint_protein_kl8 trainer=gpu
# evaluate a trained LDM checkpoint
python src/test_diffusion.py experiment=hot_joint_protein_kl8 ckpt_path=path/to/ldm.ckptThe denoiser conditions on the modality via dataset_idx (0=null/CFG, 1=molecule,
2=crystal, 3=protein); set num_datasets in the diffusion_experiment config
accordingly (currently 3).
Multi-GPU training uses torchrun; trainer settings live in configs/trainer:
torchrun --nproc_per_node=2 --nnodes=1 src/train.py experiment=hot_joint trainer=ddpNote on mixed batches: items from different modalities are padded to the per-batch maximum sequence length, so co-training short molecules/crystals with long proteins (padded to ~256) is compute-inefficient. Prefer
hot_proteinfor protein-only runs; length-bucketed sampling is a recommended follow-up for large joint runs.
# unconditional sampling
python src/sample.py \
--num_mols 1000 --num_steps 100 \
--checkpoint path/to/model.ckpt \
--output_path path/to/output/folder
# boosted physical plausibility (UFF-bound guidance)
python src/sample_uff_bounds.py \
--guidance 0.01 --step-switch 90 --to-center False \
--ckpt path/to/model.ckpt --output-dir path/to/output/folderCrystal and protein generation are evaluated inside the LDM validation loop; protein generation metrics (e.g. designability) are a work in progress.
configs/ Hydra configs (experiment / model / datamodule / trainer / ...)
src/
train.py Stage-1 autoencoder training
train_diffusion.py Stage-2 latent-diffusion training
test_diffusion.py LDM evaluation
sample*.py Molecule sampling scripts
sinae/
data/ LMDB datasets (mol / crystal / protein) + datamodule
flow/ Flow-matching interpolants & paths
models/ FlowMatchingModel (AE), LatentDiffusionLitModule (LDM), DiT, Transformer
chem/ Atom-type constants & RDKit conversion
callbacks/ utils/ Lightning callbacks and helpers
eval/ Molecule / crystal / MOF generation evaluators
The model treats structure generation as a sequence-modelling problem with a standard
non-equivariant Transformer (see positional encodings
and the Transformer blocks). Coordinates and
atom types are jointly embedded with time and positional encodings; crystals additionally
prepend lattice tokens and proteins reuse the molecule path on Cα coordinates. No explicit
bond information is used — the model relies on producing physically sensible coordinates so
standard tools can infer bonds. The flow-matching logic is unified in a base Interpolant
class with four operations (noise sampling, path creation, loss, Euler step), making it
easy to mix interpolation strategies per field.