Tutorial 9: Analyse Module - 3D Molecular Structure Analysis¶
Prerequisites: Tutorial 5 — Generation Overview · You’ll learn: optimising, validating, comparing, and featurising generated structures · Next: Tutorial 10 — Generation Sweeps
At a Glance¶
Objective |
Validate and characterise generated 3D molecular structures. |
You need |
Generated XYZ files or an ASE database, plus optional analysis dependencies for the selected command. |
Main command |
|
Success looks like |
The requested structures, metrics, tables, or feature arrays are written without silently skipping the input set. |
This tutorial covers the analyse command group, which provides tools for post-generation analysis and validation of 3D molecular structures. The CLI spelling remains MolCraftDiff analyze.
Overview¶
The analysis command group includes five subcommands:
Command |
Description |
|---|---|
|
XTB geometry optimisation |
|
Validity/connectivity metrics, plus the paired |
|
XYZ to SMILES + fingerprints |
|
XTB electronic properties |
|
Fixed-size molecular feature vectors (SOAP / UMA / SSL3D) |
Access the CLI with:
MolCraftDiff analyze --help
Part 1: XTB Geometry Optimisation¶
Optimise generated structures using xTB (GFN1, GFN2, GFN-FF) or MMFF94.
Usage¶
MolCraftDiff analyze optimize gen_xyz/ --level gfn2 --charge 0
Options¶
Option |
Default |
Description |
|---|---|---|
|
|
Output directory |
|
|
Optimisation level: gfn1, gfn2, gfn-ff, mmff94 |
|
|
Molecular charge |
|
|
Timeout per molecule (seconds) |
|
|
Covalent radii scale factor |
Output¶
Optimised XYZ files are saved to output_dir/, each with an _opt suffix (e.g. mol_0000.xyz → mol_0000_opt.xyz). Part 3’s --metrics conformer expects exactly these *_opt.xyz files.
Part 2: Validity & Connectivity Metrics¶
Compute structural validation metrics for generated molecules.
Usage¶
MolCraftDiff analyze metrics gen_xyz/ --metrics all
The input may be a directory of .xyz files or an ASE .db file. For a
database, every row is scored and the results carry an ase_db_row_id column
so rows can be joined back to the source database.
Metric Types¶
Type |
Description |
|---|---|
|
Validity, connectivity, atom stability, and set-level uniqueness/novelty/diversity |
|
Bond lengths, angles, clashes |
|
Aromatic-aware stability metrics |
|
QED, SA, LogP, fsp3, MW, HBD, HBA, Lipinski, PAINS, ring statistics (+ optional |
|
Shape / ESP / pharmacophore similarity to |
|
AutoDock Vina binding affinity against a protein pocket (needs |
|
All of the above except |
What core reports¶
Two validity columns are written per molecule, because they answer different questions:
Column |
Meaning |
|---|---|
|
The perceived molecule sanitises in RDKit and its SMILES round-trips. This is the definition used throughout the literature, so it is the number to quote in comparisons. |
|
Stricter, geometry-aware check: bond counts, bond lengths, and the coordination-shape measure of every atom. Use it to catch physically distorted structures that still sanitise. |
|
|
Note: before this release
validheld what is nowvalid_geom. If you have a sweep config or script that depends on the old, stricter number, point it atvalid_geom.
Alongside the per-molecule CSV, core writes a <output>_summary.json with
the aggregate rates (validity, connectivity, atom stability, uniqueness,
novelty, diversity, the molecular-size statistics, and how many files failed
to process). Sweeps pick this file up automatically.
Options¶
Option |
Default |
Description |
|---|---|---|
|
None |
Output CSV file |
|
|
Metric type to compute |
|
False |
Recheck topology using RDKit |
|
|
XYZ to mol converter |
|
|
Deterministic splits for mean±std summary logging |
|
|
Fraction of XYZ files to process |
|
None |
Keep only structures whose column is truthy (e.g. |
|
None |
Reference SMILES ( |
|
None |
Protein receptor for |
|
None |
Reference ligand |
|
|
|
|
|
|
|
|
|
|
|
Vina search effort for |
|
None / |
Reference |
|
off / |
|
|
— |
Atom indices to skip in validation |
|
|
Timeout per xyz2mol conversion (s) |
What druglike and similarity3d report¶
These two were one set called shepherd until they were split, because they
answer different questions and need different inputs.
druglike needs nothing but the structures:
Column |
Meaning |
|---|---|
|
standard RDKit descriptors |
|
how many of the five Lipinski rules the molecule obeys, 0–5 |
|
free of PAINS-A alerts |
|
no non-aromatic ring larger than 6 |
|
ring counts |
|
per molecule a boolean; the summary reports the fraction of molecules containing each size, which is the form the SBDD papers print |
|
only with |
similarity3d needs --reference-mol and reports shape_sim, esp_sim and
pharm_sim — Gaussian-overlap scores after alignment.
Reproducibility:
shape_simandesp_simare not deterministic. The molecular surface is sampled randomly and the overlay uses 45 random restarts, so re-running the same input moves them by up to ~0.05. Compare them within a run, and treat small differences between runs as noise.pharm_simis deterministic.
Migration:
--metrics shepherdno longer exists. Use--metrics druglikefor the descriptors and--metrics similarity3dfor the reference comparison.--metrics allrunsdruglikealways, andsimilarity3donly when--reference-molis given.
What sbdd reports¶
Binding affinity for pocket-conditioned models, the numbers the SBDD
literature quotes. Requires the [sbdd] extra (pip install 'molcraftdiffusion[sbdd]').
MolCraftDiff analyze metrics gen_xyz/ --metrics sbdd \
--receptor pocket.pdbqt --ref-ligand reference.sdf
Column / metric |
Meaning |
|---|---|
|
Affinity of the generated pose exactly as produced, no relaxation |
|
After local minimisation in the pocket |
|
After a full redock — the number most papers report |
|
Fraction of molecules beating |
|
QED > 0.25 ∧ SA > 0.59 ∧ |
The search box is centred on each molecule’s own bounding box plus a 5 Å buffer, matching the reference implementations — generated ligands already sit in the pocket frame.
Two things worth knowing:
Poses are perceived with OpenBabel, not xyz2mol. xyz2mol returns a topology with no conformer, which would silently discard the coordinates the model generated. Structures that cannot be perceived, that lose their conformer, or that come back as several fragments are reported with an
errorand no affinity rather than being skipped silently — checkn_scoredin the summary against your input count.sbddis not part of--metrics all, because it needs a receptor and costs seconds per molecule (--dock-mode scoreis far cheaper thandock).
Part 3: Conformer Metrics (paired generated-vs-reference)¶
--metrics conformer is the home for every metric that needs a pair of
structures — a generated one and the reference it should correspond to. It
replaces the old analyze compare command and the removed --check-strain
flag.
It is exclusive: it is not part of --metrics all, because a plain
directory of samples has no reference to pair against.
Two accepted input layouts¶
1. SDF pairs — what conformer generation writes:
generated_conformers/
conformers.csv
mol_0000/{conformers.sdf, reference.sdf, conformer_000.xyz, ...}
MolCraftDiff generate my_conformer_config.yaml
MolCraftDiff analyze metrics generated_conformers/ --metrics conformer
Both molecules of a pair are built from the same molecular graph, so atom and bond ordering match — which is what makes stereochemistry comparable. This layout is never routed through xyz2mol/OpenBabel: re-perceiving bonds from coordinates would destroy the stereochemistry being measured.
2. xyz + optimised — the layout Part 1 produces:
MolCraftDiff analyze optimize gen_xyz/
MolCraftDiff analyze metrics gen_xyz/ --metrics conformer
Here each structure is perceived from coordinates independently, so atom ordering is not guaranteed to agree and stereo columns are not emitted (the run logs the reason).
Computed metrics¶
Column |
Meaning |
|---|---|
|
Stereochemistry preserved vs the reference (layout 1 only). All-or-nothing per molecule; the reference’s full enantiomer counts as correct, matching the convention the conformer literature reports. |
|
Read from |
|
Mean paired deviation, weighted across bond/angle/torsion types |
|
MMFF94 energy drop on relaxation — always available |
|
Same at the xTB level. Empty with a warning when the |
Output¶
conformer_metrics.csv (one row per conformer) plus a
conformer_metrics_summary.json with rs_score, ez_score, n_rs_scored,
n_skipped, rmsd_median, rmsd_best_per_mol_mean,
coverage_at_threshold, and the geometry / strain means.
Part 4: XYZ to SMILES Conversion¶
Convert 3D XYZ files to 2D SMILES and extract molecular fingerprints.
Usage¶
MolCraftDiff analyze xyz2mol gen_xyz/ --bits 2048
Output Files (in xyz_dir/2d_reprs/)¶
File |
Description |
|---|---|
|
Filename → SMILES mapping |
|
Morgan fingerprints array |
|
Murcko scaffolds |
|
Substructure counts |
Part 5: XTB Electronic Properties¶
Compute quantum-chemical descriptors at GFN-xTB level using morfeus.
Usage¶
# Basic energy properties
MolCraftDiff analyze xtb-electronic gen_xyz/ -p energy
# All properties with JSON output
MolCraftDiff analyze xtb-electronic gen_xyz/ -p all -f json -o results.json
# ASE database for downstream analysis
MolCraftDiff analyze xtb-electronic gen_xyz/ -p all -f ase -o results.db
Property Groups¶
Molecular-level:
Group |
Properties |
|---|---|
|
HOMO, LUMO, HOMO-LUMO gap |
|
Dipole vector and magnitude |
|
Ionisation potential, electron affinity |
|
Electrophilicity, nucleophilicity, fugalities |
|
Solvation energy, H-bond correction (requires |
Atomic-level:
Group |
Properties |
|---|---|
|
Mulliken atomic charges |
|
Fukui indices (f⁺, f⁻, radical, dual) |
|
Wiberg bond orders |
Output Formats¶
Format |
Description |
|---|---|
|
Molecular-level properties (one row per molecule) |
|
Full data including atomic-level properties |
|
ASE database with properties in atoms.info/arrays |
|
Generate all three formats |
Options¶
Option |
Default |
Description |
|---|---|---|
|
|
XTB method: 1=GFN1, 2=GFN2, ptb=PTB |
|
|
Molecular charge |
|
|
Number of unpaired electrons |
|
False |
For PTB neutral singlets with odd electrons, infer ±1 charge from the XYZ |
|
None |
Solvent for the |
|
|
Property groups to compute |
|
|
Output format |
|
True |
Apply empirical IP/EA correction |
|
False |
For ASE |
|
|
Parallel jobs |
|
|
Timeout per molecule (s) |
Part 6: Featurise — Fixed-Size Molecular Vectors¶
Convert a directory of XYZ files into a fixed-size feature matrix for downstream machine learning (clustering, regression, dimensionality reduction, etc.).
Three backends are available:
Backend |
Description |
GPU needed |
|---|---|---|
|
SOAP descriptor via dscribe |
No |
|
UMA backbone embeddings from pretrained fairchem model |
Optional |
|
Embeddings from a trained SSL3D checkpoint |
Optional |
Usage¶
# SOAP (default) — uses the built-in species list
MolCraftDiff analyze featurize gen_xyz/
# SOAP — auto-detect species from the files
MolCraftDiff analyze featurize gen_xyz/ --autodetect
# SOAP — specify species explicitly
MolCraftDiff analyze featurize gen_xyz/ --species C --species H --species N --species O
# SOAP — custom descriptor parameters
MolCraftDiff analyze featurize gen_xyz/ --n-max 12 --l-max 9 --r-cut 8.0
# UMA — CPU inference
MolCraftDiff analyze featurize gen_xyz/ --backend uma --device cpu
# UMA — GPU inference with custom checkpoint
MolCraftDiff analyze featurize gen_xyz/ --backend uma --device cuda \
--checkpoint training_outputs/uma-s-1p2.pt
# UMA — all spherical components (higher-dimensional embedding)
MolCraftDiff analyze featurize gen_xyz/ --backend uma --all-components
# SSL3D — use a trained SSL3D checkpoint
MolCraftDiff analyze featurize gen_xyz/ --backend ssl3d \
--ssl3d-checkpoint runs/last.ckpt --device cuda
SOAP Options¶
Option |
Default |
Description |
|---|---|---|
|
False |
Detect element species from files (overrides |
|
See below |
Element symbols; repeatable: |
|
|
Cutoff radius in Å |
|
|
Radial basis functions |
|
|
Angular basis functions |
|
|
Gaussian smearing width |
|
|
Atom pooling mode: |
|
|
Parallel workers |
Default species list:
H B C N O F Al Si P S Cl As Se Br I Hg Bi
Elements found in the files that are not in the species list are added automatically with a warning, so the run never fails silently on unseen atoms.
UMA Options¶
Option |
Default |
Description |
|---|---|---|
|
|
Path to UMA checkpoint |
|
|
UMA task name |
|
auto |
|
|
|
Molecules per UMA forward pass |
|
|
Total molecular charge applied to all structures |
|
|
Spin multiplicity applied to all structures |
|
False |
Use all spherical components instead of L=0 scalars only |
|
|
Atom pooling mode: |
The UMA backend requires the vendored fairchem source tree at <repo_root>/fairchem/src.
If it is not found, clone it with:
git clone https://github.com/pregHosh/fairchem fairchem
Then run from the repository root, or set:
export MOLCRAFT_REPO_ROOT=/path/to/MolCraftDiffusion
SSL3D Options¶
Option |
Default |
Description |
|---|---|---|
|
None |
Required path to a trained SSL3D |
|
|
Radius graph cutoff in Å |
|
auto |
|
|
|
Molecules per SSL3D forward pass |
|
|
Atom pooling mode: |
Output Files¶
Three files are written to the output stem (default: input_dir/features):
File |
Content |
|---|---|
|
|
|
Row index → source file + frame mapping |
|
Backend, all parameters, feature dim, timestamp |
# Custom output stem
MolCraftDiff analyze featurize gen_xyz/ -o results/soap_features
# writes: results/soap_features.npy / .csv / _meta.json
Loading the Output¶
import numpy as np
import pandas as pd
features = np.load("gen_xyz/features.npy") # (N, D) float32
index = pd.read_csv("gen_xyz/features.csv") # maps row → file/frame
print(features.shape) # e.g. (160, 81396) for SOAP, (160, 128) for UMA
Python API¶
from MolecularDiffusion.runmodes.analyze.featurize import run_featurize
# SOAP
features = run_featurize(
input_dir="gen_xyz/",
backend="soap",
output_path="gen_xyz/soap_features",
n_max=12,
l_max=9,
)
# UMA — pass a pre-loaded list of ASE Atoms directly
from MolecularDiffusion.runmodes.analyze.uma_embeddings import get_uma_molecule_embeddings
from ase.io import read
atoms_list = [read("gen_xyz/molecule_0000.xyz"), read("gen_xyz/molecule_0001.xyz")]
results = get_uma_molecule_embeddings(
source=atoms_list,
checkpoint_path="training_outputs/uma-s-1p2.pt",
device="cpu",
charge=0,
spin=1,
)
mol_emb = results[0]["molecule_embedding"] # torch.Tensor, shape (128,)
node_emb = results[0]["node_embedding"] # torch.Tensor, shape (n_atoms, 128)
Example Workflow¶
A typical post-generation analysis workflow:
# 1. Generate molecules
MolCraftDiff generate gen_config.yaml
# 2. Optimise geometries
MolCraftDiff analyze optimize gen_xyz/ -l gfn2 -o gen_xyz/optimized_xyz
# 3. Compute validity metrics
MolCraftDiff analyze metrics gen_xyz/optimized_xyz -o metrics.csv
# 4. Paired generated-vs-optimised metrics
MolCraftDiff analyze metrics gen_xyz/ --metrics conformer
# 5. Convert to SMILES for downstream analysis
MolCraftDiff analyze xyz2mol gen_xyz/optimized_xyz
# 6. Compute electronic properties
MolCraftDiff analyze xtb-electronic gen_xyz/optimized_xyz -p all -f ase -o electronic.db
# 7. Featurise for downstream ML (SOAP)
MolCraftDiff analyze featurize gen_xyz/optimized_xyz -o gen_xyz/soap_features
# 7b. Or use UMA embeddings (requires fairchem checkout + checkpoint)
MolCraftDiff analyze featurize gen_xyz/optimized_xyz --backend uma --device cuda \
-o gen_xyz/uma_features
# 7c. Or use SSL3D embeddings from a trained SSL3D checkpoint
MolCraftDiff analyze featurize gen_xyz/optimized_xyz --backend ssl3d \
--ssl3d-checkpoint runs/last.ckpt -o gen_xyz/ssl3d_features
Python API¶
The analysis functions are also available programmatically:
from MolecularDiffusion.runmodes.analyze import (
optimize_molecule,
get_xtb_optimized_xyz,
compute_xtb_electronic,
batch_xtb_electronic,
run_xyz2mol,
)
from MolecularDiffusion.runmodes.analyze.conformer_metrics import (
compute_conformer_metrics,
)
from MolecularDiffusion.runmodes.analyze.featurize import run_featurize
from MolecularDiffusion.runmodes.analyze.uma_embeddings import get_uma_molecule_embeddings
# Compute electronic properties for single file
result = compute_xtb_electronic(
"molecule.xyz",
method=2,
properties=["energy", "charges"]
)
print(result["homo"], result["lumo"])
# Batch processing
df = batch_xtb_electronic(
input_dir="gen_xyz/",
output_path="results.csv",
output_format="csv",
properties=["energy", "reactivity"],
)
Verify the Result¶
For every analysis command, compare the reported processed count with the number of eligible input structures. Inspect missing values and per-file failures before using aggregate metrics or feature arrays downstream.
Troubleshooting¶
Use
MolCraftDiff analyze <subcommand> --helpto confirm optional dependencies and accepted formats.Conversion failures often indicate unsupported elements, invalid coordinates, or disconnected structures.
xTB-backed commands require the xTB executable to be available in the active environment.