use std::collections::HashMap;
use molrs::chem::rings::{RingInfo, find_rings};
use molrs::system::atomistic::{AtomId, Atomistic};
use molrs::system::element::Element;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Hybridization {
Sp,
Sp2,
Sp3,
Other,
}
#[derive(Clone, Debug)]
pub struct PerceivedAtom {
pub element: Element,
pub hybridization: Hybridization,
pub aromatic: bool,
pub conjugated: bool,
pub degree: usize,
pub total_valence: f64,
}
pub struct Perceived {
pub atom_ids: Vec<AtomId>,
pub atoms: Vec<PerceivedAtom>,
pub adj: Vec<Vec<usize>>,
pub order: HashMap<(usize, usize), f64>,
pub aromatic_bond: HashMap<(usize, usize), bool>,
pub rings: RingInfo,
pub ring_idx: Vec<Vec<usize>>,
}
impl Perceived {
pub fn bond_order(&self, i: usize, j: usize) -> f64 {
let key = if i < j { (i, j) } else { (j, i) };
self.order.get(&key).copied().unwrap_or(0.0)
}
pub fn is_aromatic_bond(&self, i: usize, j: usize) -> bool {
let key = if i < j { (i, j) } else { (j, i) };
self.aromatic_bond.get(&key).copied().unwrap_or(false)
}
}
fn element_of(mol: &Atomistic, id: AtomId) -> Element {
mol.get_atom(id)
.ok()
.and_then(|a| a.get_str("element").and_then(Element::by_symbol))
.unwrap_or(Element::C)
}
pub fn perceive(mol: &Atomistic) -> Perceived {
let atom_ids: Vec<AtomId> = mol.atoms().map(|(id, _)| id).collect();
let id_to_idx: HashMap<AtomId, usize> = atom_ids
.iter()
.enumerate()
.map(|(i, &id)| (id, i))
.collect();
let n = atom_ids.len();
let _ = &id_to_idx;
let mut adj = vec![Vec::new(); n];
let mut order: HashMap<(usize, usize), f64> = HashMap::new();
for (i, &aid) in atom_ids.iter().enumerate() {
let mut nbrs: Vec<usize> = Vec::new();
for (nid, ord) in mol.neighbor_bonds(aid) {
if let Some(&j) = id_to_idx.get(&nid) {
nbrs.push(j);
let key = if i < j { (i, j) } else { (j, i) };
order.insert(key, ord);
}
}
nbrs.sort_unstable();
nbrs.dedup();
adj[i] = nbrs;
}
let rings = find_rings(mol);
let ring_idx: Vec<Vec<usize>> = rings
.rings()
.iter()
.map(|r| {
r.iter()
.filter_map(|aid| id_to_idx.get(aid).copied())
.collect()
})
.collect();
let mut aromatic_atom = vec![false; n];
{
let mut probe = mol.clone();
molrs::perceive_aromaticity(&mut probe);
for (i, (_, atom)) in probe.atoms().enumerate().take(n) {
if atom.get_int("is_aromatic") == Some(1) {
aromatic_atom[i] = true;
}
}
}
let mut atoms: Vec<PerceivedAtom> = Vec::with_capacity(n);
for (i, &aid) in atom_ids.iter().enumerate() {
let element = element_of(mol, aid);
let degree = adj[i].len();
let mut max_order = 0.0_f64;
let mut valence = 0.0_f64;
for &j in &adj[i] {
let o = {
let key = if i < j { (i, j) } else { (j, i) };
order.get(&key).copied().unwrap_or(1.0)
};
max_order = max_order.max(o);
valence += o;
}
let hybridization = if aromatic_atom[i] {
Hybridization::Sp2
} else if max_order >= 2.5 {
Hybridization::Sp
} else if (max_order - 2.0).abs() < 0.25 {
let n_double = adj[i]
.iter()
.filter(|&&j| {
let key = if i < j { (i, j) } else { (j, i) };
order.get(&key).copied().unwrap_or(0.0) >= 1.75
})
.count();
if n_double >= 2 {
Hybridization::Sp
} else {
Hybridization::Sp2
}
} else if max_order >= 1.5 {
Hybridization::Sp2
} else if degree == 0 {
Hybridization::Other
} else {
Hybridization::Sp3
};
atoms.push(PerceivedAtom {
element,
hybridization,
aromatic: aromatic_atom[i],
conjugated: false,
degree,
total_valence: valence,
});
}
let is_carbonyl_c = |i: usize| -> bool {
if element_of(mol, atom_ids[i]).symbol() != "C" {
return false;
}
adj[i].iter().any(|&j| {
let key = if i < j { (i, j) } else { (j, i) };
let o = order.get(&key).copied().unwrap_or(0.0);
let s = element_of(mol, atom_ids[j]).symbol();
o >= 1.75 && (s == "O" || s == "N")
})
};
let mut upgrade_sp2 = vec![false; n];
for i in 0..n {
let sym = element_of(mol, atom_ids[i]).symbol();
if atoms[i].hybridization != Hybridization::Sp3 {
continue;
}
let eligible = (sym == "O" && atoms[i].degree <= 2) || (sym == "N" && atoms[i].degree == 3);
if eligible && adj[i].iter().any(|&j| is_carbonyl_c(j)) {
upgrade_sp2[i] = true;
}
}
for i in 0..n {
if upgrade_sp2[i] {
atoms[i].hybridization = Hybridization::Sp2;
}
}
let mut conj_atom = vec![false; n];
for i in 0..n {
for &j in &adj[i] {
if j <= i {
continue;
}
let hi = atoms[i].hybridization;
let hj = atoms[j].hybridization;
let pi_i = matches!(hi, Hybridization::Sp | Hybridization::Sp2);
let pi_j = matches!(hj, Hybridization::Sp | Hybridization::Sp2);
if pi_i && pi_j {
conj_atom[i] = true;
conj_atom[j] = true;
}
}
}
for i in 0..n {
atoms[i].conjugated = conj_atom[i] || atoms[i].aromatic;
}
let mut aromatic_bond: HashMap<(usize, usize), bool> = HashMap::new();
for i in 0..n {
for &j in &adj[i] {
if j <= i {
continue;
}
let arom = aromatic_atom[i]
&& aromatic_atom[j]
&& rings.is_atom_in_ring(atom_ids[i])
&& rings.is_atom_in_ring(atom_ids[j])
&& bond_shares_ring(&ring_idx, i, j);
aromatic_bond.insert((i, j), arom);
}
}
Perceived {
atom_ids,
atoms,
adj,
order,
aromatic_bond,
rings,
ring_idx,
}
}
fn bond_shares_ring(ring_idx: &[Vec<usize>], i: usize, j: usize) -> bool {
for ring in ring_idx {
let rsize = ring.len();
for w in 0..rsize {
let a = ring[w];
let b = ring[(w + 1) % rsize];
if (a == i && b == j) || (a == j && b == i) {
return true;
}
}
}
false
}