use omgkit_core::{AtomFlags, BondData, BondDirection, BondOrder, ChiralTag, MolBuilder};
pub fn remove_hs(mol: &mut MolBuilder) -> usize {
let n = mol.num_atoms();
let doomed: Vec<bool> = (0..n as u32).map(|a| is_removable(mol, a)).collect();
let n_removed = doomed.iter().filter(|&&d| d).count();
if n_removed == 0 {
return 0;
}
let mut new_idx = vec![u32::MAX; n];
let mut out = MolBuilder::with_capacity(n - n_removed, mol.num_bonds());
for a in 0..n as u32 {
if doomed[a as usize] {
continue;
}
let mut data = mol.atoms()[a as usize];
let positions: Vec<usize> = mol
.neighbors(a)
.enumerate()
.filter(|(_, (other, _))| doomed[*other as usize])
.map(|(pos, _)| pos)
.collect();
if !positions.is_empty() {
let merged = u8::try_from(positions.len()).unwrap_or(u8::MAX);
if data.flags.contains(AtomFlags::NO_IMPLICIT) {
data.num_explicit_hs = data.num_explicit_hs.saturating_add(merged);
} else {
data.num_implicit_hs = data.num_implicit_hs.saturating_add(merged);
}
data.chiral_tag = rebased_tag(data.chiral_tag, &positions);
}
new_idx[a as usize] = out.add_atom_data(data);
}
for b in mol.bonds() {
if doomed[b.begin as usize] || doomed[b.end as usize] {
continue;
}
let mut nb = *b;
nb.begin = new_idx[b.begin as usize];
nb.end = new_idx[b.end as usize];
nb.stereo_atoms = [
translate(b.stereo_atoms[0], &new_idx),
translate(b.stereo_atoms[1], &new_idx),
];
let _ = out.add_bond_data(nb);
}
if let Some(name) = mol.name() {
out.set_name(name.to_string());
}
*mol = out;
n_removed
}
fn translate(idx: u32, new_idx: &[u32]) -> u32 {
if idx == BondData::NO_STEREO_ATOM {
return BondData::NO_STEREO_ATOM;
}
new_idx
.get(idx as usize)
.copied()
.filter(|&v| v != u32::MAX)
.unwrap_or(BondData::NO_STEREO_ATOM)
}
fn rebased_tag(tag: ChiralTag, removed_positions: &[usize]) -> ChiralTag {
if !tag.is_tetrahedral() || removed_positions.len() != 1 {
return tag;
}
let k = removed_positions[0];
if k.abs_diff(1) % 2 == 1 {
tag.inverted()
} else {
tag
}
}
#[must_use]
pub fn is_removable(mol: &MolBuilder, atom: u32) -> bool {
let Some(&a) = mol.atoms().get(atom as usize) else {
return false;
};
if a.atomic_num != 1 {
return false;
}
if a.isotope != 0
|| a.formal_charge != 0
|| a.atom_map != 0
|| a.num_radical_electrons != 0
|| a.flags.contains(AtomFlags::AROMATIC)
{
return false;
}
if a.num_explicit_hs != 0 {
return false;
}
let mut it = mol.neighbors(atom);
let Some((other, bond)) = it.next() else {
return false; };
if it.next().is_some() {
return false; }
let b = mol.bonds()[bond as usize];
if b.order != BondOrder::Single || b.direction != BondDirection::None {
return false;
}
let host = mol.atoms()[other as usize];
if host.atomic_num == 1 || host.atomic_num == 0 {
return false;
}
!mol.bonds()
.iter()
.any(|bb| bb.stereo_atoms[0] == atom || bb.stereo_atoms[1] == atom)
}