knuckles_parse/records/
atom.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

#[cfg(feature = "python")]
use knuckles_macro::pydefault;

#[cfg(feature = "python")]
use pyo3::prelude::*;

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
    feature = "python",
    pyclass(get_all, set_all, module = "kncukles_parse")
)]
#[cfg_attr(feature = "python", pydefault)]
/// A record for an atom in a PDB file
pub struct AtomRecord {
    pub serial: u32,
    pub name: String,
    pub alt_loc: Option<char>,
    pub res_name: String,
    pub chain_id: Option<char>,
    pub res_seq: i16,
    pub i_code: Option<char>,
    pub x: f32,
    pub y: f32,
    pub z: f32,
    pub occupancy: f32,
    pub temp_factor: f32,
    pub element: Option<String>,
    pub charge: Option<String>,
    pub entry: Option<String>,
}

impl AtomRecord {
    pub fn new(str: &str) -> AtomRecord {
        let mut radix = 10;
        let serial = str[6..11].trim();
        if serial.chars().any(|c| c.is_ascii_alphabetic()) {
            radix = 16;
        }
        AtomRecord {
            // TODO: add support for parsing serial numbers > 99999
            serial: u32::from_str_radix(serial, radix).unwrap_or_default(),
            name: str[12..16].trim().to_string(),
            alt_loc: str[16..17].trim().parse::<char>().ok(),
            res_name: str[17..20].trim().to_string(),
            chain_id: str[21..22].trim().parse::<char>().ok(),
            res_seq: str[22..26].trim().parse().unwrap(),
            i_code: str[26..27].trim().parse().ok(),
            x: str[30..38].trim().parse().unwrap(),
            y: str[38..46].trim().parse().unwrap(),
            z: str[46..54].trim().parse().unwrap(),
            occupancy: str[54..60].trim().parse().unwrap(),
            temp_factor: str[60..66].trim().parse().unwrap(),
            entry: str
                .get(72..76)
                .map(|str| str.trim().to_string())
                .filter(|item| !item.is_empty()),
            element: str
                .get(77..80)
                .map(|str| str.trim().to_string())
                .filter(|item| !item.is_empty()),
            charge: str
                .get(78..80)
                .map(|str| str.trim().to_string())
                .filter(|item| !item.is_empty()),
        }
    }
}

impl From<&str> for AtomRecord {
    fn from(str: &str) -> Self {
        AtomRecord::new(str)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_atom_line_test() {
        const LINE: &str =
            "ATOM     17  NE2 GLN     2      25.562  32.733   1.806  1.00 19.49      1UBQ    ";
        let record = AtomRecord::new(LINE);
        assert_eq!(record.serial, 17);
        assert_eq!(record.name, "NE2");
        assert_eq!(record.alt_loc, None);
        assert_eq!(record.res_name, "GLN");
        assert_eq!(record.res_seq, 2);
        assert_eq!(record.x, 25.562);
        assert_eq!(record.y, 32.733);
        assert_eq!(record.z, 1.806);
        assert_eq!(record.occupancy, 1.00);
        assert_eq!(record.temp_factor, 19.49);
        assert_eq!(record.entry, Some("1UBQ".to_string()));
        assert_eq!(record.element, None);
        assert_eq!(record.charge, None);
    }

    #[test]
    fn parse_atom_line_hex_test() {
        const LINE: &str =
            "ATOM  186a0  CA  GLY A  67      26.731  62.085   4.078  0.00  7.83           C  ";
        let record = AtomRecord::new(LINE);
        assert_eq!(record.serial, 100000);
        assert_eq!(record.name, "CA");
        assert_eq!(record.alt_loc, None);
        assert_eq!(record.res_name, "GLY");
        assert_eq!(record.res_seq, 67);
        assert_eq!(record.x, 26.731);
        assert_eq!(record.y, 62.085);
        assert_eq!(record.z, 4.078);
        assert_eq!(record.occupancy, 0.00);
        assert_eq!(record.temp_factor, 7.83);
        assert_eq!(record.entry, None);
        assert_eq!(record.element, Some("C".to_string()));
        assert_eq!(record.charge, None);
    }
}