1use super::chain::Chain;
8use super::residue::Residue;
9use super::types::Point;
10use std::fmt;
11
12#[derive(Debug, Clone, Default)]
19pub struct Structure {
20 chains: Vec<Chain>,
22 pub box_vectors: Option<[[f64; 3]; 3]>,
24}
25
26impl Structure {
27 pub fn new() -> Self {
33 Self::default()
34 }
35
36 pub fn add_chain(&mut self, chain: Chain) {
45 debug_assert!(
46 self.chain(&chain.id).is_none(),
47 "Attempted to add a duplicate chain ID '{}'",
48 chain.id
49 );
50 self.chains.push(chain);
51 }
52
53 pub fn remove_chain(&mut self, id: &str) -> Option<Chain> {
63 if let Some(index) = self.chains.iter().position(|c| c.id == id) {
64 Some(self.chains.remove(index))
65 } else {
66 None
67 }
68 }
69
70 pub fn clear(&mut self) {
72 self.chains.clear();
73 }
74
75 pub fn chain(&self, id: &str) -> Option<&Chain> {
85 self.chains.iter().find(|c| c.id == id)
86 }
87
88 pub fn chain_mut(&mut self, id: &str) -> Option<&mut Chain> {
98 self.chains.iter_mut().find(|c| c.id == id)
99 }
100
101 pub fn find_residue(
113 &self,
114 chain_id: &str,
115 residue_id: i32,
116 insertion_code: Option<char>,
117 ) -> Option<&Residue> {
118 self.chain(chain_id)
119 .and_then(|c| c.residue(residue_id, insertion_code))
120 }
121
122 pub fn find_residue_mut(
134 &mut self,
135 chain_id: &str,
136 residue_id: i32,
137 insertion_code: Option<char>,
138 ) -> Option<&mut Residue> {
139 self.chain_mut(chain_id)
140 .and_then(|c| c.residue_mut(residue_id, insertion_code))
141 }
142
143 pub fn sort_chains_by_id(&mut self) {
145 self.chains.sort_by(|a, b| a.id.cmp(&b.id));
146 }
147
148 pub fn chain_count(&self) -> usize {
154 self.chains.len()
155 }
156
157 pub fn residue_count(&self) -> usize {
163 self.chains.iter().map(|c| c.residue_count()).sum()
164 }
165
166 pub fn atom_count(&self) -> usize {
172 self.chains.iter().map(|c| c.iter_atoms().count()).sum()
173 }
174
175 pub fn is_empty(&self) -> bool {
181 self.chains.is_empty()
182 }
183
184 pub fn iter_chains(&self) -> std::slice::Iter<'_, Chain> {
190 self.chains.iter()
191 }
192
193 pub fn iter_chains_mut(&mut self) -> std::slice::IterMut<'_, Chain> {
199 self.chains.iter_mut()
200 }
201
202 pub fn iter_atoms(&self) -> impl Iterator<Item = &super::atom::Atom> {
208 self.chains.iter().flat_map(|c| c.iter_atoms())
209 }
210
211 pub fn iter_atoms_mut(&mut self) -> impl Iterator<Item = &mut super::atom::Atom> {
217 self.chains.iter_mut().flat_map(|c| c.iter_atoms_mut())
218 }
219
220 pub fn retain_residues<F>(&mut self, mut f: F)
229 where
230 F: FnMut(&str, &Residue) -> bool,
231 {
232 for chain in &mut self.chains {
233 let chain_id = chain.id.clone();
234 chain.retain_residues(|residue| f(&chain_id, residue));
235 }
236 }
237
238 pub fn prune_empty_chains(&mut self) {
240 self.chains.retain(|chain| !chain.is_empty());
241 }
242
243 pub fn iter_atoms_with_context(
249 &self,
250 ) -> impl Iterator<Item = (&Chain, &Residue, &super::atom::Atom)> {
251 self.chains.iter().flat_map(|chain| {
252 chain.iter_residues().flat_map(move |residue| {
253 residue.iter_atoms().map(move |atom| (chain, residue, atom))
254 })
255 })
256 }
257
258 pub fn geometric_center(&self) -> Point {
266 let mut sum = nalgebra::Vector3::zeros();
267 let mut count = 0;
268
269 for atom in self.iter_atoms() {
270 sum += atom.pos.coords;
271 count += 1;
272 }
273
274 if count > 0 {
275 Point::from(sum / (count as f64))
276 } else {
277 Point::origin()
278 }
279 }
280
281 pub fn center_of_mass(&self) -> Point {
290 let mut total_mass = 0.0;
291 let mut weighted_sum = nalgebra::Vector3::zeros();
292
293 for atom in self.iter_atoms() {
294 let mass = atom.element.atomic_mass();
295 weighted_sum += atom.pos.coords * mass;
296 total_mass += mass;
297 }
298
299 if total_mass > 1e-9 {
300 Point::from(weighted_sum / total_mass)
301 } else {
302 Point::origin()
303 }
304 }
305}
306
307impl fmt::Display for Structure {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 write!(
310 f,
311 "Structure {{ chains: {}, residues: {}, atoms: {} }}",
312 self.chain_count(),
313 self.residue_count(),
314 self.atom_count()
315 )
316 }
317}
318
319impl FromIterator<Chain> for Structure {
320 fn from_iter<T: IntoIterator<Item = Chain>>(iter: T) -> Self {
321 Self {
322 chains: iter.into_iter().collect(),
323 box_vectors: None,
324 }
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331 use crate::model::atom::Atom;
332 use crate::model::types::{Element, ResidueCategory, StandardResidue};
333
334 fn make_residue(id: i32, name: &str) -> Residue {
335 Residue::new(
336 id,
337 None,
338 name,
339 Some(StandardResidue::ALA),
340 ResidueCategory::Standard,
341 )
342 }
343
344 #[test]
345 fn structure_new_creates_empty_structure() {
346 let structure = Structure::new();
347
348 assert!(structure.is_empty());
349 assert_eq!(structure.chain_count(), 0);
350 assert_eq!(structure.residue_count(), 0);
351 assert_eq!(structure.atom_count(), 0);
352 assert!(structure.box_vectors.is_none());
353 }
354
355 #[test]
356 fn structure_default_creates_empty_structure() {
357 let structure = Structure::default();
358
359 assert!(structure.is_empty());
360 assert!(structure.box_vectors.is_none());
361 }
362
363 #[test]
364 fn structure_add_chain_adds_chain_correctly() {
365 let mut structure = Structure::new();
366 let chain = Chain::new("A");
367
368 structure.add_chain(chain);
369
370 assert_eq!(structure.chain_count(), 1);
371 assert!(structure.chain("A").is_some());
372 assert_eq!(structure.chain("A").unwrap().id, "A");
373 }
374
375 #[test]
376 fn structure_remove_chain_removes_existing_chain() {
377 let mut structure = Structure::new();
378 let chain = Chain::new("A");
379 structure.add_chain(chain);
380
381 let removed = structure.remove_chain("A");
382
383 assert!(removed.is_some());
384 assert_eq!(removed.unwrap().id, "A");
385 assert_eq!(structure.chain_count(), 0);
386 assert!(structure.chain("A").is_none());
387 }
388
389 #[test]
390 fn structure_remove_chain_returns_none_for_nonexistent_chain() {
391 let mut structure = Structure::new();
392
393 let removed = structure.remove_chain("NONEXISTENT");
394
395 assert!(removed.is_none());
396 }
397
398 #[test]
399 fn structure_clear_removes_all_chains() {
400 let mut structure = Structure::new();
401 structure.add_chain(Chain::new("A"));
402 structure.add_chain(Chain::new("B"));
403
404 structure.clear();
405
406 assert!(structure.is_empty());
407 assert_eq!(structure.chain_count(), 0);
408 }
409
410 #[test]
411 fn structure_chain_returns_correct_chain() {
412 let mut structure = Structure::new();
413 let chain = Chain::new("A");
414 structure.add_chain(chain);
415
416 let retrieved = structure.chain("A");
417
418 assert!(retrieved.is_some());
419 assert_eq!(retrieved.unwrap().id, "A");
420 }
421
422 #[test]
423 fn structure_chain_returns_none_for_nonexistent_chain() {
424 let structure = Structure::new();
425
426 let retrieved = structure.chain("NONEXISTENT");
427
428 assert!(retrieved.is_none());
429 }
430
431 #[test]
432 fn structure_chain_mut_returns_correct_mutable_chain() {
433 let mut structure = Structure::new();
434 let chain = Chain::new("A");
435 structure.add_chain(chain);
436
437 let retrieved = structure.chain_mut("A");
438
439 assert!(retrieved.is_some());
440 assert_eq!(retrieved.unwrap().id, "A");
441 }
442
443 #[test]
444 fn structure_chain_mut_returns_none_for_nonexistent_chain() {
445 let mut structure = Structure::new();
446
447 let retrieved = structure.chain_mut("NONEXISTENT");
448
449 assert!(retrieved.is_none());
450 }
451
452 #[test]
453 fn structure_find_residue_finds_correct_residue() {
454 let mut structure = Structure::new();
455 let mut chain = Chain::new("A");
456 let residue = Residue::new(
457 1,
458 None,
459 "ALA",
460 Some(StandardResidue::ALA),
461 ResidueCategory::Standard,
462 );
463 chain.add_residue(residue);
464 structure.add_chain(chain);
465
466 let found = structure.find_residue("A", 1, None);
467
468 assert!(found.is_some());
469 assert_eq!(found.unwrap().id, 1);
470 assert_eq!(found.unwrap().name, "ALA");
471 }
472
473 #[test]
474 fn structure_find_residue_returns_none_for_nonexistent_chain() {
475 let structure = Structure::new();
476
477 let found = structure.find_residue("NONEXISTENT", 1, None);
478
479 assert!(found.is_none());
480 }
481
482 #[test]
483 fn structure_find_residue_returns_none_for_nonexistent_residue() {
484 let mut structure = Structure::new();
485 let chain = Chain::new("A");
486 structure.add_chain(chain);
487
488 let found = structure.find_residue("A", 999, None);
489
490 assert!(found.is_none());
491 }
492
493 #[test]
494 fn structure_find_residue_mut_finds_correct_mutable_residue() {
495 let mut structure = Structure::new();
496 let mut chain = Chain::new("A");
497 let residue = Residue::new(
498 1,
499 None,
500 "ALA",
501 Some(StandardResidue::ALA),
502 ResidueCategory::Standard,
503 );
504 chain.add_residue(residue);
505 structure.add_chain(chain);
506
507 let found = structure.find_residue_mut("A", 1, None);
508
509 assert!(found.is_some());
510 assert_eq!(found.unwrap().id, 1);
511 }
512
513 #[test]
514 fn structure_sort_chains_by_id_sorts_correctly() {
515 let mut structure = Structure::new();
516 structure.add_chain(Chain::new("C"));
517 structure.add_chain(Chain::new("A"));
518 structure.add_chain(Chain::new("B"));
519
520 structure.sort_chains_by_id();
521
522 let ids: Vec<&str> = structure.iter_chains().map(|c| c.id.as_str()).collect();
523 assert_eq!(ids, vec!["A", "B", "C"]);
524 }
525
526 #[test]
527 fn structure_chain_count_returns_correct_count() {
528 let mut structure = Structure::new();
529
530 assert_eq!(structure.chain_count(), 0);
531
532 structure.add_chain(Chain::new("A"));
533 assert_eq!(structure.chain_count(), 1);
534
535 structure.add_chain(Chain::new("B"));
536 assert_eq!(structure.chain_count(), 2);
537 }
538
539 #[test]
540 fn structure_residue_count_returns_correct_count() {
541 let mut structure = Structure::new();
542 let mut chain = Chain::new("A");
543 chain.add_residue(Residue::new(
544 1,
545 None,
546 "ALA",
547 Some(StandardResidue::ALA),
548 ResidueCategory::Standard,
549 ));
550 chain.add_residue(Residue::new(
551 2,
552 None,
553 "GLY",
554 Some(StandardResidue::GLY),
555 ResidueCategory::Standard,
556 ));
557 structure.add_chain(chain);
558
559 assert_eq!(structure.residue_count(), 2);
560 }
561
562 #[test]
563 fn structure_atom_count_returns_correct_count() {
564 let mut structure = Structure::new();
565 let mut chain = Chain::new("A");
566 let mut residue = Residue::new(
567 1,
568 None,
569 "ALA",
570 Some(StandardResidue::ALA),
571 ResidueCategory::Standard,
572 );
573 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
574 residue.add_atom(Atom::new("CB", Element::C, Point::new(1.0, 0.0, 0.0)));
575 chain.add_residue(residue);
576 structure.add_chain(chain);
577
578 assert_eq!(structure.atom_count(), 2);
579 }
580
581 #[test]
582 fn structure_is_empty_returns_true_for_empty_structure() {
583 let structure = Structure::new();
584
585 assert!(structure.is_empty());
586 }
587
588 #[test]
589 fn structure_is_empty_returns_false_for_non_empty_structure() {
590 let mut structure = Structure::new();
591 structure.add_chain(Chain::new("A"));
592
593 assert!(!structure.is_empty());
594 }
595
596 #[test]
597 fn structure_iter_chains_iterates_correctly() {
598 let mut structure = Structure::new();
599 structure.add_chain(Chain::new("A"));
600 structure.add_chain(Chain::new("B"));
601
602 let mut ids = Vec::new();
603 for chain in structure.iter_chains() {
604 ids.push(chain.id.clone());
605 }
606
607 assert_eq!(ids, vec!["A", "B"]);
608 }
609
610 #[test]
611 fn structure_iter_chains_mut_iterates_correctly() {
612 let mut structure = Structure::new();
613 structure.add_chain(Chain::new("A"));
614
615 for chain in structure.iter_chains_mut() {
616 chain.id = "MODIFIED".to_string();
617 }
618
619 assert_eq!(structure.chain("MODIFIED").unwrap().id, "MODIFIED");
620 }
621
622 #[test]
623 fn structure_iter_atoms_iterates_over_all_atoms() {
624 let mut structure = Structure::new();
625 let mut chain = Chain::new("A");
626 let mut residue = Residue::new(
627 1,
628 None,
629 "ALA",
630 Some(StandardResidue::ALA),
631 ResidueCategory::Standard,
632 );
633 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
634 residue.add_atom(Atom::new("CB", Element::C, Point::new(1.0, 0.0, 0.0)));
635 chain.add_residue(residue);
636 structure.add_chain(chain);
637
638 let mut atom_names = Vec::new();
639 for atom in structure.iter_atoms() {
640 atom_names.push(atom.name.clone());
641 }
642
643 assert_eq!(atom_names, vec!["CA", "CB"]);
644 }
645
646 #[test]
647 fn structure_iter_atoms_mut_iterates_over_all_atoms_mutably() {
648 let mut structure = Structure::new();
649 let mut chain = Chain::new("A");
650 let mut residue = Residue::new(
651 1,
652 None,
653 "ALA",
654 Some(StandardResidue::ALA),
655 ResidueCategory::Standard,
656 );
657 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
658 chain.add_residue(residue);
659 structure.add_chain(chain);
660
661 for atom in structure.iter_atoms_mut() {
662 atom.translate_by(&nalgebra::Vector3::new(1.0, 0.0, 0.0));
663 }
664
665 let translated_atom = structure
666 .find_residue("A", 1, None)
667 .unwrap()
668 .atom("CA")
669 .unwrap();
670 assert!((translated_atom.pos.x - 1.0).abs() < 1e-10);
671 }
672
673 #[test]
674 fn structure_retain_residues_filters_using_chain_context() {
675 let mut structure = Structure::new();
676 let mut chain_a = Chain::new("A");
677 chain_a.add_residue(make_residue(1, "ALA"));
678 chain_a.add_residue(make_residue(2, "GLY"));
679 let mut chain_b = Chain::new("B");
680 chain_b.add_residue(make_residue(3, "SER"));
681 structure.add_chain(chain_a);
682 structure.add_chain(chain_b);
683
684 structure.retain_residues(|chain_id, residue| chain_id == "A" && residue.id == 1);
685
686 let chain_a = structure.chain("A").unwrap();
687 assert_eq!(chain_a.residue_count(), 1);
688 assert!(chain_a.residue(1, None).is_some());
689 assert!(structure.chain("B").unwrap().is_empty());
690 }
691
692 #[test]
693 fn structure_prune_empty_chains_removes_them() {
694 let mut structure = Structure::new();
695 let mut chain_a = Chain::new("A");
696 chain_a.add_residue(make_residue(1, "ALA"));
697 let chain_b = Chain::new("B");
698 structure.add_chain(chain_a);
699 structure.add_chain(chain_b);
700
701 structure.prune_empty_chains();
702
703 assert!(structure.chain("A").is_some());
704 assert!(structure.chain("B").is_none());
705 assert_eq!(structure.chain_count(), 1);
706 }
707
708 #[test]
709 fn structure_iter_atoms_with_context_provides_correct_context() {
710 let mut structure = Structure::new();
711 let mut chain = Chain::new("A");
712 let mut residue = Residue::new(
713 1,
714 None,
715 "ALA",
716 Some(StandardResidue::ALA),
717 ResidueCategory::Standard,
718 );
719 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
720 chain.add_residue(residue);
721 structure.add_chain(chain);
722
723 let mut contexts = Vec::new();
724 for (chain, residue, atom) in structure.iter_atoms_with_context() {
725 contexts.push((chain.id.clone(), residue.id, atom.name.clone()));
726 }
727
728 assert_eq!(contexts, vec![("A".to_string(), 1, "CA".to_string())]);
729 }
730
731 #[test]
732 fn structure_geometric_center_calculates_correctly() {
733 let mut structure = Structure::new();
734 let mut chain = Chain::new("A");
735 let mut residue = Residue::new(
736 1,
737 None,
738 "ALA",
739 Some(StandardResidue::ALA),
740 ResidueCategory::Standard,
741 );
742 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
743 residue.add_atom(Atom::new("CB", Element::C, Point::new(2.0, 0.0, 0.0)));
744 chain.add_residue(residue);
745 structure.add_chain(chain);
746
747 let center = structure.geometric_center();
748
749 assert!((center.x - 1.0).abs() < 1e-10);
750 assert!((center.y - 0.0).abs() < 1e-10);
751 assert!((center.z - 0.0).abs() < 1e-10);
752 }
753
754 #[test]
755 fn structure_geometric_center_returns_origin_for_empty_structure() {
756 let structure = Structure::new();
757
758 let center = structure.geometric_center();
759
760 assert_eq!(center, Point::origin());
761 }
762
763 #[test]
764 fn structure_center_of_mass_calculates_correctly() {
765 let mut structure = Structure::new();
766 let mut chain = Chain::new("A");
767 let mut residue = Residue::new(
768 1,
769 None,
770 "ALA",
771 Some(StandardResidue::ALA),
772 ResidueCategory::Standard,
773 );
774 residue.add_atom(Atom::new("H", Element::H, Point::new(0.0, 0.0, 0.0)));
775 residue.add_atom(Atom::new("C", Element::C, Point::new(2.0, 0.0, 0.0)));
776 chain.add_residue(residue);
777 structure.add_chain(chain);
778
779 let com = structure.center_of_mass();
780
781 let expected_x = (0.0 * 1.00794 + 2.0 * 12.0107) / (1.00794 + 12.0107);
782 assert!((com.x - expected_x).abs() < 1e-3);
783 assert!((com.y - 0.0).abs() < 1e-10);
784 assert!((com.z - 0.0).abs() < 1e-10);
785 }
786
787 #[test]
788 fn structure_center_of_mass_returns_origin_for_empty_structure() {
789 let structure = Structure::new();
790
791 let com = structure.center_of_mass();
792
793 assert_eq!(com, Point::origin());
794 }
795
796 #[test]
797 fn structure_display_formats_correctly() {
798 let mut structure = Structure::new();
799 let mut chain = Chain::new("A");
800 let mut residue = Residue::new(
801 1,
802 None,
803 "ALA",
804 Some(StandardResidue::ALA),
805 ResidueCategory::Standard,
806 );
807 residue.add_atom(Atom::new("CA", Element::C, Point::new(0.0, 0.0, 0.0)));
808 chain.add_residue(residue);
809 structure.add_chain(chain);
810
811 let display = format!("{}", structure);
812 let expected = "Structure { chains: 1, residues: 1, atoms: 1 }";
813
814 assert_eq!(display, expected);
815 }
816
817 #[test]
818 fn structure_display_formats_empty_structure_correctly() {
819 let structure = Structure::new();
820
821 let display = format!("{}", structure);
822 let expected = "Structure { chains: 0, residues: 0, atoms: 0 }";
823
824 assert_eq!(display, expected);
825 }
826
827 #[test]
828 fn structure_from_iterator_creates_structure_correctly() {
829 let chains = vec![Chain::new("A"), Chain::new("B")];
830 let structure: Structure = chains.into_iter().collect();
831
832 assert_eq!(structure.chain_count(), 2);
833 assert!(structure.chain("A").is_some());
834 assert!(structure.chain("B").is_some());
835 assert!(structure.box_vectors.is_none());
836 }
837
838 #[test]
839 fn structure_clone_creates_identical_copy() {
840 let mut structure = Structure::new();
841 structure.add_chain(Chain::new("A"));
842 structure.box_vectors = Some([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]);
843
844 let cloned = structure.clone();
845
846 assert_eq!(structure.chain_count(), cloned.chain_count());
847 assert_eq!(structure.box_vectors, cloned.box_vectors);
848 }
849}