use molrs::chem::rings::{RingInfo, find_rings};
use molrs::system::element::Element;
use molrs::system::molgraph::PropValue;
use molrs::{AtomId, Atomistic};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BondOrder {
Single,
Double,
Triple,
Aromatic,
}
impl BondOrder {
fn from_order(o: f64) -> Self {
if o >= 2.5 {
BondOrder::Triple
} else if o >= 1.5 {
BondOrder::Double
} else {
BondOrder::Single
}
}
}
#[derive(Debug, Clone)]
pub struct Topo {
pub atom_ids: Vec<AtomId>,
idx_of: std::collections::HashMap<AtomId, usize>,
pub atno: Vec<u8>,
pub formal_charge: Vec<i32>,
pub nbrs: Vec<Vec<usize>>,
pub nbr_order: Vec<Vec<BondOrder>>,
pub nbr_kekule: Vec<Vec<BondOrder>>,
pub rings: RingInfo,
pub ring_idx: Vec<Vec<usize>>,
pub is_aromatic: Vec<bool>,
pub ring_aromatic: Vec<bool>,
}
impl Topo {
pub fn build(mol: &Atomistic) -> Result<Self, String> {
let atom_ids: Vec<AtomId> = mol.atoms().map(|(id, _)| id).collect();
let idx_of: std::collections::HashMap<AtomId, usize> = atom_ids
.iter()
.enumerate()
.map(|(i, &id)| (id, i))
.collect();
let n = atom_ids.len();
let mut atno = vec![0u8; n];
let mut formal_charge = vec![0i32; n];
let mut nbrs = vec![Vec::new(); n];
let mut nbr_order = vec![Vec::new(); n];
let mut nbr_kekule = vec![Vec::new(); n];
for (i, &id) in atom_ids.iter().enumerate() {
let atom = mol.get_atom(id).map_err(|e| e.to_string())?;
let sym = atom.get_str("element").unwrap_or("");
let el = Element::by_symbol(sym).ok_or_else(|| sym.to_string())?;
atno[i] = el.z();
formal_charge[i] = match atom.get("formal_charge") {
Some(PropValue::F64(v)) => v.round() as i32,
Some(PropValue::Int(v)) => *v,
_ => 0,
};
for (nbr_id, order) in mol.neighbor_bonds(id) {
let j = idx_of[&nbr_id];
let bo = BondOrder::from_order(order);
nbrs[i].push(j);
nbr_order[i].push(bo);
nbr_kekule[i].push(bo);
}
}
let rings = find_rings(mol);
let ring_idx: Vec<Vec<usize>> = rings
.rings()
.iter()
.map(|r| r.iter().map(|id| idx_of[id]).collect())
.collect();
let ring_aromatic = vec![false; ring_idx.len()];
Ok(Self {
atom_ids,
idx_of,
atno,
formal_charge,
nbrs,
nbr_order,
nbr_kekule,
rings,
ring_idx,
is_aromatic: vec![false; n],
ring_aromatic,
})
}
pub fn n_atoms(&self) -> usize {
self.atom_ids.len()
}
pub fn id(&self, i: usize) -> AtomId {
self.atom_ids[i]
}
pub fn degree(&self, i: usize) -> usize {
self.nbrs[i].len()
}
pub fn total_degree(&self, i: usize) -> usize {
self.degree(i)
}
pub fn bond_order(&self, i: usize, j: usize) -> Option<BondOrder> {
self.nbrs[i]
.iter()
.position(|&k| k == j)
.map(|p| self.nbr_order[i][p])
}
pub fn total_bond_order(&self, i: usize) -> u32 {
self.nbr_kekule[i]
.iter()
.map(|o| match o {
BondOrder::Single | BondOrder::Aromatic => 1,
BondOrder::Double => 2,
BondOrder::Triple => 3,
})
.sum()
}
pub fn n_h_neighbors(&self, i: usize) -> usize {
self.nbrs[i].iter().filter(|&&j| self.atno[j] == 1).count()
}
pub fn is_atom_in_ring_of_size(&self, i: usize, size: usize) -> bool {
self.rings
.rings_of_size(size)
.iter()
.any(|r| r.iter().any(|&id| self.idx_of[&id] == i))
}
pub fn atoms_in_same_ring_of_size(&self, size: usize, atoms: &[usize]) -> bool {
self.ring_idx
.iter()
.filter(|r| r.len() == size)
.any(|r| atoms.iter().all(|a| r.contains(a)))
}
pub fn is_atom_in_aromatic_ring_of_size(&self, i: usize, size: usize) -> bool {
if !self.is_aromatic[i] {
return false;
}
self.ring_idx
.iter()
.zip(self.ring_aromatic.iter())
.any(|(r, &arom)| arom && r.len() == size && r.contains(&i))
}
}