mod clash;
mod overlap;
mod radii;
mod report;
pub use clash::AtomClash;
pub use report::LigandPoseReport;
pub use radii::{covalent_radius, vdw_radius};
use crate::core::PdbStructure;
pub const CLASH_VDW_MULTIPLIER: f64 = 0.75;
pub const MAX_VOLUME_OVERLAP_PCT: f64 = 7.5;
pub const VOLUME_VDW_SCALING: f64 = 0.8;
pub const VOLUME_GRID_SPACING: f64 = 0.5;
pub const WATER_RESIDUES: [&str; 4] = ["HOH", "WAT", "H2O", "DOD"];
impl PdbStructure {
pub fn ligand_pose_quality(&self, ligand_name: &str) -> Option<LigandPoseReport> {
report::compute_ligand_pose_report(self, ligand_name)
}
pub fn all_ligand_pose_quality(&self) -> Vec<LigandPoseReport> {
report::compute_all_ligand_reports(self)
}
pub fn get_ligand_names(&self) -> Vec<String> {
use std::collections::HashSet;
let mut ligand_names: HashSet<String> = self
.atoms
.iter()
.filter(|atom| atom.is_hetatm)
.filter(|atom| !WATER_RESIDUES.contains(&atom.residue_name.as_str()))
.map(|atom| atom.residue_name.clone())
.collect();
let mut names: Vec<String> = ligand_names.drain().collect();
names.sort();
names
}
pub fn is_water_residue(residue_name: &str) -> bool {
WATER_RESIDUES.contains(&residue_name)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::records::Atom;
#[allow(clippy::too_many_arguments)]
fn create_test_atom(
serial: i32,
name: &str,
residue_name: &str,
chain_id: &str,
residue_seq: i32,
x: f64,
y: f64,
z: f64,
element: &str,
is_hetatm: bool,
) -> Atom {
Atom {
serial,
name: name.to_string(),
alt_loc: None,
residue_name: residue_name.to_string(),
chain_id: chain_id.to_string(),
residue_seq,
ins_code: None,
is_hetatm,
x,
y,
z,
occupancy: 1.0,
temp_factor: 20.0,
element: element.to_string(),
}
}
fn create_simple_complex() -> PdbStructure {
let mut structure = PdbStructure::new();
structure.atoms.push(create_test_atom(
1, "CA", "ALA", "A", 1, 0.0, 0.0, 0.0, "C", false,
));
structure.atoms.push(create_test_atom(
2, "CB", "ALA", "A", 1, 1.5, 0.0, 0.0, "C", false,
));
structure.atoms.push(create_test_atom(
3, "N", "ALA", "A", 1, -1.5, 0.0, 0.0, "N", false,
));
structure.atoms.push(create_test_atom(
4, "O", "ALA", "A", 1, 0.0, 1.5, 0.0, "O", false,
));
structure.atoms.push(create_test_atom(
5, "C1", "LIG", "A", 100, 5.0, 5.0, 5.0, "C", true,
));
structure.atoms.push(create_test_atom(
6, "O1", "LIG", "A", 100, 6.0, 5.0, 5.0, "O", true,
));
structure.atoms.push(create_test_atom(
7, "N1", "LIG", "A", 100, 5.0, 6.0, 5.0, "N", true,
));
structure.atoms.push(create_test_atom(
8, "O", "HOH", "A", 200, 10.0, 10.0, 10.0, "O", true,
));
structure
}
#[test]
fn test_get_ligand_names() {
let structure = create_simple_complex();
let ligands = structure.get_ligand_names();
assert_eq!(ligands.len(), 1);
assert!(ligands.contains(&"LIG".to_string()));
assert!(!ligands.contains(&"HOH".to_string()));
}
#[test]
fn test_is_water_residue() {
assert!(PdbStructure::is_water_residue("HOH"));
assert!(PdbStructure::is_water_residue("WAT"));
assert!(PdbStructure::is_water_residue("H2O"));
assert!(PdbStructure::is_water_residue("DOD"));
assert!(!PdbStructure::is_water_residue("LIG"));
assert!(!PdbStructure::is_water_residue("ATP"));
}
#[test]
fn test_ligand_pose_quality_good_pose() {
let structure = create_simple_complex();
let report = structure.ligand_pose_quality("LIG");
assert!(report.is_some());
let report = report.unwrap();
assert_eq!(report.ligand_name, "LIG");
assert!(!report.has_protein_clash);
assert_eq!(report.num_clashes, 0);
assert!(report.is_geometry_valid);
}
#[test]
fn test_ligand_pose_quality_nonexistent_ligand() {
let structure = create_simple_complex();
let report = structure.ligand_pose_quality("XYZ");
assert!(report.is_none());
}
#[test]
fn test_ligand_pose_quality_with_clash() {
let mut structure = create_simple_complex();
structure.atoms.push(create_test_atom(
9, "C2", "BAD", "A", 101, 0.5, 0.0, 0.0, "C", true,
));
let report = structure.ligand_pose_quality("BAD");
assert!(report.is_some());
let report = report.unwrap();
assert!(report.has_protein_clash);
assert!(report.num_clashes > 0);
assert!(!report.is_geometry_valid);
}
#[test]
fn test_all_ligand_pose_quality() {
let structure = create_simple_complex();
let reports = structure.all_ligand_pose_quality();
assert_eq!(reports.len(), 1);
assert_eq!(reports[0].ligand_name, "LIG");
}
#[test]
fn test_empty_structure() {
let structure = PdbStructure::new();
let ligands = structure.get_ligand_names();
let reports = structure.all_ligand_pose_quality();
assert!(ligands.is_empty());
assert!(reports.is_empty());
}
}