1use crate::error::{Error, Result};
45use crate::types::{
46 AtomFlags, BondDirection, BondFlags, BondOrder, BondStereo, ChiralTag, Hybridization,
47};
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct AtomData {
54 pub atomic_num: u8,
56 pub formal_charge: i8,
58 pub isotope: u16,
60 pub num_explicit_hs: u8,
62 pub num_implicit_hs: u8,
64 pub num_radical_electrons: u8,
69 pub atom_map: u16,
71 pub chiral_tag: ChiralTag,
73 pub stereo_perm: u8,
95 pub hybridization: Hybridization,
97 pub flags: AtomFlags,
99}
100
101impl AtomData {
102 #[must_use]
104 pub fn new(atomic_num: u8) -> Self {
105 Self {
106 atomic_num,
107 formal_charge: 0,
108 isotope: 0,
109 num_explicit_hs: 0,
110 num_implicit_hs: 0,
111 num_radical_electrons: 0,
112 atom_map: 0,
113 chiral_tag: ChiralTag::Unspecified,
114 stereo_perm: 0,
115 hybridization: Hybridization::Unspecified,
116 flags: AtomFlags::NONE,
117 }
118 }
119}
120
121impl Default for AtomData {
122 fn default() -> Self {
123 Self::new(0)
124 }
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub struct BondData {
130 pub begin: u32,
132 pub end: u32,
134 pub order: BondOrder,
136 pub direction: BondDirection,
138 pub stereo: BondStereo,
141 pub stereo_atoms: [u32; 2],
155 pub flags: BondFlags,
157}
158
159impl BondData {
160 pub const NO_STEREO_ATOM: u32 = u32::MAX;
162
163 #[must_use]
165 pub fn new(begin: u32, end: u32, order: BondOrder) -> Self {
166 Self {
167 begin,
168 end,
169 order,
170 direction: BondDirection::None,
171 stereo: BondStereo::None,
172 stereo_atoms: [Self::NO_STEREO_ATOM; 2],
173 flags: BondFlags::NONE,
174 }
175 }
176
177 #[must_use]
185 pub fn valence_contribution_to(&self, atom: u32) -> f32 {
186 if atom != self.begin && atom != self.end {
187 return 0.0;
188 }
189 if self.order == BondOrder::Dative && atom != self.end {
190 return 0.0; }
192 self.order.as_double()
193 }
194
195 #[must_use]
197 pub fn other_end(&self, from: u32) -> Option<u32> {
198 if from == self.begin {
199 Some(self.end)
200 } else if from == self.end {
201 Some(self.begin)
202 } else {
203 None
204 }
205 }
206}
207
208const NO_HALF: u32 = u32::MAX;
210
211#[derive(Debug, Clone, Default)]
215pub struct MolBuilder {
216 atoms: Vec<AtomData>,
217 bonds: Vec<BondData>,
218 name: Option<String>,
219
220 first_half: Vec<u32>,
227 last_half: Vec<u32>,
229 next_half: Vec<u32>,
231 degree: Vec<u32>,
233}
234
235impl MolBuilder {
236 #[must_use]
238 pub fn new() -> Self {
239 Self::default()
240 }
241
242 #[must_use]
244 pub fn with_capacity(n_atoms: usize, n_bonds: usize) -> Self {
245 Self {
246 atoms: Vec::with_capacity(n_atoms),
247 bonds: Vec::with_capacity(n_bonds),
248 name: None,
249 first_half: Vec::with_capacity(n_atoms),
250 last_half: Vec::with_capacity(n_atoms),
251 next_half: Vec::with_capacity(n_bonds * 2),
252 degree: Vec::with_capacity(n_atoms),
253 }
254 }
255
256 pub fn add_atom(&mut self, atomic_num: u8) -> u32 {
258 self.add_atom_data(AtomData::new(atomic_num))
259 }
260
261 pub fn add_atom_data(&mut self, atom: AtomData) -> u32 {
263 let idx = self.atoms.len() as u32;
264 self.atoms.push(atom);
265 self.first_half.push(NO_HALF);
266 self.last_half.push(NO_HALF);
267 self.degree.push(0);
268 idx
269 }
270
271 pub fn add_bond(&mut self, begin: u32, end: u32, order: BondOrder) -> Result<u32> {
277 self.add_bond_data(BondData::new(begin, end, order))
278 }
279
280 pub fn add_bond_data(&mut self, bond: BondData) -> Result<u32> {
291 let n = self.atoms.len() as u32;
292 if bond.begin >= n || bond.end >= n {
293 return Err(Error::AtomIndexOutOfRange {
294 index: bond.begin.max(bond.end),
295 num_atoms: n,
296 });
297 }
298 if bond.begin == bond.end {
299 return Err(Error::SelfLoop { atom: bond.begin });
300 }
301 assert!(
302 self.bonds.len() < (u32::MAX / 2) as usize,
303 "键数超出半边编号能表示的范围"
304 );
305 let idx = self.bonds.len() as u32;
306 self.bonds.push(bond);
307 self.link_half(bond.begin, idx * 2);
308 self.link_half(bond.end, idx * 2 + 1);
309 Ok(idx)
310 }
311
312 pub fn swap_bond_ends(&mut self, bond: u32) -> Result<()> {
330 let num_bonds = self.bonds.len() as u32;
331 let b = self
332 .bonds
333 .get_mut(bond as usize)
334 .ok_or(Error::BondIndexOutOfRange {
335 index: bond,
336 num_bonds,
337 })?;
338 std::mem::swap(&mut b.begin, &mut b.end);
339 self.rebuild_index();
340 Ok(())
341 }
342
343 fn rebuild_index(&mut self) {
345 let n = self.atoms.len();
346 self.first_half.clear();
347 self.first_half.resize(n, NO_HALF);
348 self.last_half.clear();
349 self.last_half.resize(n, NO_HALF);
350 self.degree.clear();
351 self.degree.resize(n, 0);
352 self.next_half.clear();
353
354 for i in 0..self.bonds.len() {
355 let (begin, end) = (self.bonds[i].begin, self.bonds[i].end);
356 self.link_half(begin, (i * 2) as u32);
357 self.link_half(end, (i * 2 + 1) as u32);
358 }
359 }
360
361 fn link_half(&mut self, atom: u32, half: u32) {
365 debug_assert_eq!(
366 self.next_half.len() as u32,
367 half,
368 "半边必须按编号顺序追加,否则 next_half 的下标语义就断了"
369 );
370 self.next_half.push(NO_HALF);
371
372 let a = atom as usize;
373 let tail = self.last_half[a];
374 if tail == NO_HALF {
375 self.first_half[a] = half;
376 } else {
377 self.next_half[tail as usize] = half;
378 }
379 self.last_half[a] = half;
380 self.degree[a] += 1;
381 }
382
383 #[must_use]
390 pub fn neighbors(&self, atom: u32) -> Neighbors<'_> {
391 let head = self
392 .first_half
393 .get(atom as usize)
394 .copied()
395 .unwrap_or(NO_HALF);
396 Neighbors {
397 mol: self,
398 half: head,
399 }
400 }
401
402 #[must_use]
404 pub fn degree(&self, atom: u32) -> usize {
405 self.degree.get(atom as usize).copied().unwrap_or(0) as usize
406 }
407
408 #[must_use]
410 pub fn bond_between(&self, a: u32, b: u32) -> Option<u32> {
411 let from = if self.degree(a) <= self.degree(b) {
413 a
414 } else {
415 b
416 };
417 let to = if from == a { b } else { a };
418 self.neighbors(from)
419 .find(|&(nbr, _)| nbr == to)
420 .map(|(_, bi)| bi)
421 }
422
423 #[must_use]
425 pub fn num_atoms(&self) -> usize {
426 self.atoms.len()
427 }
428
429 #[must_use]
431 pub fn num_bonds(&self) -> usize {
432 self.bonds.len()
433 }
434
435 #[must_use]
437 pub fn atoms(&self) -> &[AtomData] {
438 &self.atoms
439 }
440
441 #[must_use]
443 pub fn bonds(&self) -> &[BondData] {
444 &self.bonds
445 }
446
447 pub fn atom_mut(&mut self, idx: u32) -> Option<&mut AtomData> {
449 self.atoms.get_mut(idx as usize)
450 }
451
452 pub fn bond_mut(&mut self, idx: u32) -> Option<BondMut<'_>> {
458 self.bonds
459 .get_mut(idx as usize)
460 .map(|bond| BondMut { bond })
461 }
462
463 #[must_use]
465 pub fn name(&self) -> Option<&str> {
466 self.name.as_deref()
467 }
468
469 pub fn set_name(&mut self, name: impl Into<String>) {
471 self.name = Some(name.into());
472 }
473
474 #[doc(hidden)]
479 #[must_use]
480 pub fn adjacency_index_is_consistent(&self) -> bool {
481 for a in 0..self.atoms.len() as u32 {
482 let expected: Vec<(u32, u32)> = self
483 .bonds
484 .iter()
485 .enumerate()
486 .filter_map(|(bi, b)| b.other_end(a).map(|o| (o, bi as u32)))
487 .collect();
488 let actual: Vec<(u32, u32)> = self.neighbors(a).collect();
489 if expected != actual || self.degree(a) != expected.len() {
490 return false;
491 }
492 }
493 true
494 }
495}
496
497#[derive(Debug, Clone)]
499pub struct Neighbors<'a> {
500 mol: &'a MolBuilder,
501 half: u32,
502}
503
504impl Iterator for Neighbors<'_> {
505 type Item = (u32, u32);
507
508 fn next(&mut self) -> Option<Self::Item> {
509 if self.half == NO_HALF {
510 return None;
511 }
512 let h = self.half;
513 self.half = self.mol.next_half[h as usize];
514
515 let bi = h >> 1;
516 let bond = self.mol.bonds[bi as usize];
517 let nbr = if h & 1 == 0 { bond.end } else { bond.begin };
519 Some((nbr, bi))
520 }
521}
522
523#[derive(Debug)]
527pub struct BondMut<'a> {
528 bond: &'a mut BondData,
529}
530
531impl BondMut<'_> {
532 #[must_use]
534 pub fn get(&self) -> BondData {
535 *self.bond
536 }
537
538 pub fn set_order(&mut self, order: BondOrder) {
540 self.bond.order = order;
541 }
542
543 pub fn set_direction(&mut self, direction: BondDirection) {
545 self.bond.direction = direction;
546 }
547
548 pub fn set_stereo(&mut self, stereo: BondStereo) {
550 self.bond.stereo = stereo;
551 }
552
553 pub fn set_stereo_atoms(&mut self, atoms: [u32; 2]) {
558 self.bond.stereo_atoms = atoms;
559 }
560
561 pub fn flags_mut(&mut self) -> &mut BondFlags {
563 &mut self.bond.flags
564 }
565}
566
567#[cfg(test)]
568mod tests {
569 use super::*;
570
571 #[test]
572 fn build_ethanol() {
573 let mut b = MolBuilder::new();
574 let c0 = b.add_atom(6);
575 let c1 = b.add_atom(6);
576 let o = b.add_atom(8);
577 b.add_bond(c0, c1, BondOrder::Single).unwrap();
578 b.add_bond(c1, o, BondOrder::Single).unwrap();
579
580 assert_eq!(b.num_atoms(), 3);
581 assert_eq!(b.num_bonds(), 2);
582 assert_eq!(b.atoms()[2].atomic_num, 8);
583 }
584
585 #[test]
586 fn rejects_out_of_range_endpoint() {
587 let mut b = MolBuilder::new();
588 b.add_atom(6);
589 let err = b.add_bond(0, 5, BondOrder::Single).unwrap_err();
590 assert!(matches!(
591 err,
592 Error::AtomIndexOutOfRange {
593 index: 5,
594 num_atoms: 1
595 }
596 ));
597 }
598
599 #[test]
600 fn rejects_self_loop() {
601 let mut b = MolBuilder::new();
602 b.add_atom(6);
603 let err = b.add_bond(0, 0, BondOrder::Single).unwrap_err();
604 assert!(matches!(err, Error::SelfLoop { atom: 0 }));
605 }
606
607 #[test]
608 fn bond_other_end() {
609 let bond = BondData::new(3, 7, BondOrder::Double);
610 assert_eq!(bond.other_end(3), Some(7));
611 assert_eq!(bond.other_end(7), Some(3));
612 assert_eq!(bond.other_end(5), None);
613 }
614}
615
616#[cfg(test)]
617mod adjacency_tests {
618 use super::*;
619
620 fn isobutane() -> MolBuilder {
622 let mut m = MolBuilder::new();
623 for _ in 0..4 {
624 m.add_atom(6);
625 }
626 m.add_bond(0, 1, BondOrder::Single).unwrap();
627 m.add_bond(1, 2, BondOrder::Single).unwrap();
628 m.add_bond(1, 3, BondOrder::Single).unwrap();
629 m
630 }
631
632 #[test]
633 fn neighbors_and_degree() {
634 let m = isobutane();
635 assert_eq!(
636 m.neighbors(1).collect::<Vec<_>>(),
637 vec![(0, 0), (2, 1), (3, 2)]
638 );
639 assert_eq!(m.neighbors(0).collect::<Vec<_>>(), vec![(1, 0)]);
640 assert_eq!(m.degree(1), 3);
641 assert_eq!(m.degree(0), 1);
642 }
643
644 #[test]
645 fn isolated_atom_has_no_neighbors() {
646 let mut m = MolBuilder::new();
647 m.add_atom(10); assert_eq!(m.neighbors(0).count(), 0);
649 assert_eq!(m.degree(0), 0);
650 }
651
652 #[test]
653 fn out_of_range_atom_is_empty_not_panic() {
654 let m = isobutane();
655 assert_eq!(m.neighbors(99).count(), 0);
656 assert_eq!(m.degree(99), 0);
657 assert_eq!(m.bond_between(99, 0), None);
658 }
659
660 #[test]
663 fn neighbor_order_is_bond_insertion_order() {
664 let mut m = MolBuilder::new();
665 for _ in 0..4 {
666 m.add_atom(6);
667 }
668 m.add_bond(3, 0, BondOrder::Single).unwrap();
670 m.add_bond(0, 1, BondOrder::Single).unwrap();
671 m.add_bond(2, 0, BondOrder::Single).unwrap();
672
673 assert_eq!(
674 m.neighbors(0).map(|(a, _)| a).collect::<Vec<_>>(),
675 vec![3, 1, 2],
676 "无论中心原子在哪一端,顺序都应是键的插入顺序"
677 );
678 assert_eq!(
679 m.neighbors(0).map(|(_, b)| b).collect::<Vec<_>>(),
680 vec![0, 1, 2]
681 );
682 }
683
684 #[test]
685 fn bond_between_finds_edges_from_either_side() {
686 let m = isobutane();
687 assert_eq!(m.bond_between(1, 0), Some(0));
688 assert_eq!(m.bond_between(0, 1), Some(0));
689 assert_eq!(m.bond_between(1, 3), Some(2));
690 assert_eq!(m.bond_between(0, 2), None, "0 与 2 不相邻");
691 }
692
693 #[test]
696 fn rejected_bond_leaves_index_untouched() {
697 let mut m = isobutane();
698 assert!(m.add_bond(1, 1, BondOrder::Single).is_err());
699 assert!(m.add_bond(0, 99, BondOrder::Single).is_err());
700 assert_eq!(m.degree(1), 3);
701 assert_eq!(m.num_bonds(), 3);
702 assert!(m.adjacency_index_is_consistent());
703 }
704
705 #[test]
707 fn index_stays_consistent_through_incremental_build() {
708 let mut m = MolBuilder::new();
709 assert!(m.adjacency_index_is_consistent());
710 for i in 0..12u32 {
711 m.add_atom(6);
712 assert!(m.adjacency_index_is_consistent(), "加原子 {i} 后失配");
713 if i > 0 {
714 m.add_bond(i - 1, i, BondOrder::Single).unwrap();
715 assert!(m.adjacency_index_is_consistent(), "加键 {i} 后失配");
716 }
717 }
718 m.add_bond(0, 11, BondOrder::Single).unwrap();
720 m.add_bond(3, 8, BondOrder::Single).unwrap();
721 assert!(m.adjacency_index_is_consistent());
722 }
723
724 #[test]
726 fn clone_carries_a_valid_index() {
727 let m = isobutane().clone();
728 assert!(m.adjacency_index_is_consistent());
729 assert_eq!(m.degree(1), 3);
730 }
731
732 #[test]
734 fn property_edits_do_not_disturb_topology() {
735 let mut m = isobutane();
736 let mut b = m.bond_mut(1).unwrap();
737 b.set_order(BondOrder::Double);
738 b.flags_mut().insert(BondFlags::AROMATIC);
739 b.set_direction(BondDirection::UpRight);
740 assert!(m.adjacency_index_is_consistent());
741 assert_eq!(m.bonds()[1].order, BondOrder::Double);
742 assert_eq!(m.bonds()[1].direction, BondDirection::UpRight);
743 }
744}
745
746#[cfg(test)]
747mod valence_contrib_tests {
748 use super::*;
749
750 #[test]
752 fn dative_contribution_is_asymmetric() {
753 let d = BondData::new(3, 7, BondOrder::Dative);
754 assert_eq!(d.valence_contribution_to(3), 0.0, "给体不计价");
755 assert_eq!(d.valence_contribution_to(7), 1.0, "受体计 1");
756 assert_eq!(d.valence_contribution_to(9), 0.0, "非端点");
757 }
758
759 #[test]
760 fn normal_bonds_are_symmetric() {
761 for (order, v) in [
762 (BondOrder::Single, 1.0),
763 (BondOrder::Double, 2.0),
764 (BondOrder::Triple, 3.0),
765 (BondOrder::Aromatic, 1.5),
766 ] {
767 let b = BondData::new(1, 2, order);
768 assert_eq!(b.valence_contribution_to(1), v);
769 assert_eq!(b.valence_contribution_to(2), v);
770 }
771 }
772}