rust_sasa/utils/
consts.rs

1use std::collections::HashSet;
2use std::sync::LazyLock;
3
4use fnv::FnvHashMap;
5
6pub(crate) static POLAR_AMINO_ACIDS: LazyLock<HashSet<String>> = LazyLock::new(|| {
7    let mut m = HashSet::new();
8    m.insert("SER".to_string());
9    m.insert("THR".to_string());
10    m.insert("CYS".to_string());
11    m.insert("ASN".to_string());
12    m.insert("GLN".to_string());
13    m.insert("TYR".to_string());
14    m
15});
16
17pub(crate) const GOLDEN_RATIO: f32 = 1.618_034;
18pub(crate) const ANGLE_INCREMENT: f32 = 2.0 * std::f32::consts::PI * GOLDEN_RATIO;
19
20/// Macro to load ProtOr radii config at compile time and parse lazily
21macro_rules! load_protor_radii {
22    () => {
23        LazyLock::new(|| {
24            let config = include_str!("../../radii/protor.config");
25            parse_radii_config(config)
26        })
27    };
28}
29
30pub(crate) fn parse_radii_config(content: &str) -> FnvHashMap<String, FnvHashMap<String, f32>> {
31    let mut types: FnvHashMap<String, f32> = FnvHashMap::default();
32    let mut atoms: FnvHashMap<String, FnvHashMap<String, f32>> = FnvHashMap::default();
33
34    let mut in_types = false;
35    let mut in_atoms = false;
36
37    for line in content.lines() {
38        let line = line.trim();
39
40        if line.is_empty() || line.starts_with('#') || line.starts_with("name:") {
41            continue;
42        }
43
44        // Track which section we're in
45        if line == "types:" {
46            in_types = true;
47            in_atoms = false;
48            continue;
49        }
50        if line == "atoms:" {
51            in_types = false;
52            in_atoms = true;
53            continue;
54        }
55
56        if in_types {
57            // Format: TYPE_NAME RADIUS POLARITY
58            let parts: Vec<&str> = line.split_whitespace().collect();
59            if parts.len() >= 2 {
60                if let Ok(radius) = parts[1].parse::<f32>() {
61                    types.insert(parts[0].to_string(), radius);
62                }
63            }
64        } else if in_atoms {
65            // Format: RESIDUE ATOM_NAME TYPE_NAME
66            let parts: Vec<&str> = line.split_whitespace().collect();
67            if parts.len() >= 3 {
68                if let Some(&radius) = types.get(parts[2]) {
69                    #[allow(clippy::unwrap_or_default)]
70                    atoms
71                        .entry(parts[0].to_string())
72                        .or_insert_with(FnvHashMap::default)
73                        .insert(parts[1].to_string(), radius);
74                }
75            }
76        }
77    }
78
79    atoms
80}
81
82pub(crate) fn load_radii_from_file(
83    path: &str,
84) -> Result<FnvHashMap<String, FnvHashMap<String, f32>>, std::io::Error> {
85    let content = std::fs::read_to_string(path)?;
86    Ok(parse_radii_config(&content))
87}
88
89pub(crate) static PROTOR_RADII: LazyLock<FnvHashMap<String, FnvHashMap<String, f32>>> =
90    load_protor_radii!();
91
92pub(crate) fn get_protor_radius(residue: &str, atom: &str) -> Option<f32> {
93    PROTOR_RADII.get(residue)?.get(atom).copied()
94}