Skip to main content

ferritin_core/info/
constants.rs

1//! # Constants
2//!
3//! This module contains core data structures and functions for analyzing molecular structures.
4//!
5//! ## Residue Types
6//! The module provides functions to check residue types:
7//!
8//! - `is_amino_acid()` - Check if a residue is an amino acid
9//! - `is_carbohydrate()` - Check if a residue is a carbohydrate
10//! - `is_nucleotide()` - Check if a residue is a nucleotide
11//!
12//! ## Bond Information
13//! Bond data from CCD includes:
14//!
15//! - Bond lengths with standard deviations
16//! - Canonical amino acid connectivity
17//! - Bond order information
18//!
19
20use std::collections::{HashMap, HashSet};
21use std::sync::OnceLock;
22
23#[rustfmt::skip]
24#[allow(dead_code)]
25pub(crate) fn default_distance_range(a: &str, b: &str) -> Option<(f32, f32)> {
26    match (a, b) {
27        // https://github.com/biotite-dev/biotite/blob/main/src/biotite/structure/bonds.pyx#L1341C1-L1389C1
28        //               # Taken from Allen et al.
29        //                 min   - 2*std     max   + 2*std
30        ("B",  "C" ) => Some((1.556 - 2.0*0.015,  1.556 + 2.0*0.015)),
31        ("BR", "C" ) => Some((1.875 - 2.0*0.029,  1.966 + 2.0*0.029)),
32        ("BR", "O" ) => Some((1.581 - 2.0*0.007,  1.581 + 2.0*0.007)),
33        ("C",  "C" ) => Some((1.174 - 2.0*0.011,  1.588 + 2.0*0.025)),
34        ("C",  "CL") => Some((1.713 - 2.0*0.011,  1.849 + 2.0*0.011)),
35        ("C",  "F" ) => Some((1.320 - 2.0*0.009,  1.428 + 2.0*0.009)),
36        ("C",  "H" ) => Some((1.059 - 2.0*0.030,  1.099 + 2.0*0.007)),
37        ("C",  "I" ) => Some((2.095 - 2.0*0.015,  2.162 + 2.0*0.015)),
38        ("C",  "N" ) => Some((1.325 - 2.0*0.009,  1.552 + 2.0*0.023)),
39        ("C",  "O" ) => Some((1.187 - 2.0*0.011,  1.477 + 2.0*0.008)),
40        ("C",  "P" ) => Some((1.791 - 2.0*0.006,  1.855 + 2.0*0.019)),
41        ("C",  "S" ) => Some((1.630 - 2.0*0.014,  1.863 + 2.0*0.015)),
42        ("C",  "SE") => Some((1.893 - 2.0*0.013,  1.970 + 2.0*0.032)),
43        ("C",  "SI") => Some((1.837 - 2.0*0.012,  1.888 + 2.0*0.023)),
44        ("CL", "O" ) => Some((1.414 - 2.0*0.026,  1.414 + 2.0*0.026)),
45        ("CL", "P" ) => Some((1.997 - 2.0*0.035,  2.008 + 2.0*0.035)),
46        ("CL", "S" ) => Some((2.072 - 2.0*0.023,  2.072 + 2.0*0.023)),
47        ("CL", "SI") => Some((2.072 - 2.0*0.009,  2.072 + 2.0*0.009)),
48        ("F",  "N" ) => Some((1.406 - 2.0*0.016,  1.406 + 2.0*0.016)),
49        ("F",  "P" ) => Some((1.495 - 2.0*0.016,  1.579 + 2.0*0.025)),
50        ("F",  "S" ) => Some((1.640 - 2.0*0.011,  1.640 + 2.0*0.011)),
51        ("F",  "SI") => Some((1.588 - 2.0*0.014,  1.694 + 2.0*0.013)),
52        ("H",  "N" ) => Some((1.009 - 2.0*0.022,  1.033 + 2.0*0.022)),
53        ("H",  "O" ) => Some((0.967 - 2.0*0.010,  1.015 + 2.0*0.017)),
54        ("I",  "O" ) => Some((2.144 - 2.0*0.028,  2.144 + 2.0*0.028)),
55        ("N",  "N" ) => Some((1.124 - 2.0*0.015,  1.454 + 2.0*0.021)),
56        ("N",  "O" ) => Some((1.210 - 2.0*0.011,  1.463 + 2.0*0.012)),
57        ("N",  "P" ) => Some((1.571 - 2.0*0.013,  1.697 + 2.0*0.015)),
58        ("N",  "S" ) => Some((1.541 - 2.0*0.022,  1.710 + 2.0*0.019)),
59        ("N",  "SI") => Some((1.711 - 2.0*0.019,  1.748 + 2.0*0.022)),
60        ("O",  "P" ) => Some((1.449 - 2.0*0.007,  1.689 + 2.0*0.024)),
61        ("O",  "S" ) => Some((1.423 - 2.0*0.008,  1.580 + 2.0*0.015)),
62        ("O",  "SI") => Some((1.622 - 2.0*0.014,  1.680 + 2.0*0.008)),
63        ("P",  "P" ) => Some((2.214 - 2.0*0.022,  2.214 + 2.0*0.022)),
64        ("P",  "S" ) => Some((1.913 - 2.0*0.014,  1.954 + 2.0*0.005)),
65        ("P",  "SE") => Some((2.093 - 2.0*0.019,  2.093 + 2.0*0.019)),
66        ("P",  "SI") => Some((2.264 - 2.0*0.019,  2.264 + 2.0*0.019)),
67        ("S",  "S" ) => Some((1.897 - 2.0*0.012,  2.070 + 2.0*0.022)),
68        ("S",  "SE") => Some((2.193 - 2.0*0.015,  2.193 + 2.0*0.015)),
69        ("S",  "SI") => Some((2.145 - 2.0*0.020,  2.145 + 2.0*0.020)),
70        ("SE", "SE") => Some((2.340 - 2.0*0.024,  2.340 + 2.0*0.024)),
71        ("SI", "SE") => Some((2.359 - 2.0*0.012,  2.359 + 2.0*0.012)),
72        _ => None,
73    }
74}
75
76#[allow(clippy::type_complexity)] // (atom, atom, bond-order) bond table keyed by residue
77static AA_BONDS: OnceLock<HashMap<&'static str, Vec<(&'static str, &'static str, i32)>>> =
78    OnceLock::new();
79
80#[rustfmt::skip]
81/// get_bonds_canonical20
82///
83/// This is the bond information for the 10 canonical
84/// AAs.  Data were obtained from the [CCD](https://www.wwpdb.org/data/ccd).
85///
86pub(crate) fn get_bonds_canonical20() -> &'static HashMap<&'static str, Vec<(&'static str, &'static str, i32)>> {
87    AA_BONDS.get_or_init(|| {
88        let mut m = HashMap::new();
89        m.insert("ALA", vec![
90            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
91            ("CB","HB1",1), ("CB","HB2",1), ("CB","HB3",1), ("CA","N",1), ("H","N",1),
92            ("H2","N",1), ("HXT","OXT",1)
93        ]);
94        m.insert("ARG", vec![
95            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
96            ("CB","CG",1), ("CB","HB2",1), ("CB","HB3",1), ("CD","HD2",1), ("CD","HD3",1),
97            ("CD","NE",1), ("CD","CG",1), ("CG","HG2",1), ("CG","HG3",1), ("CZ","NH1",1),
98            ("CZ","NH2",2), ("CA","N",1), ("H","N",1), ("H2","N",1), ("CZ","NE",1),
99            ("HE","NE",1), ("HH11","NH1",1), ("HH12","NH1",1), ("HH21","NH2",1),
100            ("HH22","NH2",1), ("HXT","OXT",1)
101        ]);
102        m.insert("ASN", vec![
103            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
104            ("CB","CG",1), ("CB","HB2",1), ("CB","HB3",1), ("CG","ND2",1), ("CG","OD1",2),
105            ("CA","N",1), ("H","N",1), ("H2","N",1), ("HD21","ND2",1), ("HD22","ND2",1),
106            ("HXT","OXT",1)
107        ]);
108        m.insert("ASP", vec![
109            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
110            ("CB","CG",1), ("CB","HB2",1), ("CB","HB3",1), ("CG","OD1",2), ("CG","OD2",1),
111            ("CA","N",1), ("H","N",1), ("H2","N",1), ("HD2","OD2",1), ("HXT","OXT",1)
112        ]);
113        m.insert("CYS", vec![
114            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
115            ("CB","HB2",1), ("CB","HB3",1), ("CB","SG",1), ("CA","N",1), ("H","N",1),
116            ("H2","N",1), ("HXT","OXT",1), ("HG","SG",1)
117        ]);
118        m.insert("GLN", vec![
119            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
120            ("CB","CG",1), ("CB","HB2",1), ("CB","HB3",1), ("CD","NE2",1), ("CD","OE1",2),
121            ("CD","CG",1), ("CG","HG2",1), ("CG","HG3",1), ("CA","N",1), ("H","N",1),
122            ("H2","N",1), ("HE21","NE2",1), ("HE22","NE2",1), ("HXT","OXT",1)
123        ]);
124        m.insert("GLU", vec![
125            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
126            ("CB","CG",1), ("CB","HB2",1), ("CB","HB3",1), ("CD","OE1",2), ("CD","OE2",1),
127            ("CD","CG",1), ("CG","HG2",1), ("CG","HG3",1), ("CA","N",1), ("H","N",1),
128            ("H2","N",1), ("HE2","OE2",1), ("HXT","OXT",1)
129        ]);
130        m.insert("GLY", vec![
131            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","HA2",1), ("CA","HA3",1),
132            ("CA","N",1), ("H","N",1), ("H2","N",1), ("HXT","OXT",1)
133        ]);
134        m.insert("HIS", vec![
135            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
136            ("CB","CG",1), ("CB","HB2",1), ("CB","HB3",1), ("CD2","HD2",1), ("CD2","NE2",5),
137            ("CE1","HE1",1), ("CE1","NE2",5), ("CD2","CG",6), ("CG","ND1",5), ("CA","N",1),
138            ("H","N",1), ("H2","N",1), ("CE1","ND1",6), ("HD1","ND1",1), ("HE2","NE2",1),
139            ("HXT","OXT",1)
140        ]);
141        m.insert("ILE", vec![
142            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
143            ("CB","CG1",1), ("CB","CG2",1), ("CB","HB",1), ("CD1","HD11",1), ("CD1","HD12",1),
144            ("CD1","HD13",1), ("CD1","CG1",1), ("CG1","HG12",1), ("CG1","HG13",1),
145            ("CG2","HG21",1), ("CG2","HG22",1), ("CG2","HG23",1), ("CA","N",1), ("H","N",1),
146            ("H2","N",1), ("HXT","OXT",1)
147        ]);
148        m.insert("LEU", vec![
149            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
150            ("CB","CG",1), ("CB","HB2",1), ("CB","HB3",1), ("CD1","HD11",1), ("CD1","HD12",1),
151            ("CD1","HD13",1), ("CD2","HD21",1), ("CD2","HD22",1), ("CD2","HD23",1),
152            ("CD1","CG",1), ("CD2","CG",1), ("CG","HG",1), ("CA","N",1), ("H","N",1),
153            ("H2","N",1), ("HXT","OXT",1)
154        ]);
155        m.insert("LYS", vec![
156            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
157            ("CB","CG",1), ("CB","HB2",1), ("CB","HB3",1), ("CD","CE",1), ("CD","HD2",1),
158            ("CD","HD3",1), ("CE","HE2",1), ("CE","HE3",1), ("CE","NZ",1), ("CD","CG",1),
159            ("CG","HG2",1), ("CG","HG3",1), ("CA","N",1), ("H","N",1), ("H2","N",1),
160            ("HZ1","NZ",1), ("HZ2","NZ",1), ("HZ3","NZ",1), ("HXT","OXT",1)
161        ]);
162        m.insert("MET", vec![
163            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
164            ("CB","CG",1), ("CB","HB2",1), ("CB","HB3",1), ("CE","HE1",1), ("CE","HE2",1),
165            ("CE","HE3",1), ("CG","HG2",1), ("CG","HG3",1), ("CG","SD",1), ("CA","N",1),
166            ("H","N",1), ("H2","N",1), ("HXT","OXT",1), ("CE","SD",1)
167        ]);
168        m.insert("PHE", vec![
169            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
170            ("CB","CG",1), ("CB","HB2",1), ("CB","HB3",1), ("CD1","CE1",5), ("CD1","HD1",1),
171            ("CD2","CE2",6), ("CD2","HD2",1), ("CE1","CZ",6), ("CE1","HE1",1), ("CE2","CZ",5),
172            ("CE2","HE2",1), ("CD1","CG",6), ("CD2","CG",5), ("CZ","HZ",1), ("CA","N",1),
173            ("H","N",1), ("H2","N",1), ("HXT","OXT",1)
174        ]);
175        m.insert("PRO", vec![
176            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
177            ("CB","CG",1), ("CB","HB2",1), ("CB","HB3",1), ("CD","HD2",1), ("CD","HD3",1),
178            ("CD","CG",1), ("CG","HG2",1), ("CG","HG3",1), ("CA","N",1), ("CD","N",1),
179            ("H","N",1), ("HXT","OXT",1)
180        ]);
181        m.insert("SER", vec![
182            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
183            ("CB","HB2",1), ("CB","HB3",1), ("CB","OG",1), ("CA","N",1), ("H","N",1),
184            ("H2","N",1), ("HG","OG",1), ("HXT","OXT",1)
185        ]);
186        m.insert("THR", vec![
187            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
188            ("CB","CG2",1), ("CB","HB",1), ("CB","OG1",1), ("CG2","HG21",1), ("CG2","HG22",1),
189            ("CG2","HG23",1), ("CA","N",1), ("H","N",1), ("H2","N",1), ("HG1","OG1",1),
190            ("HXT","OXT",1)
191        ]);
192        m.insert("TRP", vec![
193            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
194            ("CB","CG",1), ("CB","HB2",1), ("CB","HB3",1), ("CD1","HD1",1), ("CD1","NE1",5),
195            ("CD2","CE2",6), ("CD2","CE3",5), ("CE2","CZ2",5), ("CE3","CZ3",6), ("CE3","HE3",1),
196            ("CD1","CG",6), ("CD2","CG",5), ("CH2","HH2",1), ("CH2","CZ2",6), ("CZ2","HZ2",1),
197            ("CH2","CZ3",5), ("CZ3","HZ3",1), ("CA","N",1), ("H","N",1), ("H2","N",1),
198            ("CE2","NE1",5), ("HE1","NE1",1), ("HXT","OXT",1)
199        ]);
200        m.insert("TYR", vec![
201            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
202            ("CB","CG",1), ("CB","HB2",1), ("CB","HB3",1), ("CD1","CE1",5), ("CD1","HD1",1),
203            ("CD2","CE2",6), ("CD2","HD2",1), ("CE1","CZ",6), ("CE1","HE1",1), ("CE2","CZ",5),
204            ("CE2","HE2",1), ("CD1","CG",6), ("CD2","CG",5), ("CZ","OH",1), ("CA","N",1),
205            ("H","N",1), ("H2","N",1), ("HH","OH",1), ("HXT","OXT",1)
206        ]);
207        m.insert("VAL", vec![
208            ("C","O",2), ("C","OXT",1), ("C","CA",1), ("CA","CB",1), ("CA","HA",1),
209            ("CB","CG1",1), ("CB","CG2",1), ("CB","HB",1), ("CG1","HG11",1), ("CG1","HG12",1),
210            ("CG1","HG13",1), ("CG2","HG21",1), ("CG2","HG22",1), ("CG2","HG23",1),
211            ("CA","N",1), ("H","N",1), ("H2","N",1), ("HXT","OXT",1)
212        ]);
213        m
214    })
215}
216
217static AMINO_ACIDS: OnceLock<HashSet<&'static str>> = OnceLock::new();
218static CARBOHYDRATES: OnceLock<HashSet<&'static str>> = OnceLock::new();
219static NUCLEOTIDES: OnceLock<HashSet<&'static str>> = OnceLock::new();
220
221fn get_amino_acids() -> &'static HashSet<&'static str> {
222    AMINO_ACIDS.get_or_init(|| include_str!("ccddata/amino_acids.txt").lines().collect())
223}
224
225fn get_carbohydrates() -> &'static HashSet<&'static str> {
226    CARBOHYDRATES.get_or_init(|| include_str!("ccddata/carbohydrates.txt").lines().collect())
227}
228
229fn get_nucleotides() -> &'static HashSet<&'static str> {
230    NUCLEOTIDES.get_or_init(|| include_str!("ccddata/nucleotides.txt").lines().collect())
231}
232
233pub(crate) fn is_amino_acid(symbol: &str) -> bool {
234    get_amino_acids().contains(symbol)
235}
236
237pub(crate) fn is_carbohydrate(symbol: &str) -> bool {
238    get_carbohydrates().contains(symbol)
239}
240
241pub(crate) fn is_nucleotide(symbol: &str) -> bool {
242    get_nucleotides().contains(symbol)
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn test_residue_checking() {
251        assert!(is_amino_acid("ALA"));
252        assert!(is_amino_acid("ARG"));
253        assert!(!is_amino_acid("ZZZ"));
254
255        assert!(is_carbohydrate("045"));
256        assert!(is_carbohydrate("05L"));
257        assert!(!is_carbohydrate("ZZZ"));
258
259        assert!(is_nucleotide("02I"));
260        assert!(is_nucleotide("05A"));
261        assert!(!is_nucleotide("ZZZ"));
262    }
263}