use crate::sssr::RingSet;
use chematic_core::{AtomIdx, Molecule};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RingSystemKind {
Simple,
Fused,
Spiro,
Bridged,
}
#[derive(Debug, Clone)]
pub struct RingFamily {
pub ring_indices: Vec<usize>,
pub atoms: Vec<AtomIdx>,
pub kind: RingSystemKind,
}
pub fn find_ring_families(mol: &Molecule, sssr: &RingSet) -> Vec<RingFamily> {
if sssr.ring_count() == 0 {
return vec![];
}
let rings = sssr.rings();
let mut parent: Vec<usize> = (0..rings.len()).collect();
fn find(parent: &mut [usize], x: usize) -> usize {
if parent[x] != x {
parent[x] = find(parent, parent[x]);
}
parent[x]
}
fn union(parent: &mut [usize], x: usize, y: usize) {
let px = find(parent, x);
let py = find(parent, y);
if px != py {
parent[px] = py;
}
}
for i in 0..rings.len() {
for j in (i + 1)..rings.len() {
if rings[i].iter().any(|a| rings[j].contains(a)) {
union(&mut parent, i, j);
}
}
}
let mut groups: std::collections::HashMap<usize, Vec<usize>> = std::collections::HashMap::new();
for i in 0..rings.len() {
let root = find(&mut parent, i);
groups.entry(root).or_insert_with(Vec::new).push(i);
}
let mut families = Vec::new();
for ring_indices in groups.into_values() {
let mut atoms = std::collections::HashSet::new();
for &idx in &ring_indices {
for &atom in &rings[idx] {
atoms.insert(atom);
}
}
let mut atoms: Vec<AtomIdx> = atoms.into_iter().collect();
atoms.sort_by_key(|a| a.0);
let kind = classify_ring_system(mol, &ring_indices, rings);
families.push(RingFamily {
ring_indices,
atoms,
kind,
});
}
families.sort_by_key(|f| f.ring_indices.iter().min().copied().unwrap_or(0));
families
}
fn classify_ring_system(mol: &Molecule, ring_indices: &[usize], rings: &[Vec<AtomIdx>]) -> RingSystemKind {
if ring_indices.len() == 1 {
return RingSystemKind::Simple;
}
let mut worst_kind = RingSystemKind::Spiro;
for i in 0..ring_indices.len() {
for j in (i + 1)..ring_indices.len() {
let ring_i = &rings[ring_indices[i]];
let ring_j = &rings[ring_indices[j]];
let shared: Vec<AtomIdx> = ring_i
.iter()
.filter(|a| ring_j.contains(a))
.copied()
.collect();
if shared.is_empty() {
continue; }
let kind = if shared.len() == 1 {
RingSystemKind::Spiro
} else if is_fused_ring(mol, &shared) {
RingSystemKind::Fused
} else {
RingSystemKind::Bridged
};
worst_kind = worst_classification(worst_kind, kind);
}
}
worst_kind
}
fn is_fused_ring(mol: &Molecule, atoms: &[AtomIdx]) -> bool {
if atoms.len() <= 1 {
return true; }
if atoms.len() == 2 {
return mol.bond_between(atoms[0], atoms[1]).is_some();
}
mol.bond_between(atoms[atoms.len() - 1], atoms[0]).is_some()
}
fn worst_classification(a: RingSystemKind, b: RingSystemKind) -> RingSystemKind {
use RingSystemKind::*;
match (a, b) {
(Bridged, _) | (_, Bridged) => Bridged,
(Fused, _) | (_, Fused) => Fused,
_ => Spiro,
}
}
#[cfg(test)]
mod tests {
use super::*;
use chematic_smiles::parse;
#[test]
fn test_ring_families_benzene() {
let mol = parse("c1ccccc1").unwrap();
let sssr = crate::sssr::find_sssr(&mol);
let families = find_ring_families(&mol, &sssr);
assert_eq!(families.len(), 1);
assert_eq!(families[0].kind, RingSystemKind::Simple);
assert_eq!(families[0].ring_indices, vec![0]);
}
#[test]
fn test_ring_families_naphthalene() {
let mol = parse("c1ccc2ccccc2c1").unwrap(); let sssr = crate::sssr::find_sssr(&mol);
let families = find_ring_families(&mol, &sssr);
assert_eq!(families.len(), 1);
assert_eq!(families[0].kind, RingSystemKind::Fused);
assert_eq!(families[0].ring_indices.len(), 2);
}
#[test]
fn test_ring_families_biphenyl() {
let mol = parse("c1ccccc1c1ccccc1").unwrap(); let sssr = crate::sssr::find_sssr(&mol);
let families = find_ring_families(&mol, &sssr);
assert_eq!(families.len(), 2);
assert!(families.iter().all(|f| f.kind == RingSystemKind::Simple));
}
#[test]
fn test_ring_families_spiro() {
let mol = parse("C1CCC2(C1)CCC2").unwrap(); let sssr = crate::sssr::find_sssr(&mol);
let families = find_ring_families(&mol, &sssr);
assert_eq!(families.len(), 1);
assert_eq!(families[0].kind, RingSystemKind::Spiro);
}
#[test]
fn test_ring_families_norbornane() {
let mol = parse("C1CC2CCC1CC2").unwrap(); let sssr = crate::sssr::find_sssr(&mol);
let families = find_ring_families(&mol, &sssr);
assert_eq!(families.len(), 1);
assert_eq!(families[0].kind, RingSystemKind::Bridged);
assert_eq!(families[0].ring_indices.len(), 2);
}
#[test]
fn test_ring_families_adamantane() {
let mol = parse("C1C2CC3CC1CC(C2)C3").unwrap(); let sssr = crate::sssr::find_sssr(&mol);
let families = find_ring_families(&mol, &sssr);
assert_eq!(families.len(), 1);
assert_eq!(families[0].kind, RingSystemKind::Bridged);
}
#[test]
fn test_ring_families_caffeine() {
let mol = parse("CN1C=NC2=C1C(=O)N(C(=O)N2C)C").unwrap();
let sssr = crate::sssr::find_sssr(&mol);
let families = find_ring_families(&mol, &sssr);
assert_eq!(families.len(), 1);
assert_eq!(families[0].kind, RingSystemKind::Fused);
}
#[test]
fn test_ring_families_acyclic() {
let mol = parse("CCCC").unwrap();
let sssr = crate::sssr::find_sssr(&mol);
let families = find_ring_families(&mol, &sssr);
assert_eq!(families.len(), 0);
}
}