use crate::graph::{AdjacencyList, is_hydrogen};
use crate::molecule::Molecule;
fn default_valence(element: &str, charge: i8) -> Option<u8> {
let elem = element.trim();
let upper: String = elem
.chars()
.next()
.map(|c| c.to_uppercase().collect::<String>())
.unwrap_or_default()
+ &elem.chars().skip(1).collect::<String>().to_lowercase();
match (upper.as_str(), charge) {
("H", 0) => Some(1),
("H", 1) => Some(0),
("H", -1) => Some(0),
("D", 0) | ("T", 0) => Some(1),
("C", 0) => Some(4),
("C", -1) => Some(3),
("C", 1) => Some(3),
("N", 0) => Some(3),
("N", 1) => Some(4),
("N", -1) => Some(2),
("O", 0) => Some(2),
("O", 1) => Some(3),
("O", -1) => Some(1),
("F", 0) => Some(1),
("P", 0) => Some(3), ("P", 1) => Some(4),
("S", 0) => Some(2), ("S", 1) => Some(3),
("S", -1) => Some(1),
("Cl", 0) => Some(1),
("Br", 0) => Some(1),
("I", 0) => Some(1),
("Si", 0) => Some(4),
("B", 0) => Some(3),
("B", -1) => Some(4),
("Se", 0) => Some(2),
("As", 0) => Some(3),
(
"Na" | "K" | "Li" | "Ca" | "Mg" | "Fe" | "Cu" | "Zn" | "Mn" | "Co" | "Ni" | "Pt" | "Au"
| "Al",
_,
) => Some(0),
_ => None,
}
}
pub fn atom_degree(mol: &Molecule, idx: usize) -> usize {
let adj = AdjacencyList::from_molecule(mol);
adj.degree(idx)
}
pub fn bond_order_sum(mol: &Molecule, idx: usize) -> f64 {
mol.bonds
.iter()
.filter(|b| b.contains_atom(idx))
.map(|b| b.order.order())
.sum()
}
pub fn bond_order_sum_int(mol: &Molecule, idx: usize) -> u8 {
let sum: f64 = bond_order_sum(mol, idx);
sum.round() as u8
}
pub fn implicit_hydrogen_count(mol: &Molecule, idx: usize) -> u8 {
let atom = match mol.atoms.get(idx) {
Some(a) => a,
None => return 0,
};
if let Some(hc) = atom.hydrogen_count {
if hc > 0 {
return hc.saturating_sub(1); }
}
let valence = match default_valence(&atom.element, atom.formal_charge) {
Some(v) => v,
None => return 0,
};
let bo_sum = bond_order_sum_int(mol, idx);
valence.saturating_sub(bo_sum)
}
pub fn total_hydrogen_count(mol: &Molecule, idx: usize) -> u8 {
let implicit = implicit_hydrogen_count(mol, idx);
let explicit = explicit_hydrogen_count(mol, idx);
implicit.saturating_add(explicit)
}
pub fn explicit_hydrogen_count(mol: &Molecule, idx: usize) -> u8 {
let adj = AdjacencyList::from_molecule(mol);
adj.neighbors(idx)
.iter()
.filter(|(neighbor, _)| {
mol.atoms
.get(*neighbor)
.map(|a| is_hydrogen(&a.element))
.unwrap_or(false)
})
.count() as u8
}
pub fn all_atom_degrees(mol: &Molecule) -> Vec<usize> {
let adj = AdjacencyList::from_molecule(mol);
adj.degrees().to_vec()
}
pub fn all_implicit_hydrogen_counts(mol: &Molecule) -> Vec<u8> {
(0..mol.atom_count())
.map(|i| implicit_hydrogen_count(mol, i))
.collect()
}
pub fn all_total_hydrogen_counts(mol: &Molecule) -> Vec<u8> {
(0..mol.atom_count())
.map(|i| total_hydrogen_count(mol, i))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::atom::Atom;
use crate::bond::{Bond, BondOrder};
fn make_ethanol() -> Molecule {
let mut mol = Molecule::new("ethanol");
mol.atoms.push(Atom::new(0, "C", 0.0, 0.0, 0.0)); mol.atoms.push(Atom::new(1, "C", 1.5, 0.0, 0.0)); mol.atoms.push(Atom::new(2, "O", 2.5, 0.0, 0.0)); mol.bonds.push(Bond::new(0, 1, BondOrder::Single));
mol.bonds.push(Bond::new(1, 2, BondOrder::Single));
mol
}
#[test]
fn test_atom_degree() {
let mol = make_ethanol();
assert_eq!(atom_degree(&mol, 0), 1);
assert_eq!(atom_degree(&mol, 1), 2);
assert_eq!(atom_degree(&mol, 2), 1);
}
#[test]
fn test_bond_order_sum_single() {
let mol = make_ethanol();
assert!((bond_order_sum(&mol, 0) - 1.0).abs() < 1e-10);
assert!((bond_order_sum(&mol, 1) - 2.0).abs() < 1e-10);
}
#[test]
fn test_bond_order_sum_double() {
let mut mol = Molecule::new("CO");
mol.atoms.push(Atom::new(0, "C", 0.0, 0.0, 0.0));
mol.atoms.push(Atom::new(1, "O", 1.2, 0.0, 0.0));
mol.bonds.push(Bond::new(0, 1, BondOrder::Double));
assert!((bond_order_sum(&mol, 0) - 2.0).abs() < 1e-10);
}
#[test]
fn test_implicit_h_methanol_skeleton() {
let mut mol = Molecule::new("methanol_skel");
mol.atoms.push(Atom::new(0, "C", 0.0, 0.0, 0.0));
mol.atoms.push(Atom::new(1, "O", 1.4, 0.0, 0.0));
mol.bonds.push(Bond::new(0, 1, BondOrder::Single));
assert_eq!(implicit_hydrogen_count(&mol, 0), 3); assert_eq!(implicit_hydrogen_count(&mol, 1), 1); }
#[test]
fn test_implicit_h_formaldehyde() {
let mut mol = Molecule::new("formaldehyde_skel");
mol.atoms.push(Atom::new(0, "C", 0.0, 0.0, 0.0));
mol.atoms.push(Atom::new(1, "O", 1.2, 0.0, 0.0));
mol.bonds.push(Bond::new(0, 1, BondOrder::Double));
assert_eq!(implicit_hydrogen_count(&mol, 0), 2); assert_eq!(implicit_hydrogen_count(&mol, 1), 0); }
#[test]
fn test_implicit_h_nitrogen() {
let mut mol = Molecule::new("ammonia");
mol.atoms.push(Atom::new(0, "N", 0.0, 0.0, 0.0));
assert_eq!(implicit_hydrogen_count(&mol, 0), 3);
}
#[test]
fn test_implicit_h_charged_nitrogen() {
let mut mol = Molecule::new("ammonium");
let mut atom = Atom::new(0, "N", 0.0, 0.0, 0.0);
atom.formal_charge = 1;
mol.atoms.push(atom);
assert_eq!(implicit_hydrogen_count(&mol, 0), 4);
}
#[test]
fn test_total_h_methane_partial() {
let mut mol = Molecule::new("methane_partial");
mol.atoms.push(Atom::new(0, "C", 0.0, 0.0, 0.0));
mol.atoms.push(Atom::new(1, "H", 1.0, 0.0, 0.0));
mol.atoms.push(Atom::new(2, "H", -1.0, 0.0, 0.0));
mol.bonds.push(Bond::new(0, 1, BondOrder::Single));
mol.bonds.push(Bond::new(0, 2, BondOrder::Single));
assert_eq!(total_hydrogen_count(&mol, 0), 4);
assert_eq!(explicit_hydrogen_count(&mol, 0), 2);
assert_eq!(implicit_hydrogen_count(&mol, 0), 2);
}
#[test]
fn test_implicit_h_aromatic() {
let mut mol = Molecule::new("benzene");
for i in 0..6 {
mol.atoms.push(Atom::new(i, "C", 0.0, 0.0, 0.0));
}
for i in 0..6 {
mol.bonds
.push(Bond::new(i, (i + 1) % 6, BondOrder::Aromatic));
}
assert_eq!(implicit_hydrogen_count(&mol, 0), 1);
}
#[test]
fn test_all_atom_degrees() {
let mol = make_ethanol();
let degrees = all_atom_degrees(&mol);
assert_eq!(degrees, vec![1, 2, 1]);
}
#[test]
fn test_all_implicit_h() {
let mol = make_ethanol();
let h_counts = all_implicit_hydrogen_counts(&mol);
assert_eq!(h_counts, vec![3, 2, 1]); }
#[test]
fn test_default_valence_unknown() {
assert_eq!(default_valence("Xx", 0), None);
}
#[test]
fn test_sdf_hydrogen_count_field() {
let mut mol = Molecule::new("test");
let mut atom = Atom::new(0, "C", 0.0, 0.0, 0.0);
atom.hydrogen_count = Some(2); mol.atoms.push(atom);
assert_eq!(implicit_hydrogen_count(&mol, 0), 1);
}
}