use crate::core::error::GraphValidationError;
use crate::core::graph::{BondEdge, MolecularGraph};
use crate::core::properties::{Element, GraphBondOrder, Hybridization};
#[derive(Debug, Clone)]
pub struct AnnotatedAtom {
pub id: usize,
pub element: Element,
pub formal_charge: i8,
pub lone_pairs: u8,
pub degree: u8,
pub is_in_ring: bool,
pub smallest_ring_size: Option<u8>,
pub is_aromatic: bool,
pub is_anti_aromatic: bool,
pub is_resonant: bool,
pub has_aromatic_edge: bool,
pub steric_number: u8,
pub hybridization: Hybridization,
}
pub type Ring = Vec<usize>;
#[derive(Debug, Clone)]
pub struct ResonanceSystem {
#[allow(dead_code)]
pub atom_ids: Vec<usize>,
pub bond_ids: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct AnnotatedMolecule {
pub atoms: Vec<AnnotatedAtom>,
pub bonds: Vec<BondEdge>,
pub adjacency: Vec<Vec<(usize, GraphBondOrder)>>,
pub rings: Vec<Ring>,
pub resonance_systems: Vec<ResonanceSystem>,
}
impl AnnotatedMolecule {
pub fn new(graph: &MolecularGraph) -> Result<Self, GraphValidationError> {
let mut adjacency = vec![vec![]; graph.atoms.len()];
for bond in &graph.bonds {
let (u, v) = bond.atom_ids;
if u >= graph.atoms.len() {
return Err(GraphValidationError::MissingAtom { atom_id: u });
}
if v >= graph.atoms.len() {
return Err(GraphValidationError::MissingAtom { atom_id: v });
}
adjacency[u].push((v, bond.order));
adjacency[v].push((u, bond.order));
}
let atoms = graph
.atoms
.iter()
.map(|node| AnnotatedAtom {
id: node.id,
element: node.element,
degree: adjacency[node.id].len() as u8,
formal_charge: 0,
lone_pairs: 0,
is_in_ring: false,
smallest_ring_size: None,
is_aromatic: false,
is_anti_aromatic: false,
is_resonant: false,
has_aromatic_edge: false,
steric_number: 0,
hybridization: Hybridization::Unknown,
})
.collect();
Ok(Self {
atoms,
bonds: graph.bonds.clone(),
adjacency,
rings: Vec::new(),
resonance_systems: Vec::new(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::graph::AtomNode;
use crate::core::properties::GraphBondOrder;
fn water_like_graph() -> MolecularGraph {
let mut graph = MolecularGraph::new();
let oxygen = graph.add_atom(Element::O);
let hydrogen1 = graph.add_atom(Element::H);
let hydrogen2 = graph.add_atom(Element::H);
graph
.add_bond(oxygen, hydrogen1, GraphBondOrder::Single)
.expect("valid bond should be created");
graph
.add_bond(oxygen, hydrogen2, GraphBondOrder::Single)
.expect("valid bond should be created");
graph
}
#[test]
fn annotated_molecule_new_populates_adjacency_and_atoms() {
let graph = water_like_graph();
let molecule = AnnotatedMolecule::new(&graph).expect("graph should be valid");
let adjacency_sizes: Vec<_> = molecule
.adjacency
.iter()
.map(|neighbors| neighbors.len())
.collect();
assert_eq!(adjacency_sizes, vec![2, 1, 1]);
let oxygen_neighbors = &molecule.adjacency[0];
assert!(oxygen_neighbors.contains(&(1, GraphBondOrder::Single)));
assert!(oxygen_neighbors.contains(&(2, GraphBondOrder::Single)));
let oxygen = &molecule.atoms[0];
assert_eq!(oxygen.element, Element::O);
assert_eq!(oxygen.degree, 2);
assert_eq!(oxygen.formal_charge, 0);
assert_eq!(oxygen.lone_pairs, 0);
assert!(!oxygen.is_in_ring);
assert_eq!(oxygen.smallest_ring_size, None);
assert!(!oxygen.is_aromatic);
assert!(!oxygen.is_anti_aromatic);
assert!(!oxygen.is_resonant);
assert_eq!(oxygen.steric_number, 0);
assert_eq!(oxygen.hybridization, Hybridization::Unknown);
assert!(molecule.resonance_systems.is_empty());
}
#[test]
fn annotated_molecule_new_detects_invalid_bond_endpoints() {
let graph = MolecularGraph {
atoms: vec![AtomNode {
id: 0,
element: Element::C,
}],
bonds: vec![BondEdge {
id: 0,
atom_ids: (0, 2),
order: GraphBondOrder::Single,
}],
};
let err = AnnotatedMolecule::new(&graph).expect_err("invalid bond must fail");
match err {
GraphValidationError::MissingAtom { atom_id } => assert_eq!(atom_id, 2),
_ => panic!("unexpected error returned: {err:?}"),
}
}
}