mod assignment;
mod dihedral;
mod hbond;
mod patterns;
mod types;
pub use types::{ResidueSSAssignment, SecondaryStructure, SecondaryStructureAssignment};
pub use dihedral::{PPII_ANGLE_TOLERANCE, PPII_PHI_CENTER, PPII_PSI_CENTER};
pub use hbond::HBOND_THRESHOLD;
pub use patterns::BEND_ANGLE_THRESHOLD;
pub use dihedral::{
BackboneDihedrals, PPII_MIN_CONSECUTIVE, calculate_all_dihedrals, calculate_dihedral,
calculate_omega, calculate_phi, calculate_psi,
};
pub use hbond::{
BackboneAtoms, HydrogenBond, calculate_hbond_energy, compute_all_virtual_hydrogens,
detect_hydrogen_bonds, extract_backbone_atoms,
};
use crate::PdbStructure;
impl PdbStructure {
pub fn assign_secondary_structure(&self) -> SecondaryStructureAssignment {
assignment::assign_secondary_structure(&self.atoms)
}
pub fn secondary_structure_string(&self) -> String {
self.assign_secondary_structure().to_string()
}
pub fn secondary_structure_composition(&self) -> (f64, f64, f64) {
self.assign_secondary_structure().composition()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::records::Atom;
fn create_minimal_residue(chain_id: &str, seq: i32, res_name: &str) -> Vec<Atom> {
vec![
Atom {
serial: seq * 4,
name: "N".to_string(),
alt_loc: None,
residue_name: res_name.to_string(),
chain_id: chain_id.to_string(),
residue_seq: seq,
ins_code: None,
is_hetatm: false,
x: (seq as f64) * 3.0,
y: 0.0,
z: 0.0,
occupancy: 1.0,
temp_factor: 20.0,
element: "N".to_string(),
},
Atom {
serial: seq * 4 + 1,
name: "CA".to_string(),
alt_loc: None,
residue_name: res_name.to_string(),
chain_id: chain_id.to_string(),
residue_seq: seq,
ins_code: None,
is_hetatm: false,
x: (seq as f64) * 3.0 + 1.5,
y: 0.0,
z: 0.0,
occupancy: 1.0,
temp_factor: 20.0,
element: "C".to_string(),
},
Atom {
serial: seq * 4 + 2,
name: "C".to_string(),
alt_loc: None,
residue_name: res_name.to_string(),
chain_id: chain_id.to_string(),
residue_seq: seq,
ins_code: None,
is_hetatm: false,
x: (seq as f64) * 3.0 + 2.5,
y: 1.0,
z: 0.0,
occupancy: 1.0,
temp_factor: 20.0,
element: "C".to_string(),
},
Atom {
serial: seq * 4 + 3,
name: "O".to_string(),
alt_loc: None,
residue_name: res_name.to_string(),
chain_id: chain_id.to_string(),
residue_seq: seq,
ins_code: None,
is_hetatm: false,
x: (seq as f64) * 3.0 + 2.5,
y: 2.2,
z: 0.0,
occupancy: 1.0,
temp_factor: 20.0,
element: "O".to_string(),
},
]
}
#[test]
fn test_empty_structure() {
let structure = PdbStructure::new();
let ss = structure.assign_secondary_structure();
assert!(ss.is_empty());
}
#[test]
fn test_secondary_structure_string() {
let mut structure = PdbStructure::new();
for i in 1..=5 {
structure
.atoms
.extend(create_minimal_residue("A", i, "ALA"));
}
let ss_string = structure.secondary_structure_string();
assert_eq!(ss_string.len(), 5);
for c in ss_string.chars() {
assert!("HGIPEBTSC".contains(c), "Invalid SS code: {}", c);
}
}
#[test]
fn test_secondary_structure_composition() {
let mut structure = PdbStructure::new();
for i in 1..=10 {
structure
.atoms
.extend(create_minimal_residue("A", i, "ALA"));
}
let (helix, sheet, coil) = structure.secondary_structure_composition();
assert!((helix + sheet + coil - 1.0).abs() < 0.01);
assert!((0.0..=1.0).contains(&helix));
assert!((0.0..=1.0).contains(&sheet));
assert!((0.0..=1.0).contains(&coil));
}
#[test]
fn test_secondary_structure_codes() {
assert_eq!(SecondaryStructure::AlphaHelix.code(), 'H');
assert_eq!(SecondaryStructure::Helix310.code(), 'G');
assert_eq!(SecondaryStructure::PiHelix.code(), 'I');
assert_eq!(SecondaryStructure::KappaHelix.code(), 'P');
assert_eq!(SecondaryStructure::ExtendedStrand.code(), 'E');
assert_eq!(SecondaryStructure::BetaBridge.code(), 'B');
assert_eq!(SecondaryStructure::Turn.code(), 'T');
assert_eq!(SecondaryStructure::Bend.code(), 'S');
assert_eq!(SecondaryStructure::Coil.code(), 'C');
}
}