1use crate::index::{AtomIndex, BondIndex};
8use crate::optional::OptionalSymbol;
9use std::ops::Range;
10use std::sync::{Arc, OnceLock};
11
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub enum BondOrder {
15 Single,
17 Double,
19 Triple,
21 Quadruple,
23 Aromatic,
25 Polymeric,
27 #[default]
29 Unknown,
30}
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub enum BondProvenance {
35 File,
37 ChemicalComponentDictionary,
39 InferredDistance,
41 User,
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub struct BondRecord {
48 pub atom_a: AtomIndex,
50 pub atom_b: AtomIndex,
52 pub order: BondOrder,
54 pub provenance: BondProvenance,
56 pub kind: OptionalSymbol,
73}
74
75impl BondRecord {
76 #[must_use]
80 pub fn is_metal_coordination(&self, dictionary: &crate::symbol::Interner) -> bool {
81 self.kind
82 .get()
83 .and_then(|symbol| dictionary.resolve(symbol))
84 .is_some_and(|kind| kind.eq_ignore_ascii_case("metalc"))
85 }
86
87 #[must_use]
93 pub const fn unclassified(
94 atom_a: AtomIndex,
95 atom_b: AtomIndex,
96 order: BondOrder,
97 provenance: BondProvenance,
98 ) -> Self {
99 Self {
100 atom_a,
101 atom_b,
102 order,
103 provenance,
104 kind: OptionalSymbol::NONE,
105 }
106 }
107}
108
109#[derive(Clone, Debug)]
111pub struct BondTable {
112 atom_a: Arc<Vec<AtomIndex>>,
113 atom_b: Arc<Vec<AtomIndex>>,
114 order: Arc<Vec<BondOrder>>,
115 provenance: Arc<Vec<BondProvenance>>,
116 kind: Arc<Vec<OptionalSymbol>>,
117 adjacency: Arc<OnceLock<BondAdjacency>>,
118 available: bool,
119}
120
121impl Default for BondTable {
122 fn default() -> Self {
123 Self {
124 atom_a: Arc::new(Vec::new()),
125 atom_b: Arc::new(Vec::new()),
126 order: Arc::new(Vec::new()),
127 provenance: Arc::new(Vec::new()),
128 kind: Arc::new(Vec::new()),
129 adjacency: Arc::new(OnceLock::new()),
130 available: false,
131 }
132 }
133}
134
135impl BondTable {
136 #[must_use]
138 pub fn len(&self) -> usize {
139 self.atom_a.len()
140 }
141
142 #[must_use]
144 pub fn is_empty(&self) -> bool {
145 self.atom_a.is_empty()
146 }
147
148 #[must_use]
150 pub const fn is_available(&self) -> bool {
151 self.available
152 }
153
154 #[must_use]
156 pub fn get(&self, bond: BondIndex) -> Option<BondRecord> {
157 let position = bond.as_usize();
158 Some(BondRecord {
159 atom_a: *self.atom_a.get(position)?,
160 atom_b: *self.atom_b.get(position)?,
161 order: *self.order.get(position)?,
162 provenance: *self.provenance.get(position)?,
163 kind: *self.kind.get(position)?,
164 })
165 }
166
167 #[must_use]
169 pub fn iter(&self) -> impl ExactSizeIterator<Item = BondRecord> + '_ {
170 self.atom_a
171 .iter()
172 .zip(self.atom_b.iter())
173 .zip(self.order.iter())
174 .zip(self.provenance.iter())
175 .zip(self.kind.iter())
176 .map(
177 |((((atom_a, atom_b), order), provenance), kind)| BondRecord {
178 atom_a: *atom_a,
179 atom_b: *atom_b,
180 order: *order,
181 provenance: *provenance,
182 kind: *kind,
183 },
184 )
185 }
186
187 #[must_use]
193 pub fn kinds(&self) -> &[OptionalSymbol] {
194 &self.kind
195 }
196
197 #[must_use]
199 pub fn adjacency(&self, atom_count: u32) -> &BondAdjacency {
200 self.adjacency
201 .get_or_init(|| BondAdjacency::build(self, atom_count))
202 }
203}
204
205#[derive(Debug, Default)]
212pub struct BondTableBuilder {
213 records: Vec<BondRecord>,
214}
215
216impl BondTableBuilder {
217 #[must_use]
219 pub const fn new() -> Self {
220 Self {
221 records: Vec::new(),
222 }
223 }
224
225 pub fn push(&mut self, mut record: BondRecord) {
230 if record.atom_b < record.atom_a {
231 std::mem::swap(&mut record.atom_a, &mut record.atom_b);
232 }
233 self.records.push(record);
234 }
235
236 #[must_use]
238 pub fn finish(self) -> BondTable {
239 self.finish_with_availability(true)
240 }
241
242 #[must_use]
247 pub fn finish_with_availability(mut self, available: bool) -> BondTable {
248 self.records
252 .sort_by_key(|record| (record.atom_a.get(), record.atom_b.get()));
253 self.records
254 .dedup_by_key(|record| (record.atom_a.get(), record.atom_b.get()));
255
256 let mut atom_a = Vec::with_capacity(self.records.len());
257 let mut atom_b = Vec::with_capacity(self.records.len());
258 let mut order = Vec::with_capacity(self.records.len());
259 let mut provenance = Vec::with_capacity(self.records.len());
260 let mut kind = Vec::with_capacity(self.records.len());
261 for record in self.records {
262 atom_a.push(record.atom_a);
263 atom_b.push(record.atom_b);
264 order.push(record.order);
265 provenance.push(record.provenance);
266 kind.push(record.kind);
267 }
268 BondTable {
269 atom_a: Arc::new(atom_a),
270 atom_b: Arc::new(atom_b),
271 order: Arc::new(order),
272 provenance: Arc::new(provenance),
273 kind: Arc::new(kind),
274 adjacency: Arc::new(OnceLock::new()),
275 available,
276 }
277 }
278}
279
280#[derive(Clone, Debug, Default)]
282pub struct BondAdjacency {
283 offsets: Vec<u32>,
284 neighbours: Vec<AtomIndex>,
285}
286
287impl BondAdjacency {
288 fn build(table: &BondTable, atom_count: u32) -> Self {
289 let mut offsets = vec![0u32; atom_count as usize + 1];
290 for record in table.iter() {
291 if record.atom_a.get() < atom_count && record.atom_b.get() < atom_count {
292 offsets[record.atom_a.as_usize() + 1] += 1;
293 offsets[record.atom_b.as_usize() + 1] += 1;
294 }
295 }
296 for position in 1..offsets.len() {
297 offsets[position] += offsets[position - 1];
298 }
299 let neighbour_count = match offsets.last() {
300 Some(count) => *count,
301 None => 0,
302 };
303 let mut neighbours = vec![AtomIndex::new(0); neighbour_count as usize];
304 for record in table.iter() {
308 if record.atom_a.get() >= atom_count || record.atom_b.get() >= atom_count {
309 continue;
310 }
311 insert(&mut neighbours, &mut offsets, record.atom_a, record.atom_b);
312 insert(&mut neighbours, &mut offsets, record.atom_b, record.atom_a);
313 }
314 for position in (1..offsets.len()).rev() {
317 offsets[position] = offsets[position - 1];
318 }
319 offsets[0] = 0;
320 for atom in 0..atom_count as usize {
321 let range = offsets[atom] as usize..offsets[atom + 1] as usize;
322 neighbours[range].sort_unstable();
323 }
324 Self {
325 offsets,
326 neighbours,
327 }
328 }
329
330 #[must_use]
332 pub fn neighbours(&self, atom: AtomIndex) -> &[AtomIndex] {
333 let Some(range) = self.range(atom) else {
334 return &[];
335 };
336 &self.neighbours[range]
337 }
338
339 fn range(&self, atom: AtomIndex) -> Option<Range<usize>> {
340 let next = atom.as_usize().checked_add(1)?;
341 Some(*self.offsets.get(atom.as_usize())? as usize..*self.offsets.get(next)? as usize)
342 }
343}
344
345fn insert(neighbours: &mut [AtomIndex], cursor: &mut [u32], atom: AtomIndex, value: AtomIndex) {
346 let position = cursor[atom.as_usize()] as usize;
347 if let Some(slot) = neighbours.get_mut(position) {
348 *slot = value;
349 cursor[atom.as_usize()] += 1;
350 }
351}
352
353#[cfg(test)]
354#[path = "model_tests.rs"]
355mod tests;