use crate::error::PdbError;
use crate::records::{Atom, Conect, Model, Remark, SSBond, SeqRes};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct PdbStructure {
pub atoms: Vec<Atom>,
pub header: Option<String>,
pub title: Option<String>,
pub seqres: Vec<SeqRes>,
pub connects: Vec<Conect>,
pub ssbonds: Vec<SSBond>,
pub remarks: Vec<Remark>,
pub models: Vec<Model>,
pub current_model: Option<i32>,
}
impl PdbStructure {
pub fn new() -> Self {
Self {
atoms: Vec::new(),
header: None,
title: None,
seqres: Vec::new(),
connects: Vec::new(),
ssbonds: Vec::new(),
remarks: Vec::new(),
models: Vec::new(),
current_model: None,
}
}
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, PdbError> {
crate::parser::parse_pdb_file(path)
}
pub fn get_num_atoms(&self) -> usize {
self.atoms.len()
}
pub fn get_num_chains(&self) -> usize {
let mut chain_ids = std::collections::HashSet::new();
for atom in &self.atoms {
chain_ids.insert(atom.chain_id.clone());
}
chain_ids.len()
}
pub fn get_chain_ids(&self) -> Vec<String> {
let mut chain_ids = std::collections::HashSet::new();
for atom in &self.atoms {
chain_ids.insert(atom.chain_id.clone());
}
let mut chain_ids: Vec<String> = chain_ids.into_iter().collect();
chain_ids.sort();
chain_ids
}
pub fn get_num_residues(&self) -> usize {
let mut residues = std::collections::HashSet::new();
for atom in &self.atoms {
residues.insert((atom.residue_seq, atom.residue_name.clone()));
}
residues.len()
}
pub fn get_residues(&self) -> Vec<(i32, String)> {
let mut residues = std::collections::HashSet::new();
for atom in &self.atoms {
residues.insert((atom.residue_seq, atom.residue_name.clone()));
}
let mut residues: Vec<(i32, String)> = residues.into_iter().collect();
residues.sort_by_key(|&(num, _)| num);
residues
}
pub fn get_residues_for_chain(&self, chain_id: &str) -> Vec<(i32, String)> {
let mut residues = std::collections::HashSet::new();
for atom in &self.atoms {
if atom.chain_id == chain_id {
residues.insert((atom.residue_seq, atom.residue_name.clone()));
}
}
let mut residues: Vec<(i32, String)> = residues.into_iter().collect();
residues.sort_by_key(|&(num, _)| num);
residues
}
pub fn get_sequence(&self, chain_id: &str) -> Vec<String> {
let mut sequence = Vec::new();
for seqres in &self.seqres {
if seqres.chain_id == chain_id {
sequence.extend(seqres.residues.clone());
}
}
sequence
}
pub fn get_remarks_by_number(&self, number: i32) -> Vec<&Remark> {
self.remarks.iter().filter(|r| r.number == number).collect()
}
pub fn get_connected_atoms(&self, atom_serial: i32) -> Vec<&Atom> {
let mut connected = Vec::new();
for conect in &self.connects {
if conect.atom1 == atom_serial {
if let Some(atom) = self.atoms.iter().find(|a| a.serial == conect.atom2) {
connected.push(atom);
}
}
if conect.atom2 == atom_serial {
if let Some(atom) = self.atoms.iter().find(|a| a.serial == conect.atom1) {
connected.push(atom);
}
}
}
connected
}
pub fn translate(&mut self, dx: f64, dy: f64, dz: f64) {
for atom in &mut self.atoms {
atom.x += dx;
atom.y += dy;
atom.z += dz;
}
}
pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), PdbError> {
crate::writer::write_pdb_file(self, path)
}
}
impl Default for PdbStructure {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::records::{Atom, Conect, Remark, SSBond, SeqRes};
fn create_test_structure() -> PdbStructure {
let mut structure = PdbStructure::new();
structure.atoms = vec![
Atom {
serial: 1,
name: "CA".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: "C".to_string(),
},
Atom {
serial: 2,
name: "CA".to_string(),
alt_loc: None,
residue_name: "GLY".to_string(),
chain_id: "A".to_string(),
residue_seq: 2,
ins_code: None,
is_hetatm: false,
x: 1.0,
y: 1.0,
z: 1.0,
occupancy: 1.0,
temp_factor: 20.0,
element: "C".to_string(),
},
];
structure.connects = vec![Conect {
atom1: 1,
atom2: 2,
atom3: None,
atom4: None,
}];
structure.seqres = vec![SeqRes {
serial: 1,
chain_id: "A".to_string(),
num_residues: 2,
residues: vec!["ALA".to_string(), "GLY".to_string()],
}];
structure.remarks = vec![Remark {
number: 2,
content: "RESOLUTION. 2.0 ANGSTROMS.".to_string(),
}];
structure.ssbonds = vec![SSBond {
serial: 1,
residue1_name: "CYS".to_string(),
chain1_id: "A".to_string(),
residue1_seq: 1,
icode1: None,
residue2_name: "CYS".to_string(),
chain2_id: "A".to_string(),
residue2_seq: 2,
icode2: None,
sym1: 1,
sym2: 1,
length: 2.0,
}];
structure
}
#[test]
fn test_new_structure() {
let structure = PdbStructure::new();
assert!(structure.atoms.is_empty());
assert!(structure.header.is_none());
assert!(structure.title.is_none());
assert!(structure.seqres.is_empty());
assert!(structure.connects.is_empty());
assert!(structure.ssbonds.is_empty());
assert!(structure.remarks.is_empty());
assert!(structure.models.is_empty());
assert!(structure.current_model.is_none());
}
#[test]
fn test_get_chain_ids() {
let structure = create_test_structure();
let chain_ids = structure.get_chain_ids();
assert_eq!(chain_ids, vec!["A"]);
}
#[test]
fn test_get_residues_for_chain() {
let structure = create_test_structure();
let residues = structure.get_residues_for_chain("A");
assert_eq!(residues.len(), 2);
assert_eq!(residues[0], (1, "ALA".to_string()));
assert_eq!(residues[1], (2, "GLY".to_string()));
}
#[test]
fn test_get_sequence() {
let structure = create_test_structure();
let sequence = structure.get_sequence("A");
assert_eq!(sequence, vec!["ALA", "GLY"]);
}
#[test]
fn test_get_remarks_by_number() {
let structure = create_test_structure();
let remarks = structure.get_remarks_by_number(2);
assert_eq!(remarks.len(), 1);
assert!(remarks[0].content.contains("RESOLUTION"));
}
#[test]
fn test_get_connected_atoms() {
let structure = create_test_structure();
let connected = structure.get_connected_atoms(1);
assert_eq!(connected.len(), 1);
assert_eq!(connected[0].serial, 2);
}
#[test]
fn test_translate() {
let mut structure = create_test_structure();
let original_x = structure.atoms[0].x;
let original_y = structure.atoms[0].y;
let original_z = structure.atoms[0].z;
structure.translate(1.0, 2.0, 3.0);
assert_eq!(structure.atoms[0].x, original_x + 1.0);
assert_eq!(structure.atoms[0].y, original_y + 2.0);
assert_eq!(structure.atoms[0].z, original_z + 3.0);
}
#[test]
fn test_default() {
let structure = PdbStructure::default();
assert!(structure.atoms.is_empty());
assert!(structure.header.is_none());
assert!(structure.title.is_none());
assert!(structure.seqres.is_empty());
assert!(structure.connects.is_empty());
assert!(structure.ssbonds.is_empty());
assert!(structure.remarks.is_empty());
assert!(structure.models.is_empty());
assert!(structure.current_model.is_none());
}
#[test]
fn test_clone() {
let structure = create_test_structure();
let cloned = structure.clone();
assert_eq!(structure.atoms.len(), cloned.atoms.len());
assert_eq!(structure.connects.len(), cloned.connects.len());
assert_eq!(structure.seqres.len(), cloned.seqres.len());
assert_eq!(structure.remarks.len(), cloned.remarks.len());
assert_eq!(structure.ssbonds.len(), cloned.ssbonds.len());
let mut structure = structure;
structure.atoms[0].x = 100.0;
assert_ne!(structure.atoms[0].x, cloned.atoms[0].x);
}
#[test]
fn test_debug() {
let structure = create_test_structure();
let debug_string = format!("{:?}", structure);
assert!(debug_string.contains("atoms"));
assert!(debug_string.contains("connects"));
assert!(debug_string.contains("seqres"));
assert!(debug_string.contains("remarks"));
assert!(debug_string.contains("ssbonds"));
}
}