use std::{
collections::{HashMap, HashSet, VecDeque},
fmt::Display,
};
use bio_files::BondType;
use na_seq::Element::{self, *};
use crate::{
molecules::{Atom, Bond, common::MoleculeCommon, small::MoleculeSmall},
properties::mol_characterization::{Ring, RingType},
};
#[derive(Clone, Debug)]
pub struct RingComponent {
pub num_atoms: u8,
pub ring_type: RingType,
pub num_nitrogens: u8,
}
#[derive(Clone, Debug)]
pub enum ComponentType {
Atom(Element),
Ring(RingComponent),
Chain(usize),
Methyl,
Hydroxyl,
Carbonyl,
Carboxylate,
Amine,
Amide,
Sulfonamide,
Sulfonimide,
}
impl Display for ComponentType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use ComponentType::*;
let v = match self {
Atom(element) => format!("Atom: {}", element),
Ring(ring) => format!("Ring: {:?}", ring.ring_type),
Chain(chain) => format!("Chain: {}", chain),
Methyl => "Methyl".to_string(),
Hydroxyl => "Hydroxyl".to_string(),
Carbonyl => "Carbonyl".to_string(),
Carboxylate => "Carboxylate".to_string(),
Amine => "Amine".to_string(),
Amide => "Amide".to_string(),
Sulfonamide => "Sulfonamide".to_string(),
Sulfonimide => "Sulfonimide".to_string(),
};
write!(f, "{}", v)
}
}
impl ComponentType {
pub fn to_atoms_bonds(&self) -> (Vec<Atom>, Vec<Bond>) {
let a = |element: Element| Atom {
element,
..Default::default()
};
let b = |i: usize, j: usize, bond_type: BondType| Bond {
bond_type,
atom_0_sn: (i + 1) as u32,
atom_1_sn: (j + 1) as u32,
atom_0: i,
atom_1: j,
is_backbone: false,
};
match self {
ComponentType::Atom(el) => (vec![a(*el)], vec![]),
ComponentType::Ring(ring) => {
let n = ring.num_atoms as usize;
let bt = match ring.ring_type {
RingType::Aromatic => BondType::Aromatic,
_ => BondType::Single,
};
let atoms: Vec<Atom> = (0..n).map(|_| a(Carbon)).collect();
let mut bonds: Vec<Bond> = (0..n - 1).map(|i| b(i, i + 1, bt)).collect();
bonds.push(b(n - 1, 0, bt)); (atoms, bonds)
}
ComponentType::Chain(n) => {
let atoms: Vec<Atom> = (0..*n).map(|_| a(Carbon)).collect();
let bonds: Vec<Bond> = (0..*n - 1).map(|i| b(i, i + 1, BondType::Single)).collect();
(atoms, bonds)
}
ComponentType::Methyl => (
vec![a(Carbon), a(Hydrogen), a(Hydrogen), a(Hydrogen)],
vec![
b(0, 1, BondType::Single),
b(0, 2, BondType::Single),
b(0, 3, BondType::Single),
],
),
ComponentType::Hydroxyl => (
vec![a(Oxygen), a(Hydrogen)],
vec![b(0, 1, BondType::Single)],
),
ComponentType::Carbonyl => {
(vec![a(Oxygen), a(Carbon)], vec![b(0, 1, BondType::Double)])
}
ComponentType::Carboxylate => (
vec![a(Carbon), a(Oxygen), a(Oxygen), a(Hydrogen)],
vec![
b(0, 1, BondType::Double), b(0, 2, BondType::Single), b(2, 3, BondType::Single), ],
),
ComponentType::Amine => (
vec![a(Nitrogen), a(Hydrogen), a(Hydrogen)],
vec![b(0, 1, BondType::Single), b(0, 2, BondType::Single)],
),
ComponentType::Amide => (
vec![a(Nitrogen), a(Hydrogen)],
vec![b(0, 1, BondType::Single)],
),
ComponentType::Sulfonamide => (
vec![a(Nitrogen), a(Hydrogen), a(Sulfur), a(Oxygen), a(Oxygen)],
vec![
b(0, 1, BondType::Single), b(0, 2, BondType::Single), b(2, 3, BondType::Double), b(2, 4, BondType::Double), ],
),
ComponentType::Sulfonimide => (
vec![a(Nitrogen), a(Hydrogen), a(Sulfur), a(Oxygen), a(Oxygen)],
vec![
b(0, 1, BondType::Single), b(0, 2, BondType::Double), b(2, 3, BondType::Double), b(2, 4, BondType::Double), ],
),
}
}
}
#[derive(Clone, Debug)]
pub struct Component {
pub comp_type: ComponentType,
pub atoms: Vec<usize>,
}
#[derive(Clone, Debug)]
pub struct Connection {
pub comp_0: usize,
pub atom_0: usize,
pub comp_1: usize,
pub atom_1: usize,
pub shared_atoms: bool,
pub rotatable: bool,
}
#[derive(Clone, Debug)]
pub struct MolComponents {
pub components: Vec<Component>,
pub connections: Vec<Connection>,
}
impl MolComponents {
pub fn new(mol: &MoleculeSmall) -> Option<Self> {
let Some(char) = &mol.characterization else {
return None;
};
let atoms = &mol.common.atoms;
let adj = &mol.common.adjacency_list;
let bonds = &mol.common.bonds;
let n_atoms = atoms.len();
let mut comps: Vec<Component> = Vec::new();
let mut atom_to_comp: HashMap<usize, usize> = HashMap::new();
let mut claimed: HashSet<usize> = HashSet::new();
macro_rules! add_comp {
($comp_type:expr, $comp_atoms:expr) => {{
let ci = comps.len();
let comp_atoms: Vec<usize> = $comp_atoms;
for &a in &comp_atoms {
atom_to_comp.insert(a, ci);
claimed.insert(a);
}
comps.push(Component {
comp_type: $comp_type,
atoms: comp_atoms,
});
}};
}
for cluster in ring_component_clusters(&char.rings) {
let mut cluster_atoms = Vec::new();
let mut ring_type = RingType::Saturated;
for &ri in &cluster {
let ring = &char.rings[ri];
ring_type = merge_ring_type(ring_type, ring.ring_type);
for &a in &ring.atoms {
if !cluster_atoms.contains(&a) {
cluster_atoms.push(a);
}
}
}
cluster_atoms.sort_unstable();
let num_atoms = cluster_atoms.len() as u8;
let num_nitrogens = cluster_atoms
.iter()
.filter(|&&a| atoms[a].element == Nitrogen)
.count() as u8;
add_comp!(
ComponentType::Ring(RingComponent {
num_atoms,
ring_type,
num_nitrogens,
}),
cluster_atoms
);
}
for &c_idx in &char.carboxylate {
if claimed.contains(&c_idx) {
continue;
}
let mut comp_atoms = vec![c_idx]; for &nb in &adj[c_idx] {
if atoms[nb].element == Oxygen && !claimed.contains(&nb) {
comp_atoms.push(nb);
for &h in &adj[nb] {
if atoms[h].element == Hydrogen && !claimed.contains(&h) {
comp_atoms.push(h);
}
}
}
}
add_comp!(ComponentType::Carboxylate, comp_atoms);
}
for &n_idx in &char.sulfonimide {
if claimed.contains(&n_idx) {
continue;
}
let mut comp_atoms = vec![n_idx]; for &nb in &adj[n_idx] {
match atoms[nb].element {
Hydrogen if !claimed.contains(&nb) => comp_atoms.push(nb),
Sulfur if !claimed.contains(&nb) => {
comp_atoms.push(nb);
for &snb in &adj[nb] {
if atoms[snb].element == Oxygen && !claimed.contains(&snb) {
comp_atoms.push(snb);
}
}
}
_ => {}
}
}
add_comp!(ComponentType::Sulfonimide, comp_atoms);
}
for &n_idx in &char.sulfonamide {
if claimed.contains(&n_idx) {
continue;
}
let mut comp_atoms = vec![n_idx]; for &nb in &adj[n_idx] {
match atoms[nb].element {
Hydrogen if !claimed.contains(&nb) => comp_atoms.push(nb),
Sulfur if !claimed.contains(&nb) => {
comp_atoms.push(nb);
for &snb in &adj[nb] {
if atoms[snb].element == Oxygen && !claimed.contains(&snb) {
comp_atoms.push(snb);
}
}
}
_ => {}
}
}
add_comp!(ComponentType::Sulfonamide, comp_atoms);
}
for &n_idx in &char.amides {
if claimed.contains(&n_idx) {
continue;
}
let mut comp_atoms = vec![n_idx]; for &nb in &adj[n_idx] {
if atoms[nb].element == Hydrogen && !claimed.contains(&nb) {
comp_atoms.push(nb);
}
}
add_comp!(ComponentType::Amide, comp_atoms);
}
for &o_idx in &char.carbonyl {
if claimed.contains(&o_idx) {
continue;
}
let mut comp_atoms = vec![o_idx]; for &nb in &adj[o_idx] {
if atoms[nb].element == Carbon && !claimed.contains(&nb) {
comp_atoms.push(nb);
}
}
add_comp!(ComponentType::Carbonyl, comp_atoms);
}
for &n_idx in &char.amines {
if claimed.contains(&n_idx) {
continue;
}
let mut comp_atoms = vec![n_idx]; for &nb in &adj[n_idx] {
if atoms[nb].element == Hydrogen && !claimed.contains(&nb) {
comp_atoms.push(nb);
}
}
add_comp!(ComponentType::Amine, comp_atoms);
}
for &o_idx in &char.hydroxyl {
if claimed.contains(&o_idx) {
continue;
}
let mut comp_atoms = vec![o_idx]; for &nb in &adj[o_idx] {
if atoms[nb].element == Hydrogen && !claimed.contains(&nb) {
comp_atoms.push(nb);
}
}
add_comp!(ComponentType::Hydroxyl, comp_atoms);
}
for c_idx in 0..n_atoms {
let Some(comp_atoms) = methyl_component_atoms(c_idx, atoms, adj, &claimed) else {
continue;
};
add_comp!(ComponentType::Methyl, comp_atoms);
}
let mut chain_seen = vec![false; n_atoms];
for start in 0..n_atoms {
if claimed.contains(&start) || chain_seen[start] {
continue;
}
if atoms[start].element != Carbon {
continue;
}
let mut chain_atoms: Vec<usize> = Vec::new();
let mut queue: VecDeque<usize> = VecDeque::new();
queue.push_back(start);
chain_seen[start] = true;
while let Some(cur) = queue.pop_front() {
chain_atoms.push(cur);
for &nb in &adj[cur] {
if !claimed.contains(&nb) && !chain_seen[nb] && atoms[nb].element == Carbon {
chain_seen[nb] = true;
queue.push_back(nb);
}
}
}
if chain_atoms.len() >= 2 {
let len = chain_atoms.len();
add_comp!(ComponentType::Chain(len), chain_atoms);
}
}
for i in 0..n_atoms {
if !claimed.contains(&i) {
let el = atoms[i].element;
if el != Hydrogen {
add_comp!(ComponentType::Atom(el), vec![i]);
}
}
}
let rotatable_bond_indices: HashSet<_> = char
.rotatable_bonds
.iter()
.map(|bond| bond.bond_i)
.collect();
let mut conns: Vec<Connection> = Vec::new();
for (bond_i, bond) in bonds.iter().enumerate() {
let a = bond.atom_0;
let b = bond.atom_1;
let (Some(&ca), Some(&cb)) = (atom_to_comp.get(&a), atom_to_comp.get(&b)) else {
continue;
};
if ca == cb {
continue; }
let atom_0 = comps[ca].atoms.iter().position(|&x| x == a).unwrap_or(0);
let atom_1 = comps[cb].atoms.iter().position(|&x| x == b).unwrap_or(0);
let shared_atoms = comps[ca]
.atoms
.iter()
.any(|atom_i| comps[cb].atoms.contains(atom_i));
conns.push(Connection {
comp_0: ca,
atom_0,
comp_1: cb,
atom_1,
shared_atoms,
rotatable: rotatable_bond_indices.contains(&bond_i),
});
}
Some(Self {
components: comps,
connections: conns,
})
}
pub fn to_atoms_bonds(&self) -> (Vec<Atom>, Vec<Bond>) {
let mut atoms: Vec<Atom> = Vec::new();
let mut bonds: Vec<Bond> = Vec::new();
let mut comp_offsets: Vec<usize> = Vec::with_capacity(self.components.len());
for comp in &self.components {
let offset = atoms.len();
comp_offsets.push(offset);
let (comp_atoms, comp_bonds) = comp.comp_type.to_atoms_bonds();
for cb in comp_bonds {
bonds.push(Bond {
atom_0: cb.atom_0 + offset,
atom_1: cb.atom_1 + offset,
..cb
});
}
atoms.extend(comp_atoms);
}
for con in &self.connections {
let a0 = comp_offsets[con.comp_0] + con.atom_0;
let a1 = comp_offsets[con.comp_1] + con.atom_1;
bonds.push(Bond {
bond_type: BondType::Single,
atom_0_sn: 0, atom_1_sn: 0,
atom_0: a0,
atom_1: a1,
is_backbone: false,
});
}
let mut mol = MoleculeCommon::new(String::new(), atoms, bonds, HashMap::new(), None);
mol.reassign_sns();
(mol.atoms, mol.bonds)
}
}
pub fn build_adjacency_list_conn(conns: &[Connection], comps_len: usize) -> Vec<Vec<usize>> {
let mut result: Vec<HashSet<usize>> = vec![HashSet::new(); comps_len];
for conn in conns {
if conn.comp_0 >= comps_len || conn.comp_1 >= comps_len || conn.comp_0 == conn.comp_1 {
continue;
}
result[conn.comp_0].insert(conn.comp_1);
result[conn.comp_1].insert(conn.comp_0);
}
result
.into_iter()
.map(|nbrs| {
let mut nbrs: Vec<_> = nbrs.into_iter().collect();
nbrs.sort_unstable();
nbrs
})
.collect()
}
fn merge_ring_type(best: RingType, next: RingType) -> RingType {
match next {
RingType::Aromatic => RingType::Aromatic,
RingType::Aliphatic if best != RingType::Aromatic => RingType::Aliphatic,
RingType::Aliphatic | RingType::Saturated => best,
}
}
fn methyl_component_atoms(
c_idx: usize,
atoms: &[Atom],
adj: &[Vec<usize>],
claimed: &HashSet<usize>,
) -> Option<Vec<usize>> {
if claimed.contains(&c_idx) || atoms[c_idx].element != Carbon {
return None;
}
let mut hydrogens = Vec::new();
let mut heavy_neighbors = 0usize;
for &nb in &adj[c_idx] {
match atoms[nb].element {
Hydrogen if !claimed.contains(&nb) => hydrogens.push(nb),
Hydrogen => return None,
_ => heavy_neighbors += 1,
}
}
if hydrogens.len() == 3 && heavy_neighbors == 1 {
let mut comp_atoms = vec![c_idx];
comp_atoms.extend(hydrogens);
Some(comp_atoms)
} else {
None
}
}
fn ring_component_clusters(rings: &[Ring]) -> Vec<Vec<usize>> {
let n = rings.len();
if n == 0 {
return Vec::new();
}
let mut ring_adj = vec![Vec::new(); n];
for i in 0..n {
for j in (i + 1)..n {
if rings[i]
.atoms
.iter()
.any(|atom_i| rings[j].atoms.contains(atom_i))
{
ring_adj[i].push(j);
ring_adj[j].push(i);
}
}
}
let mut seen = vec![false; n];
let mut clusters = Vec::new();
for start in 0..n {
if seen[start] {
continue;
}
let mut queue = VecDeque::new();
let mut cluster = Vec::new();
seen[start] = true;
queue.push_back(start);
while let Some(cur) = queue.pop_front() {
cluster.push(cur);
for &next in &ring_adj[cur] {
if !seen[next] {
seen[next] = true;
queue.push_back(next);
}
}
}
cluster.sort_unstable();
clusters.push(cluster);
}
clusters.sort();
clusters
}