purr/graph/
atom.rs

1use crate::feature::AtomKind;
2use super::Bond;
3
4/// Atom used in graph-like (adjacency) SMILES representation.
5#[derive(Debug,PartialEq)]
6pub struct Atom {
7    pub kind: AtomKind,
8    pub bonds: Vec<Bond>
9}
10
11impl Atom {
12    /// Consutrcts an Atom without bonds.
13    pub fn new(kind: AtomKind) -> Self {
14        Self {
15            kind,
16            bonds: vec![ ]
17        }
18    }
19
20    /// Returns true if the atom was encoded as aromatic.
21    pub fn is_aromatic(&self) -> bool {
22        self.kind.is_aromatic()
23    }
24
25    /// Computes and returns the subvalence associated with this Atom.
26    /// Subvalence represents the maximum number of [implicit hydrogens](https://depth-first.com/articles/2020/06/08/hydrogen-suppression-in-smiles/)
27    /// that can be added to this Atom without exceeding a valence target.
28    /// This value is independent of an atom's aromaticity marking.
29    pub fn subvalence(&self) -> u8 {
30        let hcount: u8 = match &self.kind {
31            AtomKind::Bracket { hcount, .. } => match hcount {
32                Some(hcount) => hcount.into(),
33                None => 0
34            }
35            _ => 0
36        };
37        let valence = self.bonds.iter().fold(hcount, |sum,bond| {
38            sum + bond.order()
39        });
40        let targets = self.kind.targets().iter()
41            .find(|&&target| target >= valence);
42
43        let target = match targets {
44            Some(target) => target,
45            None => return 0
46        };
47        
48        target - valence
49    }
50
51    /// Returns the number of implicit or virtual hydrogens at this Atom,
52    /// accounting for aromaticity.
53    pub fn suppressed_hydrogens(&self) -> u8 {
54        match &self.kind {
55            AtomKind::Star => 0,
56            AtomKind::Aromatic(_) => {
57                let subvalence = self.subvalence();
58
59                if subvalence > 1 {
60                    self.subvalence() - 1
61                } else {
62                    0
63                }
64            },
65            AtomKind::Aliphatic(_) => self.subvalence(),
66            AtomKind::Bracket { hcount, .. } => match hcount {
67                Some(hcount) => hcount.into(),
68                None => 0
69            }
70        }
71    }
72    
73}
74
75#[cfg(test)]
76mod subvalence {
77    use crate::feature::{
78        BondKind, BracketSymbol, Element, BracketAromatic, Aliphatic, Aromatic,
79        Charge, VirtualHydrogen
80    };
81    use super::*;
82
83    #[test]
84    fn star() {
85        let atom = Atom {
86            kind: AtomKind::Star,
87            bonds: vec![ ]
88        };
89
90        assert_eq!(atom.subvalence(), 0)
91    }
92
93    #[test]
94    fn star_single() {
95        let atom = Atom {
96            kind: AtomKind::Star,
97            bonds: vec![
98                Bond::new(BondKind::Single, 1)
99            ]
100        };
101
102        assert_eq!(atom.subvalence(), 0)
103    }
104
105    #[test]
106    fn carbon_single() {
107        let atom = Atom {
108            kind: AtomKind::Aliphatic(Aliphatic::C),
109            bonds: vec![
110                Bond::new(BondKind::Single, 1)
111            ]
112        };
113
114        assert_eq!(atom.subvalence(), 3)
115    }
116
117    #[test]
118    fn aromatic_carbon_single() {
119        let atom = Atom {
120            kind: AtomKind::Aromatic(Aromatic::C),
121            bonds: vec![
122                Bond::new(BondKind::Single, 1)
123            ]
124        };
125
126        assert_eq!(atom.subvalence(), 3)
127    }
128
129    #[test]
130    fn bracket_star_single() {
131        let atom = Atom {
132            kind: AtomKind::Bracket {
133                isotope: None,
134                symbol: BracketSymbol::Star,
135                configuration: None,
136                hcount: None,
137                charge: None,
138                map: None
139            },
140            bonds: vec![
141                Bond::new(BondKind::Single, 1)
142            ]
143        };
144
145        assert_eq!(atom.subvalence(), 0)
146    }
147
148    #[test]
149    fn bracket_carbon_h1() {
150        let atom = Atom {
151            kind: AtomKind::Bracket {
152                isotope: None,
153                symbol: BracketSymbol::Element(Element::C),
154                configuration: None,
155                hcount: Some(VirtualHydrogen::H1),
156                charge: None,
157                map: None
158            },
159            bonds: vec![
160
161            ]
162        };
163
164        assert_eq!(atom.subvalence(), 3)
165    }
166
167    #[test]
168    fn bracket_carbon_h0_single() {
169        let atom = Atom {
170            kind: AtomKind::Bracket {
171                isotope: None,
172                symbol: BracketSymbol::Element(Element::C),
173                configuration: None,
174                hcount: None,
175                charge: None,
176                map: None
177            },
178            bonds: vec![
179                Bond::new(BondKind::Single, 1)
180            ]
181        };
182
183        assert_eq!(atom.subvalence(), 3)
184    }
185
186    #[test]
187    fn bracket_aromatic_h0_single() {
188        let atom = Atom {
189            kind: AtomKind::Bracket {
190                isotope: None,
191                symbol: BracketSymbol::Aromatic(BracketAromatic::C),
192                configuration: None,
193                hcount: None,
194                charge: None,
195                map: None
196            },
197            bonds: vec![
198                Bond::new(BondKind::Single, 1)
199            ]
200        };
201
202        assert_eq!(atom.subvalence(), 3)
203    }
204
205    #[test]
206    fn bracket_aromatic_carbon_h1_single() {
207        let atom = Atom {
208            kind: AtomKind::Bracket {
209                isotope: None,
210                symbol: BracketSymbol::Aromatic(BracketAromatic::C),
211                configuration: None,
212                hcount: Some(VirtualHydrogen::H1),
213                charge: None,
214                map: None
215            },
216            bonds: vec![
217                Bond::new(BondKind::Single, 1)
218            ]
219        };
220
221        assert_eq!(atom.subvalence(), 2)
222    }
223
224    #[test]
225    fn sulfur_charged_divalent() {
226        let atom = Atom {
227            kind: AtomKind::Bracket {
228                isotope: None,
229                symbol: BracketSymbol::Aromatic(BracketAromatic::S),
230                configuration: None,
231                hcount: None,
232                charge: Some(Charge::One),
233                map: None
234            },
235            bonds: vec![
236                Bond::new(BondKind::Single, 1),
237                Bond::new(BondKind::Single, 2)
238            ]
239        };
240
241        assert_eq!(atom.subvalence(), 1)
242    }
243}
244
245#[cfg(test)]
246mod suppressed_hydrogens {
247    use pretty_assertions::assert_eq;
248    use crate::feature::{
249        Aromatic, Aliphatic, BondKind, BracketSymbol, Element, VirtualHydrogen
250    };
251    use super::*;
252
253    #[test]
254    fn star() {
255        let atom = Atom::new(AtomKind::Star);
256
257        assert_eq!(atom.suppressed_hydrogens(), 0)
258    }
259
260    #[test]
261    fn aromatic_subvalence_1() {
262        let atom = Atom {
263            kind: AtomKind::Aromatic(Aromatic::C),
264            bonds: vec![
265                Bond::new(BondKind::Elided, 1),
266                Bond::new(BondKind::Elided, 2),
267                Bond::new(BondKind::Elided, 3)
268            ]
269        };
270
271        assert_eq!(atom.suppressed_hydrogens(), 0)
272    }
273
274    #[test]
275    fn aromatic_subvalence_2() {
276        let atom = Atom {
277            kind: AtomKind::Aromatic(Aromatic::C),
278            bonds: vec![
279                Bond::new(BondKind::Elided, 1),
280                Bond::new(BondKind::Elided, 2)
281            ]
282        };
283
284        assert_eq!(atom.suppressed_hydrogens(), 1)
285    }
286
287    #[test]
288    fn aliphatic_subvalence_0() {
289        let atom = Atom {
290            kind: AtomKind::Aliphatic(Aliphatic::C),
291            bonds: vec![
292                Bond::new(BondKind::Elided, 1),
293                Bond::new(BondKind::Elided, 2),
294                Bond::new(BondKind::Elided, 3),
295                Bond::new(BondKind::Elided, 4)
296            ]
297        };
298
299        assert_eq!(atom.suppressed_hydrogens(), 0)
300    }
301
302    #[test]
303    fn aliphatic_subvalence_1() {
304        let atom = Atom {
305            kind: AtomKind::Aliphatic(Aliphatic::C),
306            bonds: vec![
307                Bond::new(BondKind::Elided, 1),
308                Bond::new(BondKind::Elided, 2),
309                Bond::new(BondKind::Elided, 3)
310            ]
311        };
312
313        assert_eq!(atom.suppressed_hydrogens(), 1)
314    }
315
316    #[test]
317    fn aliphatic_subvalence_2() {
318        let atom = Atom {
319            kind: AtomKind::Aliphatic(Aliphatic::C),
320            bonds: vec![
321                Bond::new(BondKind::Elided, 1),
322                Bond::new(BondKind::Elided, 2)
323            ]
324        };
325
326        assert_eq!(atom.suppressed_hydrogens(), 2)
327    }
328
329    #[test]
330    fn bracket_h_none() {
331        let atom = Atom {
332            kind: AtomKind::Bracket {
333                isotope: None,
334                symbol: BracketSymbol::Element(Element::C),
335                hcount: None,
336                charge: None,
337                configuration: None,
338                map: None
339            },
340            bonds: vec![ ]
341        };
342
343        assert_eq!(atom.suppressed_hydrogens(), 0)
344    }
345
346    #[test]
347    fn bracket_h0() {
348        let atom = Atom {
349            kind: AtomKind::Bracket {
350                isotope: None,
351                symbol: BracketSymbol::Element(Element::C),
352                hcount: Some(VirtualHydrogen::H0),
353                charge: None,
354                configuration: None,
355                map: None
356            },
357            bonds: vec![ ]
358        };
359
360        assert_eq!(atom.suppressed_hydrogens(), 0)
361    }
362
363    #[test]
364    fn bracket_h1() {
365        let atom = Atom {
366            kind: AtomKind::Bracket {
367                isotope: None,
368                symbol: BracketSymbol::Element(Element::C),
369                hcount: Some(VirtualHydrogen::H1),
370                charge: None,
371                configuration: None,
372                map: None
373            },
374            bonds: vec![ ]
375        };
376
377        assert_eq!(atom.suppressed_hydrogens(), 1)
378    }
379}