1use crate::index::{AtomIndex, BondIndex};
8use std::ops::Range;
9use std::sync::{Arc, OnceLock};
10
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub enum BondOrder {
14 Single,
16 Double,
18 Triple,
20 Quadruple,
22 Aromatic,
24 Polymeric,
26 #[default]
28 Unknown,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub enum BondProvenance {
34 File,
36 ChemicalComponentDictionary,
38 InferredDistance,
40 User,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub struct BondRecord {
47 pub atom_a: AtomIndex,
49 pub atom_b: AtomIndex,
51 pub order: BondOrder,
53 pub provenance: BondProvenance,
55}
56
57#[derive(Clone, Debug)]
59pub struct BondTable {
60 atom_a: Arc<Vec<AtomIndex>>,
61 atom_b: Arc<Vec<AtomIndex>>,
62 order: Arc<Vec<BondOrder>>,
63 provenance: Arc<Vec<BondProvenance>>,
64 adjacency: Arc<OnceLock<BondAdjacency>>,
65 available: bool,
66}
67
68impl Default for BondTable {
69 fn default() -> Self {
70 Self {
71 atom_a: Arc::new(Vec::new()),
72 atom_b: Arc::new(Vec::new()),
73 order: Arc::new(Vec::new()),
74 provenance: Arc::new(Vec::new()),
75 adjacency: Arc::new(OnceLock::new()),
76 available: false,
77 }
78 }
79}
80
81impl BondTable {
82 #[must_use]
84 pub fn len(&self) -> usize {
85 self.atom_a.len()
86 }
87
88 #[must_use]
90 pub fn is_empty(&self) -> bool {
91 self.atom_a.is_empty()
92 }
93
94 #[must_use]
96 pub const fn is_available(&self) -> bool {
97 self.available
98 }
99
100 #[must_use]
102 pub fn get(&self, bond: BondIndex) -> Option<BondRecord> {
103 let position = bond.as_usize();
104 Some(BondRecord {
105 atom_a: *self.atom_a.get(position)?,
106 atom_b: *self.atom_b.get(position)?,
107 order: *self.order.get(position)?,
108 provenance: *self.provenance.get(position)?,
109 })
110 }
111
112 #[must_use]
114 pub fn iter(&self) -> impl ExactSizeIterator<Item = BondRecord> + '_ {
115 self.atom_a
116 .iter()
117 .zip(self.atom_b.iter())
118 .zip(self.order.iter())
119 .zip(self.provenance.iter())
120 .map(|(((atom_a, atom_b), order), provenance)| BondRecord {
121 atom_a: *atom_a,
122 atom_b: *atom_b,
123 order: *order,
124 provenance: *provenance,
125 })
126 }
127
128 #[must_use]
130 pub fn adjacency(&self, atom_count: u32) -> &BondAdjacency {
131 self.adjacency
132 .get_or_init(|| BondAdjacency::build(self, atom_count))
133 }
134}
135
136#[derive(Debug, Default)]
143pub struct BondTableBuilder {
144 records: Vec<BondRecord>,
145}
146
147impl BondTableBuilder {
148 #[must_use]
150 pub const fn new() -> Self {
151 Self {
152 records: Vec::new(),
153 }
154 }
155
156 pub fn push(&mut self, mut record: BondRecord) {
161 if record.atom_b < record.atom_a {
162 std::mem::swap(&mut record.atom_a, &mut record.atom_b);
163 }
164 self.records.push(record);
165 }
166
167 #[must_use]
169 pub fn finish(self) -> BondTable {
170 self.finish_with_availability(true)
171 }
172
173 #[must_use]
178 pub fn finish_with_availability(mut self, available: bool) -> BondTable {
179 self.records
183 .sort_by_key(|record| (record.atom_a.get(), record.atom_b.get()));
184 self.records
185 .dedup_by_key(|record| (record.atom_a.get(), record.atom_b.get()));
186
187 let mut atom_a = Vec::with_capacity(self.records.len());
188 let mut atom_b = Vec::with_capacity(self.records.len());
189 let mut order = Vec::with_capacity(self.records.len());
190 let mut provenance = Vec::with_capacity(self.records.len());
191 for record in self.records {
192 atom_a.push(record.atom_a);
193 atom_b.push(record.atom_b);
194 order.push(record.order);
195 provenance.push(record.provenance);
196 }
197 BondTable {
198 atom_a: Arc::new(atom_a),
199 atom_b: Arc::new(atom_b),
200 order: Arc::new(order),
201 provenance: Arc::new(provenance),
202 adjacency: Arc::new(OnceLock::new()),
203 available,
204 }
205 }
206}
207
208#[derive(Clone, Debug, Default)]
210pub struct BondAdjacency {
211 offsets: Vec<u32>,
212 neighbours: Vec<AtomIndex>,
213}
214
215impl BondAdjacency {
216 fn build(table: &BondTable, atom_count: u32) -> Self {
217 let mut offsets = vec![0u32; atom_count as usize + 1];
218 for record in table.iter() {
219 if record.atom_a.get() < atom_count && record.atom_b.get() < atom_count {
220 offsets[record.atom_a.as_usize() + 1] += 1;
221 offsets[record.atom_b.as_usize() + 1] += 1;
222 }
223 }
224 for position in 1..offsets.len() {
225 offsets[position] += offsets[position - 1];
226 }
227 let neighbour_count = match offsets.last() {
228 Some(count) => *count,
229 None => 0,
230 };
231 let mut neighbours = vec![AtomIndex::new(0); neighbour_count as usize];
232 for record in table.iter() {
236 if record.atom_a.get() >= atom_count || record.atom_b.get() >= atom_count {
237 continue;
238 }
239 insert(&mut neighbours, &mut offsets, record.atom_a, record.atom_b);
240 insert(&mut neighbours, &mut offsets, record.atom_b, record.atom_a);
241 }
242 for position in (1..offsets.len()).rev() {
245 offsets[position] = offsets[position - 1];
246 }
247 offsets[0] = 0;
248 for atom in 0..atom_count as usize {
249 let range = offsets[atom] as usize..offsets[atom + 1] as usize;
250 neighbours[range].sort_unstable();
251 }
252 Self {
253 offsets,
254 neighbours,
255 }
256 }
257
258 #[must_use]
260 pub fn neighbours(&self, atom: AtomIndex) -> &[AtomIndex] {
261 let Some(range) = self.range(atom) else {
262 return &[];
263 };
264 &self.neighbours[range]
265 }
266
267 fn range(&self, atom: AtomIndex) -> Option<Range<usize>> {
268 let next = atom.as_usize().checked_add(1)?;
269 Some(*self.offsets.get(atom.as_usize())? as usize..*self.offsets.get(next)? as usize)
270 }
271}
272
273fn insert(neighbours: &mut [AtomIndex], cursor: &mut [u32], atom: AtomIndex, value: AtomIndex) {
274 let position = cursor[atom.as_usize()] as usize;
275 if let Some(slot) = neighbours.get_mut(position) {
276 *slot = value;
277 cursor[atom.as_usize()] += 1;
278 }
279}
280
281#[cfg(test)]
282#[path = "model_tests.rs"]
283mod tests;