use crate::bond::BondOrder;
use crate::molecule::{AtomIdx, Molecule};
pub fn implicit_hcount(mol: &Molecule, idx: AtomIdx) -> u8 {
let atom = mol.atom(idx);
if atom.wildcard {
return 0;
}
if let Some(h) = atom.hydrogen_count {
return h;
}
if !atom.element.is_organic_subset() {
return 0;
}
let normal_valences = atom.element.normal_valences();
if normal_valences.is_empty() {
return 0;
}
let charge = atom.charge as i32;
let mut aromatic_count: usize = 0;
let mut non_aromatic_sum: i32 = 0;
for (_, bidx) in mol.neighbors(idx) {
let order = mol.bond(bidx).order;
if order == BondOrder::Aromatic {
aromatic_count += 1;
} else {
non_aromatic_sum += order.order_int() as i32;
}
}
if aromatic_count > 0 {
let effective_sum =
(aromatic_count as f64 * 1.5).floor() as i32 + non_aromatic_sum;
let v = normal_valences[0] as i32 + charge;
if v <= 0 || effective_sum >= v {
return 0;
}
return (v - effective_sum) as u8;
}
let bond_sum = non_aromatic_sum;
let valences_to_check: &[u8] = if atom.aromatic {
&normal_valences[..1]
} else {
normal_valences
};
for &v in valences_to_check {
let target = v as i32 + charge;
if target < 0 {
continue;
}
if target >= bond_sum {
return (target - bond_sum) as u8;
}
}
0
}
pub fn total_hcount(mol: &Molecule, idx: AtomIdx) -> u8 {
implicit_hcount(mol, idx)
}
pub fn bond_order_sum(mol: &Molecule, idx: AtomIdx) -> u8 {
mol.neighbors(idx)
.map(|(_, bidx)| mol.bond(bidx).order.order_int())
.fold(0u8, |acc, x| acc.saturating_add(x))
}
pub fn is_pi_bond(order: BondOrder) -> bool {
matches!(order, BondOrder::Double | BondOrder::Triple | BondOrder::Quadruple)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::atom::Atom;
use crate::bond::BondOrder;
use crate::element::Element;
use crate::molecule::MoleculeBuilder;
fn single_atom(elem: Element) -> Molecule {
let mut b = MoleculeBuilder::new();
b.add_atom(Atom::organic(elem));
b.build()
}
fn two_atoms(e1: Element, e2: Element, order: BondOrder) -> Molecule {
let mut b = MoleculeBuilder::new();
let a = b.add_atom(Atom::organic(e1));
let c = b.add_atom(Atom::organic(e2));
b.add_bond(a, c, order).unwrap();
b.build()
}
#[test]
fn test_methane() {
let mol = single_atom(Element::C);
assert_eq!(implicit_hcount(&mol, AtomIdx(0)), 4);
}
#[test]
fn test_ethane_c() {
let mol = two_atoms(Element::C, Element::C, BondOrder::Single);
assert_eq!(implicit_hcount(&mol, AtomIdx(0)), 3);
assert_eq!(implicit_hcount(&mol, AtomIdx(1)), 3);
}
#[test]
fn test_ethylene_c() {
let mol = two_atoms(Element::C, Element::C, BondOrder::Double);
assert_eq!(implicit_hcount(&mol, AtomIdx(0)), 2);
}
#[test]
fn test_acetylene_c() {
let mol = two_atoms(Element::C, Element::C, BondOrder::Triple);
assert_eq!(implicit_hcount(&mol, AtomIdx(0)), 1);
}
#[test]
fn test_nitrogen_amine() {
let mol = single_atom(Element::N);
assert_eq!(implicit_hcount(&mol, AtomIdx(0)), 3);
}
#[test]
fn test_nitrogen_triple() {
let mol = two_atoms(Element::N, Element::C, BondOrder::Triple);
assert_eq!(implicit_hcount(&mol, AtomIdx(0)), 0);
}
#[test]
fn test_oxygen_ether() {
let mol = single_atom(Element::O);
assert_eq!(implicit_hcount(&mol, AtomIdx(0)), 2);
}
#[test]
fn test_fluorine() {
let mol = single_atom(Element::F);
assert_eq!(implicit_hcount(&mol, AtomIdx(0)), 1);
}
#[test]
fn test_bracket_atom_explicit_h() {
let mut b = MoleculeBuilder::new();
let atom = Atom::bracket(Element::N, None, Default::default(), 4, 1, None);
b.add_atom(atom);
let mol = b.build();
assert_eq!(implicit_hcount(&mol, AtomIdx(0)), 4);
}
#[test]
fn test_hypervalent_sulfur() {
let mut b = MoleculeBuilder::new();
let s = b.add_atom(Atom::organic(Element::S));
for _ in 0..4 {
let c = b.add_atom(Atom::organic(Element::C));
b.add_bond(s, c, BondOrder::Single).unwrap();
}
let mol = b.build();
assert_eq!(implicit_hcount(&mol, AtomIdx(0)), 0);
}
}