protein_core/structure/atom.rs
1use super::AtomSerial;
2use super::{Element, Residue};
3use serde::Serialize;
4use std::str::FromStr;
5
6/// Represents an atom in a structural model.
7///
8/// # Field
9///
10/// - `id`: Unique identifier. No two atoms. No two `Atom`s in the same `Model` can have the same `id`.
11/// - `name`: Atom name. This represents the exact "role" of an atom in a given residue.
12/// - `residue`: The [`Residue`] this atom belongs to. This can be an amino acid, a nucleotide, water, or a custom molecule.
13/// - `chain`: The chain identifier
14/// - `sequence_number`: The position of this atom in the chain
15/// - `coord`: Cartesian coordinates `x, y, z`
16/// - `occupancy`:
17/// - `temperature_factor`:
18/// - `element`: The [`Element`]
19/// - `chaege`: The charge on the atom
20///
21/// [`Residue`]: ../residue/struct.Residue.html
22/// [`Element`]: ../element/enum.Element.html
23#[derive(Debug, Clone, Serialize)]
24pub struct Atom {
25 pub id: AtomSerial,
26 pub name: AtomName,
27 pub id1: char,
28 pub residue: Residue,
29 pub chain: char,
30 pub sequence_number: u32,
31 pub insertion_code: char,
32 pub coord: [f32; 3],
33 pub occupancy: f32,
34 pub temperature_factor: f32,
35 pub element: Element,
36 pub charge: i8,
37 // pub is_hetatom: bool, // ! implied
38}
39
40pub struct GenericAtomParser;
41
42/// Internally, an `AtomName` is represented as an array of 4 bytes.
43///
44/// For convinience, `FromStr` is implemented for `AtomName`, so you can:
45///
46/// ```
47/// use std::str::FromStr;
48/// use protein_core::structure::AtomName;
49///
50/// let atom = AtomName::from_str("O2").unwrap();
51/// assert_eq!(atom, AtomName([b'O', b'2', b' ', b' ']));
52/// ```
53#[derive(Debug, Clone, Serialize, Eq, PartialEq)]
54pub struct AtomName(pub [u8; 4]);
55
56impl AtomName {
57 pub(crate) fn is_n(&self) -> bool {
58 &self.0 == b"N "
59 }
60 pub(crate) fn is_c(&self) -> bool {
61 &self.0 == b"C "
62 }
63 pub(crate) fn is_ca(&self) -> bool {
64 &self.0 == b"CA "
65 }
66 pub(crate) fn is_o(&self) -> bool {
67 &self.0 == b"O "
68 }
69}
70
71#[derive(Debug)]
72pub enum ParseAtomNameError {
73 LengthGreaterThan4,
74 LengthZero,
75}
76
77impl FromStr for AtomName {
78 type Err = ParseAtomNameError;
79 fn from_str(s: &str) -> Result<Self, ParseAtomNameError> {
80 let s = s.as_bytes();
81 match s.len() {
82 1usize => Ok(AtomName([s[0], b' ', b' ', b' '])),
83 2usize => Ok(AtomName([s[0], s[1], b' ', b' '])),
84 3usize => Ok(AtomName([s[0], s[1], s[2], b' '])),
85 4usize => Ok(AtomName([s[0], s[1], s[2], s[3]])),
86 0usize => Err(ParseAtomNameError::LengthZero),
87 _ => Err(ParseAtomNameError::LengthGreaterThan4),
88 }
89 }
90}
91
92// #[derive(Debug, Clone, Serialize)]
93// pub enum AtomName {
94// AminoAcid(AminoAcidAtomName),
95// Nucleotide(NucleotideAtomName),
96// WaterO,
97// Other(String),
98// }
99
100// #[derive(Debug, Clone, Serialize)]
101// pub enum AminoAcidAtomName {
102// N,
103// CA,
104// C,
105// O,
106// Other(String),
107// }
108
109// impl AminoAcidAtomName {
110// pub fn from_bytes(inp: &[u8]) -> Self {
111// match inp {
112// b"N" => Self::N,
113// b"CA" => Self::CA,
114// b"C" => Self::C,
115// b"O" => Self::O,
116// _ => Self::Other(unsafe { std::str::from_utf8_unchecked(inp).to_owned() }),
117// }
118// }
119// pub fn from_bytes_fixed4(inp: &[u8]) -> Self {
120// match inp {
121// b" N " => Self::N,
122// b" CA " => Self::CA,
123// b" C " => Self::C,
124// b" O " => Self::O,
125// _ => Self::Other(unsafe { std::str::from_utf8_unchecked(inp).trim_start().to_owned() }),
126// }
127// }
128// }
129
130// #[derive(Debug, Clone, Serialize)]
131// pub enum NucleotideAtomName {
132// OP1,
133// OP2,
134// O5,
135// O4,
136// O3,
137// O2,
138// C5,
139// C4,
140// C3,
141// C2,
142// C1,
143// N9,
144// N7,
145// N6,
146// N4,
147// N3,
148// N2,
149// N1,
150// P,
151// Other(String),
152// }
153
154// impl NucleotideAtomName {
155// pub fn from_bytes_uppercase(inp: &[u8]) -> Self {
156// match inp {
157// b"OP1" => Self::OP1,
158// b"OP2" => Self::OP2,
159// b"O5" => Self::O5,
160// b"O4" => Self::O4,
161// b"O3" => Self::O3,
162// b"O2" => Self::O2,
163// b"C5" => Self::C5,
164// b"C4" => Self::C4,
165// b"C3" => Self::C3,
166// b"C2" => Self::C2,
167// b"C1" => Self::C1,
168// b"N9" => Self::N9,
169// b"N7" => Self::N7,
170// b"N6" => Self::N6,
171// b"N4" => Self::N4,
172// b"N3" => Self::N3,
173// b"N2" => Self::N2,
174// b"N1" => Self::N1,
175// b"P" => Self::P,
176// _ => Self::Other(unsafe { std::str::from_utf8_unchecked(inp).to_owned() }),
177// }
178// }
179// pub fn from_bytes_uppercase_fixed4(inp: &[u8]) -> Self {
180// match inp {
181// b" OP1" => Self::OP1,
182// b" OP2" => Self::OP2,
183// b" O5 " => Self::O5,
184// b" O4 " => Self::O4,
185// b" O3 " => Self::O3,
186// b" O2 " => Self::O2,
187// b" C5 " => Self::C5,
188// b" C4 " => Self::C4,
189// b" C3 " => Self::C3,
190// b" C2 " => Self::C2,
191// b" C1 " => Self::C1,
192// b" N9 " => Self::N9,
193// b" N7 " => Self::N7,
194// b" N6 " => Self::N6,
195// b" N4 " => Self::N4,
196// b" N3 " => Self::N3,
197// b" N2 " => Self::N2,
198// b" N1 " => Self::N1,
199// b" P " => Self::P,
200// _ => Self::Other(unsafe { std::str::from_utf8_unchecked(inp).trim_start().to_owned() }),
201// }
202// }
203// }