use crate::core::PdbStructure;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct QualityReport {
pub has_ca_only: bool,
pub has_multiple_models: bool,
pub has_altlocs: bool,
pub num_chains: usize,
pub num_models: usize,
pub num_atoms: usize,
pub num_residues: usize,
pub has_hetatm: bool,
pub has_hydrogens: bool,
pub has_ssbonds: bool,
pub has_conect: bool,
}
impl QualityReport {
pub fn is_clean(&self) -> bool {
self.num_atoms > 0 && !self.has_ca_only && !self.has_altlocs
}
pub fn is_analysis_ready(&self) -> bool {
self.num_atoms > 0 && !self.has_multiple_models && !self.has_altlocs
}
}
impl PdbStructure {
pub fn has_ca_only(&self) -> bool {
if self.atoms.is_empty() {
return false;
}
self.atoms.iter().all(|atom| atom.name.trim() == "CA")
}
pub fn has_multiple_models(&self) -> bool {
self.models.len() > 1
}
pub fn num_models(&self) -> usize {
if self.models.is_empty() {
1 } else {
self.models.len()
}
}
pub fn has_altlocs(&self) -> bool {
self.atoms.iter().any(|atom| atom.alt_loc.is_some())
}
pub fn get_altloc_ids(&self) -> Vec<char> {
let mut altlocs: Vec<char> = self.atoms.iter().filter_map(|atom| atom.alt_loc).collect();
altlocs.sort();
altlocs.dedup();
altlocs
}
pub fn has_hetatm(&self) -> bool {
#[cfg(feature = "filter")]
{
use crate::filter::is_standard_residue;
self.atoms
.iter()
.any(|atom| !is_standard_residue(&atom.residue_name))
}
#[cfg(not(feature = "filter"))]
{
const NON_STANDARD: &[&str] = &["HOH", "WAT", "DOD", "H2O", "TIP"];
self.atoms
.iter()
.any(|atom| NON_STANDARD.contains(&atom.residue_name.trim()))
}
}
pub fn has_hydrogens(&self) -> bool {
self.atoms.iter().any(|atom| atom.is_hydrogen())
}
pub fn has_ssbonds(&self) -> bool {
!self.ssbonds.is_empty()
}
pub fn has_conect(&self) -> bool {
!self.connects.is_empty()
}
pub fn count_altloc_atoms(&self) -> usize {
self.atoms
.iter()
.filter(|atom| atom.alt_loc.is_some())
.count()
}
pub fn count_hydrogens(&self) -> usize {
self.atoms.iter().filter(|atom| atom.is_hydrogen()).count()
}
pub fn quality_report(&self) -> QualityReport {
QualityReport {
has_ca_only: self.has_ca_only(),
has_multiple_models: self.has_multiple_models(),
has_altlocs: self.has_altlocs(),
num_chains: self.get_num_chains(),
num_models: self.num_models(),
num_atoms: self.get_num_atoms(),
num_residues: self.get_num_residues(),
has_hetatm: self.has_hetatm(),
has_hydrogens: self.has_hydrogens(),
has_ssbonds: self.has_ssbonds(),
has_conect: self.has_conect(),
}
}
pub fn is_empty(&self) -> bool {
self.atoms.is_empty()
}
pub fn get_resolution(&self) -> Option<f64> {
for remark in &self.remarks {
if remark.number == 2 {
let content = remark.content.to_uppercase();
if content.contains("RESOLUTION") {
for word in content.split_whitespace() {
if let Ok(value) = word.parse::<f64>() {
if value > 0.0 && value < 20.0 {
return Some(value);
}
}
}
}
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::records::{Atom, Model, Remark};
fn create_atom(
serial: i32,
name: &str,
residue_name: &str,
chain_id: &str,
alt_loc: Option<char>,
) -> Atom {
Atom {
serial,
name: name.to_string(),
alt_loc,
residue_name: residue_name.to_string(),
chain_id: chain_id.to_string(),
residue_seq: 1,
ins_code: None,
is_hetatm: false,
x: 0.0,
y: 0.0,
z: 0.0,
occupancy: 1.0,
temp_factor: 20.0,
element: if name.trim().starts_with('H') {
"H".to_string()
} else {
"C".to_string()
},
}
}
#[test]
fn test_has_ca_only_true() {
let mut structure = PdbStructure::new();
structure.atoms = vec![
create_atom(1, " CA ", "ALA", "A", None),
create_atom(2, " CA ", "GLY", "A", None),
create_atom(3, " CA ", "VAL", "A", None),
];
assert!(structure.has_ca_only());
}
#[test]
fn test_has_ca_only_false() {
let mut structure = PdbStructure::new();
structure.atoms = vec![
create_atom(1, " N ", "ALA", "A", None),
create_atom(2, " CA ", "ALA", "A", None),
create_atom(3, " C ", "ALA", "A", None),
];
assert!(!structure.has_ca_only());
}
#[test]
fn test_has_ca_only_empty() {
let structure = PdbStructure::new();
assert!(!structure.has_ca_only());
}
#[test]
fn test_has_multiple_models_true() {
let mut structure = PdbStructure::new();
structure.models = vec![
Model {
serial: 1,
atoms: vec![],
remarks: vec![],
},
Model {
serial: 2,
atoms: vec![],
remarks: vec![],
},
];
assert!(structure.has_multiple_models());
assert_eq!(structure.num_models(), 2);
}
#[test]
fn test_has_multiple_models_false() {
let structure = PdbStructure::new();
assert!(!structure.has_multiple_models());
assert_eq!(structure.num_models(), 1);
}
#[test]
fn test_has_altlocs_true() {
let mut structure = PdbStructure::new();
structure.atoms = vec![
create_atom(1, " CA ", "ALA", "A", Some('A')),
create_atom(2, " CA ", "ALA", "A", Some('B')),
];
assert!(structure.has_altlocs());
}
#[test]
fn test_has_altlocs_false() {
let mut structure = PdbStructure::new();
structure.atoms = vec![
create_atom(1, " CA ", "ALA", "A", None),
create_atom(2, " CA ", "GLY", "A", None),
];
assert!(!structure.has_altlocs());
}
#[test]
fn test_get_altloc_ids() {
let mut structure = PdbStructure::new();
structure.atoms = vec![
create_atom(1, " CA ", "ALA", "A", Some('A')),
create_atom(2, " CA ", "ALA", "A", Some('B')),
create_atom(3, " CB ", "ALA", "A", Some('A')),
];
let altlocs = structure.get_altloc_ids();
assert_eq!(altlocs, vec!['A', 'B']);
}
#[test]
fn test_has_hydrogens() {
let mut structure = PdbStructure::new();
structure.atoms = vec![
create_atom(1, " CA ", "ALA", "A", None),
Atom {
serial: 2,
name: "H ".to_string(), alt_loc: None,
residue_name: "ALA".to_string(),
chain_id: "A".to_string(),
residue_seq: 1,
ins_code: None,
is_hetatm: false,
x: 0.0,
y: 0.0,
z: 0.0,
occupancy: 1.0,
temp_factor: 20.0,
element: "H".to_string(),
},
];
assert!(structure.has_hydrogens());
assert_eq!(structure.count_hydrogens(), 1);
}
#[test]
fn test_has_ssbonds() {
let mut structure = PdbStructure::new();
assert!(!structure.has_ssbonds());
structure.ssbonds = vec![crate::records::SSBond {
serial: 1,
residue1_name: "CYS".to_string(),
chain1_id: "A".to_string(),
residue1_seq: 10,
icode1: None,
residue2_name: "CYS".to_string(),
chain2_id: "A".to_string(),
residue2_seq: 20,
icode2: None,
sym1: 1,
sym2: 1,
length: 2.03,
}];
assert!(structure.has_ssbonds());
}
#[test]
fn test_quality_report() {
let mut structure = PdbStructure::new();
structure.atoms = vec![
create_atom(1, " N ", "ALA", "A", None),
create_atom(2, " CA ", "ALA", "A", None),
create_atom(3, " C ", "ALA", "A", None),
];
let report = structure.quality_report();
assert!(!report.has_ca_only);
assert!(!report.has_multiple_models);
assert!(!report.has_altlocs);
assert_eq!(report.num_chains, 1);
assert_eq!(report.num_models, 1);
assert_eq!(report.num_atoms, 3);
}
#[test]
fn test_quality_report_is_clean() {
let report = QualityReport {
has_ca_only: false,
has_multiple_models: false,
has_altlocs: false,
num_chains: 1,
num_models: 1,
num_atoms: 100,
num_residues: 10,
has_hetatm: false,
has_hydrogens: false,
has_ssbonds: false,
has_conect: false,
};
assert!(report.is_clean());
assert!(report.is_analysis_ready());
}
#[test]
fn test_quality_report_not_clean() {
let report = QualityReport {
has_ca_only: true,
has_multiple_models: false,
has_altlocs: false,
num_chains: 1,
num_models: 1,
num_atoms: 100,
num_residues: 10,
has_hetatm: false,
has_hydrogens: false,
has_ssbonds: false,
has_conect: false,
};
assert!(!report.is_clean()); }
#[test]
fn test_is_empty() {
let structure = PdbStructure::new();
assert!(structure.is_empty());
let mut structure2 = PdbStructure::new();
structure2.atoms = vec![create_atom(1, " CA ", "ALA", "A", None)];
assert!(!structure2.is_empty());
}
#[test]
fn test_get_resolution() {
let mut structure = PdbStructure::new();
structure.remarks = vec![Remark {
number: 2,
content: "RESOLUTION. 2.50 ANGSTROMS.".to_string(),
}];
let resolution = structure.get_resolution();
assert!(resolution.is_some());
assert!((resolution.unwrap() - 2.50).abs() < 0.01);
}
#[test]
fn test_get_resolution_none() {
let structure = PdbStructure::new();
assert!(structure.get_resolution().is_none());
}
#[test]
fn test_count_altloc_atoms() {
let mut structure = PdbStructure::new();
structure.atoms = vec![
create_atom(1, " CA ", "ALA", "A", Some('A')),
create_atom(2, " CA ", "ALA", "A", Some('B')),
create_atom(3, " CB ", "ALA", "A", None),
];
assert_eq!(structure.count_altloc_atoms(), 2);
}
}