from pathlib import Path
from typing import Optional, Dict, Tuple, List
import logging
[docs]
class VinaDockingEngine:
"""
Vina-specific docking engine implementation using the vina Python interface.
This class provides an interface to AutoDock Vina, a popular molecular docking
program that uses an empirical scoring function based on the AutoDock 4 force
field. Vina is known for its speed and accuracy in structure-based drug design.
Vina features:
- Empirical scoring function (Vina scoring)
- Fast conformational search using iterated local search
- Support for flexible ligand docking
- Automatic binding site detection
- Multiple output poses with binding affinities
Attributes:
work_dir (Path): Directory for docking outputs
receptor_format (str): Expected receptor file format ("pdbqt")
ligand_format (str): Expected ligand file format ("pdbqt")
exhaustiveness (int): Search exhaustiveness parameter
num_modes (int): Number of binding modes to generate
cpu (int): Number of CPU cores to use
seed (int): Random seed for reproducibility
vina: Vina object from the vina Python package
logger (logging.Logger): Logger instance for engine events
"""
[docs]
def __init__(self,
work_dir: str,
exhaustiveness: int = 8,
num_modes: int = 9,
cpu: int = 4,
seed: int = 0):
"""
Initialize Vina docking engine.
Args:
work_dir (str): Directory for docking outputs. Will be created if
it doesn't exist.
exhaustiveness (int, optional): Search exhaustiveness (higher values
give more thorough but slower searches). Defaults to 8.
num_modes (int, optional): Number of binding modes to generate.
Defaults to 9.
cpu (int, optional): Number of CPU cores to use for docking.
Defaults to 4.
seed (int, optional): Random seed for reproducibility. Defaults to 0.
Raises:
ImportError: If vina Python package is not available
ValueError: If invalid parameters are provided
Example:
>>> engine = VinaDockingEngine(
... work_dir='./docking_results',
... box_center=(15.2, 23.1, 18.7),
... box_size=(25.0, 25.0, 25.0),
... exhaustiveness=16,
... num_modes=20
... )
Note:
Requires the vina Python package to be installed:
pip install vina
"""
self.work_dir = Path(work_dir)
self.work_dir.mkdir(parents=True, exist_ok=True)
self.box_center = None
self.box_size = None
self.exhaustiveness = exhaustiveness
self.num_modes = num_modes
self.cpu = cpu
self.seed = seed
self.receptor_format = "pdbqt"
self.ligand_format = "pdbqt"
# Configure logging
self.logger = logging.getLogger(__name__)
self.logger.setLevel(logging.INFO)
try:
from vina import Vina
except ImportError as e:
self.logger.error(f"Failed to import vina package: {e}")
# Initialize Vina object
self.vina = Vina(sf_name='vina', seed=self.seed, cpu=self.cpu)
[docs]
def dock(self,
receptor_file: str,
box_center: Tuple[float, float, float],
ligand_file: str,
box_size: Tuple[float, float, float] = (30.0, 30.0, 30.0),
output_prefix: Optional[str] = None
) -> Dict[str, float]:
"""
Perform docking using Vina.
This method executes AutoDock Vina docking with the specified parameters
and returns results including multiple poses with binding affinities.
Args:
receptor_file (str): Path to prepared receptor file (PDBQT format)
ligand_file (str): Path to prepared ligand file (PDBQT format)
box_center (Tuple[float, float, float]): (x,y,z)
coordinates of docking box center. If None, uses ligand center.
Defaults to None.
box_size (Tuple[float, float, float]): (x,y,z) dimensions
of search box in Angstroms. Defaults to (30.0, 30.0, 30.0).
output_prefix (Optional[str], optional): Prefix for output files.
If None, uses the ligand filename stem. Defaults to None.
Returns:
Dict[str, Any]: Dictionary containing docking results with keys:
- output_file: Path to PDBQT file with docked poses
- log_file: Path to Vina log file with detailed output
- scores: List of dictionaries, each containing scores for one pose:
- pose: Pose number (1-based)
- affinity: Binding affinity in kcal/mol
Raises:
FileNotFoundError: If input files are not found
RuntimeError: If Vina execution fails
ImportError: If vina package is not available
Note:
- Receptor and ligand must be in PDBQT format
- PDBQT format includes atom types, charges, and rotatable bonds
- Box parameters are applied as specified during initialization
- All poses are saved in a single PDBQT file
- Log file contains detailed Vina output and diagnostics
"""
if output_prefix is None:
output_prefix = Path(ligand_file).stem
output_pdbqt = self.work_dir / f"{output_prefix}_docked.pdbqt"
output_log = self.work_dir / f"{output_prefix}_docked.log"
try:
# Load receptor
self.vina.set_receptor(receptor_file)
# Load ligand
self.vina.set_ligand_from_file(ligand_file)
# Set box parameters
if box_center is not None:
self.vina.compute_vina_maps(
center=box_center,
box_size=box_size
)
else:
# If no box center provided, use ligand center
self.logger.info("No box center provided, using ligand center")
self.vina.compute_vina_maps(
center='ligand',
box_size=box_size
)
# Run docking
self.vina.dock(
exhaustiveness=self.exhaustiveness,
n_poses=self.num_modes,
)
# Write output
self.vina.write_poses(str(output_pdbqt), n_poses=self.num_modes, overwrite=True)
# Get scores
scores = self._get_scores()
# self.logger.info(f"Docking completed successfully. Writing output to {output_pdbqt}")
# Write log file with scores
self._write_log(output_log, scores)
return {
"output_file": str(output_pdbqt),
"log_file": str(output_log),
"scores": scores
}
except Exception as e:
self.logger.error(f"Docking failed: {str(e)}")
raise
def _get_scores(self) -> List[Dict[str, float]]:
"""
Get docking scores for all poses.
This method extracts binding affinities from the Vina object for all
generated poses. The scores are returned in order of binding affinity
(best first).
Returns:
List[Dict[str, float]]: List of score dictionaries, one for each pose.
Each dictionary contains:
- pose: Pose number (1-based indexing)
- affinity: Binding affinity in kcal/mol (lower is better)
Note:
- Scores are ordered by binding affinity (best first)
- Affinity values are in kcal/mol (negative values indicate favorable binding)
- Number of poses depends on num_modes parameter
"""
energies = self.vina.energies()
print(energies)
scores = []
for i, energy in enumerate(energies):
scores.append({
"pose": i + 1,
"affinity": energy[0] # Binding affinity in kcal/mol
})
return scores
def _write_log(self, log_file: Path, scores: List[Dict[str, float]]) -> None:
"""
Write docking scores to log file.
This method creates a human-readable log file containing the docking
results and scores for each pose.
Args:
log_file (Path): Path to output log file
scores (List[Dict[str, float]]): List of score dictionaries from _get_scores()
Raises:
IOError: If log file cannot be written
Note:
- Creates a simple text format log file
- Includes pose numbers and binding affinities
- Useful for manual inspection of results
"""
try:
with open(log_file, 'w') as f:
f.write("Vina Docking Results\n")
f.write("-" * 20 + "\n\n")
for score in scores:
f.write(f"Pose {score['pose']} affinity: {score['affinity']:.1f} kcal/mol\n")
except Exception as e:
self.logger.error(f"Failed to write log file: {str(e)}")
[docs]
def precheck(self, file_path: str) -> bool:
"""
Check if the provided file path exists.
This method performs a simple file existence check, which is useful
for validating input files before attempting docking calculations. Runs automatically
before each docking to make sure you have all the files you think you have. It does not check if
those files are correct.
Args:
file_path (str): Path to the file to check
Returns:
bool: True if the file exists, False otherwise
Note:
- Only checks file existence, not file validity
- Does not verify file format or content
- Useful for basic input validation
"""
return Path(file_path).exists()