use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum SecondaryStructure {
AlphaHelix,
Helix310,
PiHelix,
KappaHelix,
ExtendedStrand,
BetaBridge,
Turn,
Bend,
#[default]
Coil,
}
impl SecondaryStructure {
pub fn code(&self) -> char {
match self {
SecondaryStructure::AlphaHelix => 'H',
SecondaryStructure::Helix310 => 'G',
SecondaryStructure::PiHelix => 'I',
SecondaryStructure::KappaHelix => 'P',
SecondaryStructure::ExtendedStrand => 'E',
SecondaryStructure::BetaBridge => 'B',
SecondaryStructure::Turn => 'T',
SecondaryStructure::Bend => 'S',
SecondaryStructure::Coil => 'C',
}
}
pub fn from_code(code: char) -> Option<Self> {
match code {
'H' => Some(SecondaryStructure::AlphaHelix),
'G' => Some(SecondaryStructure::Helix310),
'I' => Some(SecondaryStructure::PiHelix),
'P' => Some(SecondaryStructure::KappaHelix),
'E' => Some(SecondaryStructure::ExtendedStrand),
'B' => Some(SecondaryStructure::BetaBridge),
'T' => Some(SecondaryStructure::Turn),
'S' => Some(SecondaryStructure::Bend),
'C' => Some(SecondaryStructure::Coil),
_ => None,
}
}
pub fn is_helix(&self) -> bool {
matches!(
self,
SecondaryStructure::AlphaHelix
| SecondaryStructure::Helix310
| SecondaryStructure::PiHelix
| SecondaryStructure::KappaHelix
)
}
pub fn is_sheet(&self) -> bool {
matches!(
self,
SecondaryStructure::ExtendedStrand | SecondaryStructure::BetaBridge
)
}
pub fn is_coil(&self) -> bool {
matches!(
self,
SecondaryStructure::Coil | SecondaryStructure::Turn | SecondaryStructure::Bend
)
}
}
impl fmt::Display for SecondaryStructure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.code())
}
}
#[derive(Debug, Clone)]
pub struct ResidueSSAssignment {
pub chain_id: String,
pub residue_seq: i32,
pub residue_name: String,
pub ins_code: Option<char>,
pub ss: SecondaryStructure,
}
impl ResidueSSAssignment {
pub fn new(
chain_id: String,
residue_seq: i32,
residue_name: String,
ins_code: Option<char>,
ss: SecondaryStructure,
) -> Self {
Self {
chain_id,
residue_seq,
residue_name,
ins_code,
ss,
}
}
}
#[derive(Debug, Clone)]
pub struct SecondaryStructureAssignment {
pub residue_assignments: Vec<ResidueSSAssignment>,
pub helix_count: usize,
pub sheet_count: usize,
pub coil_count: usize,
pub helix_fraction: f64,
pub sheet_fraction: f64,
pub coil_fraction: f64,
pub warnings: Vec<String>,
}
impl SecondaryStructureAssignment {
pub fn new() -> Self {
Self {
residue_assignments: Vec::new(),
helix_count: 0,
sheet_count: 0,
coil_count: 0,
helix_fraction: 0.0,
sheet_fraction: 0.0,
coil_fraction: 0.0,
warnings: Vec::new(),
}
}
pub fn as_codes(&self) -> String {
self.residue_assignments
.iter()
.map(|r| r.ss.code())
.collect()
}
pub fn len(&self) -> usize {
self.residue_assignments.len()
}
pub fn is_empty(&self) -> bool {
self.residue_assignments.is_empty()
}
pub fn compute_statistics(&mut self) {
let total = self.residue_assignments.len();
if total == 0 {
return;
}
self.helix_count = self
.residue_assignments
.iter()
.filter(|r| r.ss.is_helix())
.count();
self.sheet_count = self
.residue_assignments
.iter()
.filter(|r| r.ss.is_sheet())
.count();
self.coil_count = self
.residue_assignments
.iter()
.filter(|r| r.ss.is_coil())
.count();
let total_f64 = total as f64;
self.helix_fraction = self.helix_count as f64 / total_f64;
self.sheet_fraction = self.sheet_count as f64 / total_f64;
self.coil_fraction = self.coil_count as f64 / total_f64;
}
pub fn add_warning(&mut self, warning: String) {
self.warnings.push(warning);
}
pub fn composition(&self) -> (f64, f64, f64) {
(self.helix_fraction, self.sheet_fraction, self.coil_fraction)
}
}
impl Default for SecondaryStructureAssignment {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for SecondaryStructureAssignment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_codes())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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');
}
#[test]
fn test_from_code() {
assert_eq!(
SecondaryStructure::from_code('H'),
Some(SecondaryStructure::AlphaHelix)
);
assert_eq!(
SecondaryStructure::from_code('E'),
Some(SecondaryStructure::ExtendedStrand)
);
assert_eq!(
SecondaryStructure::from_code('P'),
Some(SecondaryStructure::KappaHelix)
);
assert_eq!(SecondaryStructure::from_code('X'), None);
}
#[test]
fn test_is_helix() {
assert!(SecondaryStructure::AlphaHelix.is_helix());
assert!(SecondaryStructure::Helix310.is_helix());
assert!(SecondaryStructure::PiHelix.is_helix());
assert!(SecondaryStructure::KappaHelix.is_helix());
assert!(!SecondaryStructure::ExtendedStrand.is_helix());
assert!(!SecondaryStructure::Coil.is_helix());
}
#[test]
fn test_is_sheet() {
assert!(SecondaryStructure::ExtendedStrand.is_sheet());
assert!(SecondaryStructure::BetaBridge.is_sheet());
assert!(!SecondaryStructure::AlphaHelix.is_sheet());
assert!(!SecondaryStructure::Coil.is_sheet());
}
#[test]
fn test_is_coil() {
assert!(SecondaryStructure::Coil.is_coil());
assert!(SecondaryStructure::Turn.is_coil());
assert!(SecondaryStructure::Bend.is_coil());
assert!(!SecondaryStructure::AlphaHelix.is_coil());
assert!(!SecondaryStructure::ExtendedStrand.is_coil());
}
#[test]
fn test_secondary_structure_assignment() {
let mut assignment = SecondaryStructureAssignment::new();
assert!(assignment.is_empty());
assignment
.residue_assignments
.push(ResidueSSAssignment::new(
"A".to_string(),
1,
"ALA".to_string(),
None,
SecondaryStructure::AlphaHelix,
));
assignment
.residue_assignments
.push(ResidueSSAssignment::new(
"A".to_string(),
2,
"GLY".to_string(),
None,
SecondaryStructure::ExtendedStrand,
));
assignment
.residue_assignments
.push(ResidueSSAssignment::new(
"A".to_string(),
3,
"VAL".to_string(),
None,
SecondaryStructure::Coil,
));
assignment.compute_statistics();
assert_eq!(assignment.len(), 3);
assert_eq!(assignment.helix_count, 1);
assert_eq!(assignment.sheet_count, 1);
assert_eq!(assignment.coil_count, 1);
assert!((assignment.helix_fraction - 0.333).abs() < 0.01);
assert_eq!(assignment.as_codes(), "HEC");
}
#[test]
fn test_default() {
assert_eq!(SecondaryStructure::default(), SecondaryStructure::Coil);
}
#[test]
fn test_display() {
let ss = SecondaryStructure::AlphaHelix;
assert_eq!(format!("{}", ss), "H");
}
}