AutoDock Vina Molecular Docking
Overview
AutoDock Vina is one of the fastest and most widely used open-source molecular docking engines for predicting protein–ligand binding modes and affinities. This skill covers the full Python-based pipeline: receptor preparation from PDB, ligand preparation from SMILES/SDF via Meeko and RDKit, search box definition, docking execution, pose analysis, and batch virtual screening for hit identification.
When to Use
- Predicting binding poses of small molecules to a protein target
- Estimating relative binding affinities (kcal/mol) for ligand ranking
- Virtual screening of compound libraries against a target receptor
- Validating docking protocols by re-docking co-crystallized ligands
- Preparing docking inputs from SMILES strings without intermediate files
- Comparing binding modes of analogs in a structure-activity relationship study
- Generating starting poses for molecular dynamics simulations
- Use DiffDock instead for blind docking when the binding site is unknown; use GNINA as an alternative with CNN scoring
Prerequisites
- Python packages:
vina,meeko,rdkit,prody(for PDB fetch),py3Dmol(for visualization) - External tools:
ADFR Suite(providesprepare_receptorfor PDBQT conversion) — download from https://ccsb.scripps.edu/adfr/downloads/ - Data requirements: Protein structure (PDB file or PDB ID), ligand(s) as SMILES, SDF, or MOL2
- Environment: Python 3.8+, Linux or macOS recommended
pip install vina meeko rdkit-pypi prody py3Dmol
# ADFR Suite must be installed separately for prepare_receptor
Workflow
Step 1: Fetch and Prepare the Receptor
Download the protein structure, remove water/heteroatoms, and convert to PDBQT format.
import prody
import subprocess
from pathlib import Path
# Download PDB structure (example: HIV-1 protease, PDB 1HPV)
pdb_id = "1HPV"
pdb_file = f"{pdb_id}.pdb"
prody.fetchPDB(pdb_id, compressed=False)
# Extract protein chain only (remove water and ligands)
structure = prody.parsePDB(pdb_file)
protein = structure.select("protein")
prody.writePDB(f"{pdb_id}_protein.pdb", protein)
print(f"Protein atoms: {protein.numAtoms()}")
# Convert to PDBQT using ADFR Suite's prepare_receptor
receptor_pdbqt = f"{pdb_id}_receptor.pdbqt"
subprocess.run([
"prepare_receptor",
"-r", f"{pdb_id}_protein.pdb",
"-o", receptor_pdbqt,
"-A", "hydrogens", # add hydrogens
], check=True)
print(f"Receptor PDBQT: {receptor_pdbqt}")
Step 2: Identify the Binding Site
Define the docking search box centered on the known binding site or co-crystallized ligand.
import numpy as np
# Option A: Center on co-crystallized ligand coordinates
structure = prody.parsePDB(pdb_file)
ligand = structure.select("hetero and not water and not ion")
if ligand is not None:
center = ligand.getCoords().mean(axis=0)
# Box size = ligand extent + padding
extent = ligand.getCoords().max(axis=0) - ligand.getCoords().min(axis=0)
box_size = extent + 10.0 # 10 Å padding on each side
print(f"Binding site center: {center}")
print(f"Box size: {box_size}")
else:
# Option B: Manual coordinates (from literature or visual inspection)
center = np.array([15.0, 54.0, 17.0])
box_size = np.array([25.0, 25.0, 25.0])
print(f"Using manual box: center={center}, size={box_size}")
Step 3: Prepare the Ligand from SMILES
Use RDKit for 3D coordinate generation and Meeko for PDBQT conversion.
from rdkit import Chem
from rdkit.Chem import AllChem
from meeko import MoleculePreparation, PDBQTWriterLegacy
# Define ligand (example: Indinavir, HIV protease inhibitor)
smiles = "CC(C)(C)NC(=O)[C@@H]1CN(CCc2ccccc2)C[C@H]1O"
mol_name = "indinavir_analog"
# Generate 3D coordinates with RDKit
mol = Chem.MolFromSmiles(smiles)
mol = Chem.AddHs(mol)
AllChem.EmbedMolecule(mol, randomSeed=42)
AllChem.MMFFOptimizeMolecule(mol)
# Convert to PDBQT using Meeko
preparator = MoleculePreparation()
mol_setups = preparator.prepare(mol)
# Write PDBQT string (for Vina API) or file
pdbqt_string = PDBQTWriterLegacy.write_string(mol_setups[0])[0]
ligand_pdbqt = f"{mol_name}.pdbqt"
with open(ligand_pdbqt, "w") as f:
f.write(pdbqt_string)
print(f"Ligand PDBQT: {ligand_pdbqt} ({mol.GetNumAtoms()} atoms)")
Step 4: Run Docking
Initialize AutoDock Vina, configure the search space, and execute docking.
from vina import Vina
# Initialize Vina
v = Vina(sf_name="vina", cpu=4)
# Load receptor and ligand
v.set_receptor(receptor_pdbqt)
v.set_ligand_from_file(ligand_pdbqt)
# Define search space
v.compute_vina_maps(
center=center.tolist(),
box_size=box_size.tolist(),
)
# Run docking
v.dock(
exhaustiveness=32, # Higher = more thorough (default 8)
n_poses=10, # Number of output poses
)
# Write output poses
output_file = f"{mol_name}_docked.pdbqt"
v.write_poses(output_file, n_poses=10, overwrite=True)
print(f"Docking complete. Poses written to {output_file}")
Step 5: Analyze Docking Results
Extract binding energies and RMSD values from the docked poses using Vina's built-in API.
# Method A: Vina built-in energies (most reliable)
energies = v.energies(n_poses=10)
# Each row: [total, inter, intra, torsions, intra_best_pose]
print(f"{'Pose':<6} {'Total (kcal/mol)':<18} {'Inter':<10} {'Intra':<10}")
print("-" * 44)
for i, e in enumerate(energies):
print(f"{i+1:<6} {e[0]:<18.2f} {e[1]:<10.2f} {e[2]:<10.2f}")
print(f"\nBest pose: {energies[0][0]:.2f} kcal/mol")
# Method B: Parse PDBQT output with Meeko (for RDKit conversion)
from meeko import PDBQTMolecule, RDKitMolCreate
pdbqt_mol = PDBQTMolecule.from_file(output_file)
best_pose_rdkit = RDKitMolCreate.from_pdbqt_mol(pdbqt_mol)[0]
print(f"Best pose converted to RDKit mol: {best_pose_rdkit.GetNumAtoms()} atoms")
Step 6: Visualize Docking Results
Generate a 3D visualization of the docked complex using py3Dmol.
import py3Dmol
# Load receptor and best docked pose
with open(f"{pdb_id}_protein.pdb") as f:
receptor_pdb = f.read()
with open(output_file) as f:
docked_pdbqt = f.read()
# Create 3D viewer
view = py3Dmol.view(width=800, height=600)
view.addModel(receptor_pdb, "pdb")
view.setStyle({"model": 0}, {"cartoon": {"color": "lightgrey"}})
# Add docked ligand (first model only)
first_model = docked_pdbqt.split("ENDMDL")[0] + "ENDMDL"
view.addModel(first_model, "pdb")
view.setStyle({"model": 1}, {"stick": {"colorscheme": "greenCarbon"}})
# Zoom to ligand
view.zoomTo({"model": 1})
view.show()
# In Jupyter: displays interactive 3D view
# To save: view.png() or view.write_html("docking_result.html")
print("3D visualization rendered")
Step 7: Batch Virtual Screening
Screen a library of compounds against the same receptor.
import pandas as pd
# Define compound library
compounds = pd.DataFrame({
"name": ["cpd_001", "cpd_002", "cpd_003", "cpd_004", "cpd_005"],
"smiles": [
"CC(=O)Oc1ccccc1C(=O)O", # Aspirin
"CC(C)Cc1ccc(cc1)C(C)C(=O)O", # Ibuprofen
"OC(=O)c1ccccc1O", # Salicylic acid
"CC12CCC3C(CCC4CC(=O)CCC34C)C1CCC2O", # Testosterone
"c1ccc2c(c1)cc1ccc3cccc4ccc2c1c34", # Pyrene
],
})
# Screen all compounds
results = []
for _, row in compounds.iterrows():
try:
# Prepare ligand
mol = Chem.MolFromSmiles(row["smiles"])
mol = Chem.AddHs(mol)
AllChem.EmbedMolecule(mol, randomSeed=42)
AllChem.MMFFOptimizeMolecule(mol)
mol_setups = preparator.prepare(mol)
pdbqt_str = PDBQTWriterLegacy.write_string(mol_setups[0])[0]
# Dock
v_screen = Vina(sf_name="vina", cpu=2)
v_screen.set_receptor(receptor_pdbqt)
v_screen.set_ligand_from_string(pdbqt_str)
v_screen.compute_vina_maps(center=center.tolist(), box_size=box_size.tolist())
v_screen.dock(exhaustiveness=16, n_poses=1)