1use omgkit_chem::{perceive_rings, ring_set};
29use omgkit_core::{AtomFlags, BondOrder, MolBuilder};
30use omgkit_io::smarts::{AtomProps, BondProps};
31
32#[derive(Debug, Clone)]
34pub struct MolProps {
35 pub atoms: Vec<AtomProps>,
37 pub bonds: Vec<BondProps>,
39}
40
41impl MolProps {
42 #[must_use]
48 pub fn compute(mol: &MolBuilder) -> Self {
49 let mut scratch = mol.clone();
51 let rings = perceive_rings(&mut scratch);
52 let cycles = ring_set(&scratch);
53
54 let n = mol.num_atoms();
55 let mut ring_count = vec![0u32; n];
56 let mut ring_bond_count = vec![0u32; n];
57 for ring in &cycles {
58 for &a in &ring.atoms {
59 ring_count[a as usize] += 1;
60 }
61 }
62 let mut bond_is_ring = vec![false; mol.num_bonds()];
64 for ring in &cycles {
65 for &b in &ring.bonds {
66 bond_is_ring[b as usize] = true;
67 }
68 }
69 for (bi, &in_ring) in bond_is_ring.iter().enumerate() {
70 if in_ring {
71 let b = mol.bonds()[bi];
72 ring_bond_count[b.begin as usize] += 1;
73 ring_bond_count[b.end as usize] += 1;
74 }
75 }
76
77 let atoms = (0..n)
78 .map(|i| {
79 let a = mol.atoms()[i];
80 AtomProps {
81 atomic_num: a.atomic_num,
82 aromatic: a.flags.contains(AtomFlags::AROMATIC),
83 charge: i32::from(a.formal_charge),
84 isotope: a.isotope,
85 degree: mol.degree(i as u32) as u32,
86 total_hs: u32::from(a.num_explicit_hs)
87 + u32::from(a.num_implicit_hs)
88 + neighbour_hydrogens(mol, i as u32),
89 implicit_hs: u32::from(a.num_implicit_hs),
90 valence: omgkit_core::valence::total_valence_nonstrict(mol, i as u32)
95 .max(0)
96 .unsigned_abs(),
97 ring_count: ring_count[i],
98 min_ring_size: u32::from(rings.atom_min_ring_size[i]),
99 ring_bonds: ring_bond_count[i],
100 chiral_tag: a.chiral_tag,
101 atom_map: a.atom_map,
102 }
103 })
104 .collect();
105
106 let bonds = (0..mol.num_bonds())
107 .map(|i| {
108 let b = mol.bonds()[i];
109 BondProps {
110 order: b.order,
111 in_ring: rings.bond_in_ring[i],
112 direction: b.direction,
113 dative_forward: true,
114 }
115 })
116 .collect();
117
118 Self { atoms, bonds }
119 }
120}
121
122fn neighbour_hydrogens(mol: &MolBuilder, atom: u32) -> u32 {
126 mol.neighbors(atom)
127 .filter(|&(other, _)| mol.atoms()[other as usize].atomic_num == 1)
128 .count() as u32
129}
130
131#[must_use]
133pub fn dative_points_from(mol: &MolBuilder, bond: u32, from: u32) -> bool {
134 let b = mol.bonds()[bond as usize];
135 b.order != BondOrder::Dative || b.begin == from
136}