1use super::structure::Structure;
8use super::types::BondOrder;
9use std::fmt;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct Bond {
17 pub a1_idx: usize,
19 pub a2_idx: usize,
21 pub order: BondOrder,
23}
24
25impl Bond {
26 pub fn new(idx1: usize, idx2: usize, order: BondOrder) -> Self {
41 if idx1 <= idx2 {
42 Self {
43 a1_idx: idx1,
44 a2_idx: idx2,
45 order,
46 }
47 } else {
48 Self {
49 a1_idx: idx2,
50 a2_idx: idx1,
51 order,
52 }
53 }
54 }
55}
56
57#[derive(Debug, Clone)]
62pub struct Topology {
63 structure: Structure,
64 bonds: Vec<Bond>,
65}
66
67impl Topology {
68 pub fn new(structure: Structure, bonds: Vec<Bond>) -> Self {
82 debug_assert!(
83 bonds.iter().all(|b| b.a2_idx < structure.atom_count()),
84 "Bond index out of bounds"
85 );
86 Self { structure, bonds }
87 }
88
89 pub fn structure(&self) -> &Structure {
95 &self.structure
96 }
97
98 pub fn bonds(&self) -> &[Bond] {
104 &self.bonds
105 }
106
107 pub fn bond_count(&self) -> usize {
113 self.bonds.len()
114 }
115
116 pub fn atom_count(&self) -> usize {
122 self.structure.atom_count()
123 }
124
125 pub fn bonds_of(&self, atom_idx: usize) -> impl Iterator<Item = &Bond> {
135 self.bonds
136 .iter()
137 .filter(move |b| b.a1_idx == atom_idx || b.a2_idx == atom_idx)
138 }
139
140 pub fn neighbors_of(&self, atom_idx: usize) -> impl Iterator<Item = usize> + '_ {
150 self.bonds_of(atom_idx).map(move |b| {
151 if b.a1_idx == atom_idx {
152 b.a2_idx
153 } else {
154 b.a1_idx
155 }
156 })
157 }
158}
159
160impl fmt::Display for Topology {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 write!(
166 f,
167 "Topology {{ atoms: {}, bonds: {} }}",
168 self.atom_count(),
169 self.bond_count()
170 )
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use crate::model::atom::Atom;
178 use crate::model::chain::Chain;
179 use crate::model::residue::Residue;
180 use crate::model::types::{Element, Point, ResidueCategory, StandardResidue};
181
182 #[test]
183 fn bond_new_creates_bond_with_canonical_ordering() {
184 let bond = Bond::new(5, 2, BondOrder::Single);
185
186 assert_eq!(bond.a1_idx, 2);
187 assert_eq!(bond.a2_idx, 5);
188 assert_eq!(bond.order, BondOrder::Single);
189 }
190
191 #[test]
192 fn bond_new_preserves_order_when_already_canonical() {
193 let bond = Bond::new(2, 5, BondOrder::Double);
194
195 assert_eq!(bond.a1_idx, 2);
196 assert_eq!(bond.a2_idx, 5);
197 assert_eq!(bond.order, BondOrder::Double);
198 }
199
200 #[test]
201 fn bond_new_handles_same_indices() {
202 let bond = Bond::new(3, 3, BondOrder::Triple);
203
204 assert_eq!(bond.a1_idx, 3);
205 assert_eq!(bond.a2_idx, 3);
206 assert_eq!(bond.order, BondOrder::Triple);
207 }
208
209 #[test]
210 fn topology_new_creates_topology_correctly() {
211 let mut structure = Structure::new();
212 let mut chain = Chain::new("A");
213 let mut residue = Residue::new(
214 1,
215 None,
216 "ALA",
217 Some(StandardResidue::ALA),
218 ResidueCategory::Standard,
219 );
220 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
221 residue.add_atom(Atom::new("CB", Element::C, Point::new(1.0, 0.0, 0.0)));
222 chain.add_residue(residue);
223 structure.add_chain(chain);
224
225 let bonds = vec![Bond::new(0, 1, BondOrder::Single)];
226 let topology = Topology::new(structure, bonds);
227
228 assert_eq!(topology.atom_count(), 2);
229 assert_eq!(topology.bond_count(), 1);
230 }
231
232 #[test]
233 fn topology_structure_returns_correct_reference() {
234 let structure = Structure::new();
235 let topology = Topology::new(structure, Vec::new());
236
237 let retrieved = topology.structure();
238
239 assert_eq!(retrieved.chain_count(), 0);
240 }
241
242 #[test]
243 fn topology_bonds_returns_correct_slice() {
244 let mut structure = Structure::new();
245 let mut chain = Chain::new("A");
246 let mut residue = Residue::new(
247 1,
248 None,
249 "ALA",
250 Some(StandardResidue::ALA),
251 ResidueCategory::Standard,
252 );
253 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
254 residue.add_atom(Atom::new("CB", Element::C, Point::new(1.0, 0.0, 0.0)));
255 residue.add_atom(Atom::new("N", Element::N, Point::new(2.0, 0.0, 0.0)));
256 chain.add_residue(residue);
257 structure.add_chain(chain);
258
259 let bonds = vec![
260 Bond::new(0, 1, BondOrder::Single),
261 Bond::new(1, 2, BondOrder::Double),
262 ];
263 let topology = Topology::new(structure, bonds);
264
265 let retrieved = topology.bonds();
266
267 assert_eq!(retrieved.len(), 2);
268 assert_eq!(retrieved[0].order, BondOrder::Single);
269 assert_eq!(retrieved[1].order, BondOrder::Double);
270 }
271
272 #[test]
273 fn topology_bond_count_returns_correct_count() {
274 let mut structure = Structure::new();
275 let mut chain = Chain::new("A");
276 let mut residue = Residue::new(
277 1,
278 None,
279 "ALA",
280 Some(StandardResidue::ALA),
281 ResidueCategory::Standard,
282 );
283 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
284 residue.add_atom(Atom::new("CB", Element::C, Point::new(1.0, 0.0, 0.0)));
285 chain.add_residue(residue);
286 structure.add_chain(chain);
287
288 let bonds = vec![Bond::new(0, 1, BondOrder::Single)];
289 let topology = Topology::new(structure, bonds);
290
291 assert_eq!(topology.bond_count(), 1);
292 }
293
294 #[test]
295 fn topology_bond_count_returns_zero_for_empty_topology() {
296 let structure = Structure::new();
297 let topology = Topology::new(structure, Vec::new());
298
299 assert_eq!(topology.bond_count(), 0);
300 }
301
302 #[test]
303 fn topology_atom_count_returns_correct_count() {
304 let mut structure = Structure::new();
305 let mut chain = Chain::new("A");
306 let mut residue = Residue::new(
307 1,
308 None,
309 "ALA",
310 Some(StandardResidue::ALA),
311 ResidueCategory::Standard,
312 );
313 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
314 residue.add_atom(Atom::new("CB", Element::C, Point::new(1.0, 0.0, 0.0)));
315 chain.add_residue(residue);
316 structure.add_chain(chain);
317
318 let topology = Topology::new(structure, Vec::new());
319
320 assert_eq!(topology.atom_count(), 2);
321 }
322
323 #[test]
324 fn topology_atom_count_returns_zero_for_empty_structure() {
325 let structure = Structure::new();
326 let topology = Topology::new(structure, Vec::new());
327
328 assert_eq!(topology.atom_count(), 0);
329 }
330
331 #[test]
332 fn topology_bonds_of_returns_correct_bonds() {
333 let mut structure = Structure::new();
334 let mut chain = Chain::new("A");
335 let mut residue = Residue::new(
336 1,
337 None,
338 "ALA",
339 Some(StandardResidue::ALA),
340 ResidueCategory::Standard,
341 );
342 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
343 residue.add_atom(Atom::new("CB", Element::C, Point::new(1.0, 0.0, 0.0)));
344 residue.add_atom(Atom::new("N", Element::N, Point::new(2.0, 0.0, 0.0)));
345 chain.add_residue(residue);
346 structure.add_chain(chain);
347
348 let bonds = vec![
349 Bond::new(0, 1, BondOrder::Single),
350 Bond::new(1, 2, BondOrder::Double),
351 ];
352 let topology = Topology::new(structure, bonds);
353
354 let bonds_of_1: Vec<_> = topology.bonds_of(1).collect();
355
356 assert_eq!(bonds_of_1.len(), 2);
357 assert!(bonds_of_1.iter().any(|b| b.a1_idx == 0 && b.a2_idx == 1));
358 assert!(bonds_of_1.iter().any(|b| b.a1_idx == 1 && b.a2_idx == 2));
359 }
360
361 #[test]
362 fn topology_bonds_of_returns_empty_for_atom_with_no_bonds() {
363 let mut structure = Structure::new();
364 let mut chain = Chain::new("A");
365 let mut residue = Residue::new(
366 1,
367 None,
368 "ALA",
369 Some(StandardResidue::ALA),
370 ResidueCategory::Standard,
371 );
372 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
373 residue.add_atom(Atom::new("CB", Element::C, Point::new(1.0, 0.0, 0.0)));
374 chain.add_residue(residue);
375 structure.add_chain(chain);
376
377 let bonds = vec![Bond::new(0, 1, BondOrder::Single)];
378 let topology = Topology::new(structure, bonds);
379
380 let bonds_of_5: Vec<_> = topology.bonds_of(5).collect();
381
382 assert!(bonds_of_5.is_empty());
383 }
384
385 #[test]
386 fn topology_neighbors_of_returns_correct_neighbors() {
387 let mut structure = Structure::new();
388 let mut chain = Chain::new("A");
389 let mut residue = Residue::new(
390 1,
391 None,
392 "ALA",
393 Some(StandardResidue::ALA),
394 ResidueCategory::Standard,
395 );
396 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
397 residue.add_atom(Atom::new("CB", Element::C, Point::new(1.0, 0.0, 0.0)));
398 residue.add_atom(Atom::new("N", Element::N, Point::new(2.0, 0.0, 0.0)));
399 residue.add_atom(Atom::new("O", Element::O, Point::new(3.0, 0.0, 0.0)));
400 chain.add_residue(residue);
401 structure.add_chain(chain);
402
403 let bonds = vec![
404 Bond::new(0, 1, BondOrder::Single),
405 Bond::new(1, 2, BondOrder::Double),
406 Bond::new(1, 3, BondOrder::Triple),
407 ];
408 let topology = Topology::new(structure, bonds);
409
410 let neighbors: Vec<_> = topology.neighbors_of(1).collect();
411
412 assert_eq!(neighbors.len(), 3);
413 assert!(neighbors.contains(&0));
414 assert!(neighbors.contains(&2));
415 assert!(neighbors.contains(&3));
416 }
417
418 #[test]
419 fn topology_neighbors_of_returns_empty_for_isolated_atom() {
420 let mut structure = Structure::new();
421 let mut chain = Chain::new("A");
422 let mut residue = Residue::new(
423 1,
424 None,
425 "ALA",
426 Some(StandardResidue::ALA),
427 ResidueCategory::Standard,
428 );
429 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
430 residue.add_atom(Atom::new("CB", Element::C, Point::new(1.0, 0.0, 0.0)));
431 chain.add_residue(residue);
432 structure.add_chain(chain);
433
434 let bonds = vec![Bond::new(0, 1, BondOrder::Single)];
435 let topology = Topology::new(structure, bonds);
436
437 let neighbors: Vec<_> = topology.neighbors_of(5).collect();
438
439 assert!(neighbors.is_empty());
440 }
441
442 #[test]
443 fn topology_display_formats_correctly() {
444 let mut structure = Structure::new();
445 let mut chain = Chain::new("A");
446 let mut residue = Residue::new(
447 1,
448 None,
449 "ALA",
450 Some(StandardResidue::ALA),
451 ResidueCategory::Standard,
452 );
453 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
454 residue.add_atom(Atom::new("CB", Element::C, Point::new(1.0, 0.0, 0.0)));
455 chain.add_residue(residue);
456 structure.add_chain(chain);
457
458 let bonds = vec![Bond::new(0, 1, BondOrder::Single)];
459 let topology = Topology::new(structure, bonds);
460
461 let display = format!("{}", topology);
462 let expected = "Topology { atoms: 2, bonds: 1 }";
463
464 assert_eq!(display, expected);
465 }
466
467 #[test]
468 fn topology_display_formats_empty_topology_correctly() {
469 let structure = Structure::new();
470 let topology = Topology::new(structure, Vec::new());
471
472 let display = format!("{}", topology);
473 let expected = "Topology { atoms: 0, bonds: 0 }";
474
475 assert_eq!(display, expected);
476 }
477
478 #[test]
479 fn topology_clone_creates_identical_copy() {
480 let mut structure = Structure::new();
481 let mut chain = Chain::new("A");
482 let mut residue = Residue::new(
483 1,
484 None,
485 "ALA",
486 Some(StandardResidue::ALA),
487 ResidueCategory::Standard,
488 );
489 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
490 residue.add_atom(Atom::new("CB", Element::C, Point::new(1.0, 0.0, 0.0)));
491 chain.add_residue(residue);
492 structure.add_chain(chain);
493
494 let bonds = vec![Bond::new(0, 1, BondOrder::Single)];
495 let topology = Topology::new(structure, bonds);
496
497 let cloned = topology.clone();
498
499 assert_eq!(topology.atom_count(), cloned.atom_count());
500 assert_eq!(topology.bond_count(), cloned.bond_count());
501 assert_eq!(topology.bonds(), cloned.bonds());
502 }
503
504 #[test]
505 fn bond_partial_eq_compares_correctly() {
506 let bond1 = Bond::new(0, 1, BondOrder::Single);
507 let bond2 = Bond::new(1, 0, BondOrder::Single);
508 let bond3 = Bond::new(0, 2, BondOrder::Single);
509 let bond4 = Bond::new(0, 1, BondOrder::Double);
510
511 assert_eq!(bond1, bond2);
512 assert_ne!(bond1, bond3);
513 assert_ne!(bond1, bond4);
514 }
515
516 #[test]
517 fn bond_hash_considers_canonical_ordering() {
518 use std::collections::HashSet;
519
520 let bond1 = Bond::new(0, 1, BondOrder::Single);
521 let bond2 = Bond::new(1, 0, BondOrder::Single);
522 let mut set = HashSet::new();
523
524 set.insert(bond1);
525 set.insert(bond2);
526
527 assert_eq!(set.len(), 1);
528 }
529
530 #[test]
531 fn topology_with_complex_structure() {
532 let mut structure = Structure::new();
533 let mut chain = Chain::new("A");
534 let mut residue = Residue::new(
535 1,
536 None,
537 "HOH",
538 Some(StandardResidue::HOH),
539 ResidueCategory::Standard,
540 );
541 residue.add_atom(Atom::new("O", Element::O, Point::new(0.0, 0.0, 0.0)));
542 residue.add_atom(Atom::new("H1", Element::H, Point::new(0.96, 0.0, 0.0)));
543 residue.add_atom(Atom::new("H2", Element::H, Point::new(-0.24, 0.93, 0.0)));
544 chain.add_residue(residue);
545 structure.add_chain(chain);
546
547 let bonds = vec![
548 Bond::new(0, 1, BondOrder::Single),
549 Bond::new(0, 2, BondOrder::Single),
550 ];
551 let topology = Topology::new(structure, bonds);
552
553 assert_eq!(topology.atom_count(), 3);
554 assert_eq!(topology.bond_count(), 2);
555
556 let o_neighbors: Vec<_> = topology.neighbors_of(0).collect();
557 assert_eq!(o_neighbors.len(), 2);
558 assert!(o_neighbors.contains(&1));
559 assert!(o_neighbors.contains(&2));
560
561 let h1_neighbors: Vec<_> = topology.neighbors_of(1).collect();
562 assert_eq!(h1_neighbors, vec![0]);
563
564 let h2_neighbors: Vec<_> = topology.neighbors_of(2).collect();
565 assert_eq!(h2_neighbors, vec![0]);
566 }
567
568 #[test]
569 fn topology_bonds_of_with_aromatic_bond() {
570 let mut structure = Structure::new();
571 let mut chain = Chain::new("A");
572 let mut residue = Residue::new(
573 1,
574 None,
575 "ALA",
576 Some(StandardResidue::ALA),
577 ResidueCategory::Standard,
578 );
579 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
580 residue.add_atom(Atom::new("CB", Element::C, Point::new(1.0, 0.0, 0.0)));
581 chain.add_residue(residue);
582 structure.add_chain(chain);
583
584 let bonds = vec![Bond::new(0, 1, BondOrder::Aromatic)];
585 let topology = Topology::new(structure, bonds);
586
587 let bonds_of_0: Vec<_> = topology.bonds_of(0).collect();
588
589 assert_eq!(bonds_of_0.len(), 1);
590 assert_eq!(bonds_of_0[0].order, BondOrder::Aromatic);
591 }
592}