use alloc::{string::String, vec::Vec};
use geometric_traits::traits::SparseValuedMatrixRef;
use super::{BondMatrixBuilder, ConcreteAtoms, Smiles, SmilesAtomPolicy};
use crate::{
bond::bond_edge::BondEdge,
errors::{RootError, SubgraphError},
};
#[derive(Debug, Clone)]
pub struct Fragment<AtomPolicy = ConcreteAtoms> {
smiles: Smiles<AtomPolicy>,
parent_of_local: Vec<usize>,
local_of_parent: Vec<usize>,
}
impl<AtomPolicy: SmilesAtomPolicy> Fragment<AtomPolicy> {
#[inline]
#[must_use]
pub fn smiles(&self) -> &Smiles<AtomPolicy> {
&self.smiles
}
#[inline]
#[must_use]
pub fn into_smiles(self) -> Smiles<AtomPolicy> {
self.smiles
}
#[inline]
#[must_use]
pub fn atom_count(&self) -> usize {
self.parent_of_local.len()
}
#[inline]
#[must_use]
pub fn local_id(&self, parent_atom: usize) -> Option<usize> {
self.local_of_parent.get(parent_atom).copied().filter(|&local| local != usize::MAX)
}
#[inline]
#[must_use]
pub fn parent_id(&self, local_atom: usize) -> usize {
self.parent_of_local[local_atom]
}
pub fn render_rooted(&self, parent_atom: usize, isomeric: bool) -> Result<String, RootError> {
let local = self.local_id(parent_atom).ok_or(RootError::AtomNotInFragment(parent_atom))?;
if isomeric {
Ok(self.smiles.render_rooted(local))
} else {
Ok(self.smiles.non_isomeric().render_rooted(local))
}
}
}
impl<AtomPolicy: SmilesAtomPolicy> Smiles<AtomPolicy> {
pub fn fragment_from_atoms(
&self,
atoms: impl IntoIterator<Item = usize>,
) -> Result<Fragment<AtomPolicy>, SubgraphError> {
let node_count = self.nodes().len();
let mut parent_of_local = Vec::new();
let mut local_of_parent = vec![usize::MAX; node_count];
for atom in atoms {
if atom >= node_count {
return Err(SubgraphError::AtomOutOfRange(atom));
}
register_local(&mut parent_of_local, &mut local_of_parent, atom);
}
let mut builder = BondMatrixBuilder::default();
for ((row, column), entry) in self.bond_matrix.sparse_entries() {
if row >= column {
continue;
}
let (local_row, local_column) = (local_of_parent[row], local_of_parent[column]);
if local_row == usize::MAX || local_column == usize::MAX {
continue;
}
builder
.push_edge_with_descriptor(local_row, local_column, entry.descriptor(), None)
.unwrap_or_else(|_| unreachable!("induced fragment preserves a simple graph"));
}
Ok(self.finish_fragment(parent_of_local, local_of_parent, builder))
}
pub fn fragment_from_bonds(
&self,
bonds: impl IntoIterator<Item = BondEdge>,
) -> Result<Fragment<AtomPolicy>, SubgraphError> {
let node_count = self.nodes().len();
let mut parent_of_local = Vec::new();
let mut local_of_parent = vec![usize::MAX; node_count];
let mut builder = BondMatrixBuilder::default();
for edge in bonds {
let [source, target] = edge.endpoints();
if source >= node_count {
return Err(SubgraphError::AtomOutOfRange(source));
}
if target >= node_count {
return Err(SubgraphError::AtomOutOfRange(target));
}
let descriptor = self
.edge_for_node_pair((source, target))
.ok_or(SubgraphError::BondReferencesUnknownAtom(target))?
.descriptor();
let local_source = register_local(&mut parent_of_local, &mut local_of_parent, source);
let local_target = register_local(&mut parent_of_local, &mut local_of_parent, target);
builder
.push_edge_with_descriptor(local_source, local_target, descriptor, None)
.map_err(|_| SubgraphError::BondReferencesUnknownAtom(target))?;
}
Ok(self.finish_fragment(parent_of_local, local_of_parent, builder))
}
fn finish_fragment(
&self,
parent_of_local: Vec<usize>,
local_of_parent: Vec<usize>,
builder: BondMatrixBuilder,
) -> Fragment<AtomPolicy> {
let atom_nodes: Vec<_> =
parent_of_local.iter().map(|&parent| self.atom_nodes[parent]).collect();
let atom_count = atom_nodes.len();
let parsed_stereo_neighbors = vec![Vec::new(); atom_count];
let parent_radical_electrons = self.radical_electron_counts();
let radical_electrons = Some(
parent_of_local
.iter()
.copied()
.map(|parent| parent_radical_electrons[parent])
.collect(),
);
let smiles = Self::from_bond_matrix_parts_with_parsed_stereo_and_radicals(
atom_nodes,
builder.finish(atom_count),
parsed_stereo_neighbors,
radical_electrons,
);
Fragment { smiles, parent_of_local, local_of_parent }
}
}
fn register_local(
parent_of_local: &mut Vec<usize>,
local_of_parent: &mut [usize],
parent_atom: usize,
) -> usize {
let local = &mut local_of_parent[parent_atom];
if *local == usize::MAX {
*local = parent_of_local.len();
parent_of_local.push(parent_atom);
}
*local
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use super::Smiles;
#[test]
fn render_rooted_is_independent_of_fragment_atom_order() {
let smiles: Smiles = "c1[H]N0cc1c[H]0nc1c[H]0N#1c[H]0nccc".parse().unwrap();
let center = 0;
let radius = 3;
let environment = smiles.atom_environment(center, radius).unwrap();
let atoms: Vec<usize> = environment.atoms().collect();
let reversed: Vec<usize> = atoms.iter().rev().copied().collect();
let forward =
smiles.fragment_from_atoms(atoms).unwrap().render_rooted(center, false).unwrap();
let backward =
smiles.fragment_from_atoms(reversed).unwrap().render_rooted(center, false).unwrap();
assert_eq!(forward, backward);
}
}