Bond Prediction¶
The EDM produces an atom cloud — element types and 3D coordinates, no bonds. Turning that into a molecule requires a bond-perception step, and MLConfGen supports two strategies:
| Strategy | Implementation | Validity (core model) | Character |
|---|---|---|---|
| ML bond prediction | AdjMatSeer GCN, built into generate_conformers |
~48% | Bond-order distribution learned from ChEMBL; chemically closer to the training set |
| Deterministic bond prediction | OpenBabel bond perception, user-side | ~93% | Rule-based from geometry; higher validity, more generic chemistry |
The ML path is what the library ships and what Molecule Generation describes. This page shows how to plug the deterministic alternative into the pipeline.
Note: OpenBabel is licensed under the GPL-2.0.
mlconfgenis Apache 2.0 and therefore does not depend on or call OpenBabel anywhere in its code. The snippet below is an example for your own environment; by installing OpenBabel you accept its license terms for your project.
1. Why Two Strategies¶
AdjMatSeer is trained to reproduce ChEMBL bonding patterns from distances, so the molecules it produces have a chemical-feature distribution close to drug-like compounds — at the cost of a lower validity rate, since a single wrong bond order fails sanitisation. OpenBabel's perception (ConnectTheDots + PerceiveBondOrders) is purely geometric and fails far less often, but it does not know what typical chemistry looks like.
The distilled AdjMatSeer (small_adj_mat_seer_obabel_15_39.pt / .onnx) was trained to imitate OpenBabel's labels and reaches ~71% validity. It is the way to get OpenBabel-grade bonds in environments where OpenBabel cannot run — the ONNX backend and the JavaScript package.
2. Installation¶
pip install openbabel-wheel # pre-built wheels
# or
conda install -c conda-forge openbabel
3. Example: Deterministic Bond Prediction¶
The EDM stage is called directly (edm_samples), OpenBabel assigns the bonds, and the library's own standardize_mol finishes the job exactly as it would after AdjMatSeer.
from openbabel import openbabel
from rdkit import Chem
from mlconfgen import MLConformerGenerator
from mlconfgen.utils import standardize_mol
ob.obErrorLog.SetOutputLevel(0) # silence OpenBabel warnings
BOND_TYPES = {1: Chem.BondType.SINGLE, 2: Chem.BondType.DOUBLE, 3: Chem.BondType.TRIPLE}
def guess_bonds_openbabel(mol: Chem.Mol) -> Chem.Mol:
ob_conv = openbabel.OBConversion()
ob_conv.SetInAndOutFormats("xyz", "mol")
obmol = openbabel.OBMol()
xyz_block = Chem.MolToXYZBlock(mol)
ob_conv.ReadString(obmol, xyz_block)
obmol.ConnectTheDots()
obmol.PerceiveBondOrders()
mol_block = ob_conv.WriteString(obmol)
raw_mol = Chem.MolFromMolBlock(mol_block)
if raw_mol:
out_mol = strip_mol(raw_mol)
else:
out_mol = None
model = MLConformerGenerator(diffusion_steps=100)
reference = Chem.MolFromMolFile("./assets/demo_files/ceyyag.mol")
# Stage 1: atom clouds from the EDM
ref_context, ref_n_atoms, _ = model.prepare_inputs(reference_conformer=reference)
edm_mols = model.edm_samples(
reference_context=ref_context,
n_samples=50,
min_n_nodes=ref_n_atoms - 2,
max_n_nodes=ref_n_atoms + 2,
)
# Stage 2: deterministic bonds instead of AdjMatSeer
bonded = [guess_bonds_openbabel(m) for m in edm_mols]
# Stage 3: the library's standardisation (largest fragment, valence, kekulisation, MMFF94)
samples = [
std for m in bonded
if m is not None and (std := standardize_mol(m, optimize_geometry=True)) is not None
]
print(f"{len(samples)} / {len(edm_mols)} valid")
Everything downstream — evaluate_samples, RL scoring functions, export — accepts these molecules like any other output of generate_conformers.
Notes on the example¶
- Fixed fragments. Atom clouds from inpainting (
edm_samples(..., fixed_fragment=...)) are processed the same way — only the geometry is used. - ONNX backend.
MLConformerGeneratorONNX.edm_samplesreturns the same kind of bond-less molecules, so the snippet works unchanged withMLConformerGeneratorONNXin place ofMLConformerGenerator.
4. Choosing a Strategy¶
- Prefer AdjMatSeer when chemical plausibility relative to ChEMBL matters (lead-like libraries, downstream chemical-similarity scoring) and you can afford to over-sample.
- Prefer OpenBabel when throughput of valid molecules is the goal, when geometry is the only thing you trust, or when comparing against the 93% figure on the Performance page.
- Prefer the distilled AdjMatSeer when you want OpenBabel-like validity without a GPL dependency or in ONNX / browser deployments.
- Fine-tuning checkpoints update the AdjMatSeer head; with a checkpoint loaded, use the ML path so the fine-tuned bond predictor is applied.