graph_symmetry/ext/molecule/
molecule.rs

1// Copyright 2021 Chiral Ltd.
2// Licensed under the Apache-2.0 license (https://opensource.org/licenses/Apache-2.0)
3// This file may not be copied, modified, or distributed
4// except according to those terms.
5
6use super::atom;
7
8pub struct Molecule {
9    pub atoms: Vec<atom::Atom>,
10}
11
12impl Molecule {
13    pub fn from_smiles(smiles: &str) -> Self {
14        let mut builder = purr::graph::Builder::new();
15
16        match purr::read::read(smiles, &mut builder, None) {
17            Ok(_) => {
18                // let mut atoms = builder.build().expect("atoms");
19                match builder.build() {
20                    Ok(mut atoms) => {
21                        // lib 'purr' indicates an aromatic bond as bondkind::elided instead of bondkind::aromatic 
22                        //      if bond symbol ':' does not occur explicitly in molecule smiles.
23                        // for the applications which requires a clear differentiation between single bond and aromatic bond,
24                        //      bondkind::elided is not enough.
25                        let aromatic_flags: Vec<bool> = atoms.iter().map(|atom| atom.is_aromatic()).collect();
26                        for atom_idx in 0..(atoms.len()) {
27                            for bond in atoms[atom_idx].bonds.iter_mut() {
28                                if aromatic_flags[atom_idx] && aromatic_flags[bond.tid] {
29                                    *bond = purr::graph::Bond::new(purr::feature::BondKind::Aromatic, bond.tid);
30                                }
31                            }
32                        }
33
34                        let new_atoms: Vec<atom::Atom> = atoms.iter().map(|a| atom::Atom::from_atom_purr(&a)).collect();
35                        Self { atoms: new_atoms }
36                    },
37                    Err(e) => {
38                        println!("error on purr builder: {:?}", e);
39                        Self { atoms: vec![] }
40                    }
41                } 
42            },
43            Err(e) => {
44                println!("error smiles parsing: {:?}", e);
45                Self { atoms: vec![] }
46            }
47        }
48    }
49    
50    pub fn smiles_with_index(&self, smiles: &String, numbering: &Vec<usize>) -> String {
51        if self.atoms.len() == 0 {
52            return "smiles parsing errro".to_string()
53        }
54
55        let mut new_smiles: String = String::from("");
56        let mut cur: usize = 0;
57
58        for i in 0..(self.atoms.len()-1) {
59            let atom_string: String = self.atoms[i].kind.to_string();
60            let mapped_number = match numbering.len() > 0 {
61                true => numbering[i],
62                false => i
63            };
64            if atom_string.as_bytes()[0] == "[".as_bytes()[0] {
65                new_smiles += &format!("[{atom_string}:{index}]", atom_string=String::from(&atom_string[1..(atom_string.len()-1)]), index=mapped_number);
66                while smiles.as_bytes()[cur] != "]".as_bytes()[0] {
67                    cur += 1;
68                }
69                cur += 1;
70            } else {
71                new_smiles += &format!("[{atom_string}:{index}]", atom_string=atom_string, index=mapped_number);
72                cur += atom_string.len();
73            }
74
75            let cur_last: usize = cur;
76            let next_atom_string: String = self.atoms[i+1].kind.to_string();
77            while next_atom_string.as_bytes()[0] != smiles.as_bytes()[cur] {
78                cur += 1;
79                if cur >= smiles.len() {
80                    break;
81                }
82            }
83
84            new_smiles += &smiles[cur_last..cur];
85        }
86
87
88        let mapped_number = match numbering.len() > 0 {
89            true => numbering[self.atoms.len() - 1],
90            false => self.atoms.len() - 1
91        };
92        let atom_string: String = self.atoms[self.atoms.len() - 1].kind.to_string();
93        new_smiles += &format!("[{atom_string}:{index}]", atom_string=atom_string, index=mapped_number);
94        cur += atom_string.len();
95        new_smiles += &smiles[cur..smiles.len()];
96
97        new_smiles
98    }
99}
100
101#[cfg(test)]
102mod test_ext_mol_molecule {
103    use super::*;
104    
105    #[test]
106    fn test_purr_features() {
107        let smiles: String = "cc1".to_string();
108        let mol = Molecule::from_smiles(&smiles);
109        assert_eq!(mol.atoms.len(), 0);
110    }
111
112    #[test]
113    fn test_from_smiles() {
114        let smiles: String = "c1ccccc1CN".to_string();
115        let mol = Molecule::from_smiles(&smiles);
116        assert_eq!(mol.atoms[0].kind, "c".to_string());
117        assert_eq!(mol.atoms[0].bonds.len(), 2);
118        assert_eq!(mol.atoms[1].kind, "c".to_string());
119        assert_eq!(mol.atoms[1].bonds.len(), 2);
120        assert_eq!(mol.atoms[2].kind, "c".to_string());
121        assert_eq!(mol.atoms[2].bonds.len(), 2);
122        assert_eq!(mol.atoms[3].kind, "c".to_string());
123        assert_eq!(mol.atoms[3].bonds.len(), 2);
124        assert_eq!(mol.atoms[4].kind, "c".to_string());
125        assert_eq!(mol.atoms[4].bonds.len(), 2);
126        assert_eq!(mol.atoms[5].kind, "c".to_string());
127        assert_eq!(mol.atoms[5].bonds.len(), 3);
128        assert_eq!(mol.atoms[6].kind, "C".to_string());
129        assert_eq!(mol.atoms[6].bonds.len(), 2);
130        assert_eq!(mol.atoms[7].kind, "N".to_string());
131        assert_eq!(mol.atoms[7].bonds.len(), 1);
132    }
133}