Skip to main content

chematic_core/
bond.rs

1//! Bond types and the bond-entry struct for molecular graphs.
2
3/// Bond order, covering all SMILES bond types.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum BondOrder {
6    /// Single bond `-`
7    Single,
8    /// Double bond `=`
9    Double,
10    /// Triple bond `#`
11    Triple,
12    /// Quadruple bond `$` (used in some metal complexes)
13    Quadruple,
14    /// Aromatic bond `:` (pre-Kekulization representation)
15    Aromatic,
16    /// Stereo bond `/` (E/Z specification)
17    Up,
18    /// Stereo bond `\` (E/Z specification)
19    Down,
20}
21
22impl BondOrder {
23    /// Fractional bond order. Returns `None` for aromatic (non-integer).
24    pub fn order_value(self) -> Option<f32> {
25        match self {
26            Self::Single    => Some(1.0),
27            Self::Double    => Some(2.0),
28            Self::Triple    => Some(3.0),
29            Self::Quadruple => Some(4.0),
30            Self::Aromatic  => None,
31            Self::Up | Self::Down => Some(1.0),
32        }
33    }
34
35    /// Integer bond order used for valence calculations.
36    /// Aromatic and stereo bonds count as 1.
37    pub fn order_int(self) -> u8 {
38        match self {
39            Self::Single | Self::Up | Self::Down | Self::Aromatic => 1,
40            Self::Double    => 2,
41            Self::Triple    => 3,
42            Self::Quadruple => 4,
43        }
44    }
45
46    /// The SMILES character that represents this bond order.
47    pub fn smiles_char(self) -> char {
48        match self {
49            Self::Single    => '-',
50            Self::Double    => '=',
51            Self::Triple    => '#',
52            Self::Quadruple => '$',
53            Self::Aromatic  => ':',
54            Self::Up        => '/',
55            Self::Down      => '\\',
56        }
57    }
58}
59
60impl core::fmt::Display for BondOrder {
61    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
62        write!(f, "{}", self.smiles_char())
63    }
64}
65
66/// A single bond edge in the molecular graph.
67///
68/// The graph is undirected; `atom1`/`atom2` ordering is not guaranteed.
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct BondEntry {
71    pub atom1: crate::molecule::AtomIdx,
72    pub atom2: crate::molecule::AtomIdx,
73    pub order: BondOrder,
74}
75
76impl BondEntry {
77    /// Return the atom on the other side of this bond, or `None` if `idx` is not an endpoint.
78    pub fn other(&self, idx: crate::molecule::AtomIdx) -> Option<crate::molecule::AtomIdx> {
79        if self.atom1 == idx {
80            Some(self.atom2)
81        } else if self.atom2 == idx {
82            Some(self.atom1)
83        } else {
84            None
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use crate::molecule::AtomIdx;
93
94    #[test]
95    fn test_bond_order_values() {
96        assert_eq!(BondOrder::Single.order_value(), Some(1.0));
97        assert_eq!(BondOrder::Double.order_value(), Some(2.0));
98        assert_eq!(BondOrder::Triple.order_value(), Some(3.0));
99        assert_eq!(BondOrder::Aromatic.order_value(), None);
100    }
101
102    #[test]
103    fn test_bond_other() {
104        let b = BondEntry { atom1: AtomIdx(0), atom2: AtomIdx(1), order: BondOrder::Single };
105        assert_eq!(b.other(AtomIdx(0)), Some(AtomIdx(1)));
106        assert_eq!(b.other(AtomIdx(1)), Some(AtomIdx(0)));
107        assert_eq!(b.other(AtomIdx(2)), None);
108    }
109}