1use crate::core::sasa;
2use crate::core::structure;
3use crate::load_pdb;
4use pdbtbx::PDB;
5use pdbtbx::PDBError;
6use serde::Deserialize;
7use std::collections::HashSet;
8
9#[derive(Deserialize, Debug, Clone)]
15pub struct Interactor {
16 id: u16,
18
19 chain: String,
21
22 active: HashSet<i16>,
24
25 active_atoms: Option<Vec<String>>,
27
28 pub passive: HashSet<i16>,
30
31 passive_atoms: Option<Vec<String>>,
33
34 target: HashSet<u16>,
36
37 target_distance: Option<f64>,
39
40 lower_margin: Option<f64>,
42
43 upper_margin: Option<f64>,
45
46 structure: Option<String>,
48
49 pdb: Option<PDB>,
51
52 passive_from_active: Option<bool>,
54
55 passive_from_active_radius: Option<f64>,
57
58 surface_as_passive: Option<bool>,
60
61 filter_buried: Option<bool>,
63
64 filter_buried_cutoff: Option<f64>,
66
67 wildcard: Option<String>,
69}
70
71#[allow(clippy::too_many_arguments)]
72impl Interactor {
73 pub fn new(id: u16) -> Self {
88 Interactor {
89 id,
90 chain: String::new(),
91 active: HashSet::new(),
92 passive: HashSet::new(),
93 target: HashSet::new(),
94 structure: None,
95 pdb: None,
96 passive_from_active: None,
97 passive_from_active_radius: None,
98 surface_as_passive: None,
99 filter_buried: None,
100 filter_buried_cutoff: None,
101 active_atoms: None,
102 passive_atoms: None,
103 wildcard: None,
104 target_distance: None,
105 lower_margin: None,
106 upper_margin: None,
107 }
108 }
109
110 pub fn is_valid(&self) -> Result<bool, &str> {
122 if self.target.is_empty() {
123 return Err("Target residues are empty");
124 }
125 if self.active.intersection(&self.passive).next().is_some() {
126 return Err("Active/Passive selections overlap");
127 }
128 Ok(true)
129 }
130
131 pub fn set_passive_from_active(&mut self) {
158 if let Some(pdb) = &self.pdb {
159 let residues =
160 structure::get_residues(pdb, self.active.iter().map(|x| *x as isize).collect());
161
162 let search_cutoff = self.passive_from_active_radius.unwrap_or(6.5);
163 let neighbors = structure::neighbor_search(pdb.clone(), residues, search_cutoff);
164
165 neighbors.iter().for_each(|x| {
167 self.passive.insert(*x as i16);
168 });
169 }
170 }
171
172 pub fn set_surface_as_passive(&mut self) {
202 if let Some(pdb) = &self.pdb {
203 let sasa = sasa::calculate_sasa(pdb.clone());
204
205 sasa.iter().for_each(|r| {
207 if r.rel_sasa_total > 0.7 && r.chain == self.chain {
209 self.passive.insert(r.residue.serial_number() as i16);
210 }
211 });
212 }
213 }
214
215 pub fn remove_buried_residues(&mut self) {
245 if let Some(pdb) = &self.pdb {
246 let sasa = sasa::calculate_sasa(pdb.clone());
247
248 let sasa_cutoff = self.filter_buried_cutoff.unwrap_or(0.7);
249
250 sasa.iter().for_each(|r| {
251 if r.rel_sasa_total < sasa_cutoff && r.chain == self.chain {
253 self.passive.remove(&(r.residue.serial_number() as i16));
255 self.active.remove(&(r.residue.serial_number() as i16));
256 }
257 });
258 }
259 }
260
261 pub fn id(&self) -> u16 {
267 self.id
268 }
269
270 pub fn chain(&self) -> &str {
276 &self.chain
277 }
278
279 pub fn active(&self) -> &HashSet<i16> {
285 &self.active
286 }
287
288 pub fn active_atoms(&self) -> &Option<Vec<String>> {
294 &self.active_atoms
295 }
296
297 pub fn passive(&self) -> &HashSet<i16> {
303 &self.passive
304 }
305
306 pub fn passive_atoms(&self) -> &Option<Vec<String>> {
312 &self.passive_atoms
313 }
314
315 pub fn wildcard(&self) -> &str {
327 match &self.wildcard {
328 Some(wildcard) => wildcard,
329 None => "",
330 }
331 }
332
333 pub fn target(&self) -> &HashSet<u16> {
339 &self.target
340 }
341
342 pub fn structure(&self) -> &str {
348 match &self.structure {
349 Some(structure) => structure,
350 None => "",
351 }
352 }
353
354 pub fn set_structure(&mut self, structure: &str) {
360 self.structure = Some(structure.to_string());
361 }
362
363 pub fn load_structure(&mut self, structure_path: &str) -> Result<(), Vec<PDBError>> {
369 match load_pdb(structure_path) {
370 Ok(pdb) => {
371 self.structure = Some(structure_path.to_string());
372 self.pdb = Some(pdb);
373 Ok(())
374 }
375 Err(e) => Err(e),
376 }
377 }
378
379 pub fn pdb(&self) -> &Option<PDB> {
385 &self.pdb
386 }
387
388 pub fn set_pdb(&mut self, pdb: PDB) {
394 self.pdb = Some(pdb)
395 }
396
397 pub fn set_chain(&mut self, chain: &str) {
403 self.chain = chain.to_string();
404 }
405
406 pub fn set_active(&mut self, active: Vec<i16>) {
412 self.active = active.into_iter().collect();
413 }
414
415 pub fn set_passive(&mut self, passive: Vec<i16>) {
421 self.passive = passive.into_iter().collect();
422 }
423
424 pub fn set_wildcard(&mut self, wildcard: &str) {
439 self.wildcard = Some(wildcard.to_string());
440 }
441
442 pub fn set_target_distance(&mut self, distance: f64) {
448 self.target_distance = Some(distance);
449 }
450
451 pub fn set_lower_margin(&mut self, margin: f64) {
457 self.lower_margin = Some(margin);
458 }
459
460 pub fn set_upper_margin(&mut self, margin: f64) {
466 self.upper_margin = Some(margin);
467 }
468
469 pub fn passive_from_active(&self) -> bool {
475 self.passive_from_active.unwrap_or(false)
476 }
477
478 pub fn surface_as_passive(&self) -> bool {
484 self.surface_as_passive.unwrap_or(false)
485 }
486
487 pub fn filter_buried(&self) -> bool {
493 self.filter_buried.unwrap_or(false)
494 }
495
496 pub fn set_filter_buried_cutoff(&mut self, cutoff: f64) {
502 self.filter_buried_cutoff = Some(cutoff);
503 }
504
505 pub fn add_target(&mut self, target: u16) {
511 self.target.insert(target);
512 }
513
514 pub fn set_active_atoms(&mut self, atoms: Vec<String>) {
520 self.active_atoms = Some(atoms);
521 }
522
523 pub fn set_passive_atoms(&mut self, atoms: Vec<String>) {
529 self.passive_atoms = Some(atoms);
530 }
531
532 pub fn create_block(&self, passive_res: Vec<PassiveResidues>) -> String {
547 let mut block = String::new();
548 let mut _active: Vec<i16> = self.active().iter().cloned().collect();
549 _active.sort();
550
551 let mut passive_res: Vec<PassiveResidues> = passive_res.clone();
553 passive_res.sort_by(|a, b| a.res_number.cmp(&b.res_number));
554
555 let multiline = passive_res.len() > 1;
557
558 for resnum in _active {
559 let atom_str = format_atom_string(&self.active_atoms);
561
562 let mut assign_str = format!(
563 "assign ( resid {} and segid {}{} {})",
564 resnum,
565 self.chain(),
566 atom_str,
567 &self.wildcard()
568 );
569
570 if multiline {
571 assign_str += "\n (\n";
572 }
573
574 block.push_str(assign_str.as_str());
575
576 let res_lines: Vec<String> = passive_res
578 .iter()
579 .enumerate()
580 .map(|(index, res)| {
581 let atom_str = format_atom_string(res.atom_str);
582
583 let mut res_line = String::new();
584 if multiline {
585 res_line.push_str(
586 format!(
587 " ( {} segid {}{} {})\n",
588 res.res_number
589 .map_or(String::new(), |num| format!("resid {} and", num)),
590 res.chain_id,
591 atom_str,
592 res.wildcard
593 )
594 .as_str(),
595 );
596 } else {
597 res_line.push_str(
598 format!(
599 " ( {} segid {}{} {})",
600 res.res_number
601 .map_or(String::new(), |num| format!("resid {} and", num)),
602 res.chain_id,
603 atom_str,
604 res.wildcard
605 )
606 .as_str(),
607 );
608 }
609
610 if index != passive_res.len() - 1 {
611 res_line.push_str(" or\n");
612 }
613 res_line
614 })
615 .collect();
616
617 block.push_str(&res_lines.join(""));
618
619 let distance_string = format_distance_string(
620 &self.target_distance,
621 &self.lower_margin,
622 &self.upper_margin,
623 );
624 if multiline {
625 block.push_str(format!(" ) {}\n\n", distance_string).as_str());
626 } else {
627 block.push_str(format!(" {}\n\n", distance_string).as_str())
628 }
629 }
630 block
631 }
632
633 pub fn make_pml_string(&self, passive_res: Vec<PassiveResidues>) -> String {
634 let mut pml = String::new();
635 let mut _active: Vec<i16> = self.active().iter().cloned().collect();
636 _active.sort();
637
638 let mut passive_res: Vec<PassiveResidues> = passive_res.clone();
639 passive_res.sort_by(|a, b| a.res_number.cmp(&b.res_number));
640
641 for resnum in _active {
642 let identifier = format!("{}-{}", resnum, self.chain);
643 let active_sel = format!(
644 "resi {} and (name CA or name C1') and chain {}",
645 resnum, self.chain
646 );
647
648 for passive_resnum in &passive_res {
649 let passive_sel = format!(
650 "resi {} and (name CA or name C1') and chain {}",
651 passive_resnum.res_number.unwrap(),
652 passive_resnum.chain_id
653 );
654
655 pml.push_str(
656 format!(
657 "distance {}, ({}), ({})\n",
658 identifier, active_sel, passive_sel
659 )
660 .as_str(),
661 )
662 }
663 }
664
665 pml
666 }
667}
668
669#[derive(Debug, Clone)]
670pub struct PassiveResidues<'a> {
671 pub chain_id: &'a str,
672 pub res_number: Option<i16>,
673 wildcard: &'a str,
674 atom_str: &'a Option<Vec<String>>,
676}
677
678pub fn collect_residues(interactors: Vec<&Interactor>) -> Vec<PassiveResidues<'_>> {
694 let mut resnums = Vec::new();
695 for interactor in interactors {
696 let active = interactor.active().iter().map(|&x| PassiveResidues {
697 chain_id: interactor.chain(),
698 res_number: Some(x),
699 wildcard: interactor.wildcard(),
700 atom_str: interactor.active_atoms(),
701 });
702
703 let passive = interactor.passive().iter().map(|&x| PassiveResidues {
704 chain_id: interactor.chain(),
705 res_number: Some(x),
706 wildcard: interactor.wildcard(),
707 atom_str: interactor.passive_atoms(),
708 });
709
710 resnums.extend(active);
711 resnums.extend(passive);
712
713 if interactor.active().is_empty() && interactor.passive().is_empty() {
715 resnums.push(PassiveResidues {
716 chain_id: interactor.chain(),
717 res_number: None,
718 wildcard: interactor.wildcard(),
719 atom_str: &None,
720 });
721 }
722 }
723 resnums
724}
725
726pub fn format_distance_string(
742 target: &Option<f64>,
743 lower: &Option<f64>,
744 upper: &Option<f64>,
745) -> String {
746 let target = match target {
747 Some(target) => target,
748 None => &2.0,
749 };
750
751 let lower = match lower {
752 Some(lower) => lower,
753 None => &2.0,
754 };
755
756 let upper = match upper {
757 Some(upper) => upper,
758 None => &0.0,
759 };
760
761 format!("{:.1} {:.1} {:.1}", target, lower, upper)
762}
763
764pub fn format_atom_string(atoms: &Option<Vec<String>>) -> String {
778 match atoms {
779 Some(atoms) if atoms.len() > 1 => {
780 let atoms: String = atoms
781 .iter()
782 .map(|x| {
783 if x.contains("-") || x.contains("+") {
784 format!(r#"name "{}""#, x)
785 } else {
786 format!("name {}", x)
787 }
788 })
789 .collect::<Vec<String>>()
790 .join(" or ");
791
792 format!(" and ({})", atoms)
793 }
794 Some(atoms) if atoms.len() == 1 => {
795 if atoms[0].contains("-") || atoms[0].contains("+") {
796 format!(r#" and name "{}""#, atoms[0])
797 } else {
798 format!(" and name {}", atoms[0])
799 }
800 }
801 _ => "".to_string(),
802 }
803}
804
805#[cfg(test)]
806mod tests {
807
808 use std::collections::HashSet;
809
810 use crate::core::interactor::{Interactor, PassiveResidues, format_atom_string};
811
812 #[test]
813 fn test_format_atom_string() {
814 let atom_str = format_atom_string(&Some(vec!["O".to_string()]));
815 let expected_atom_str = " and name O".to_string();
816 assert_eq!(atom_str, expected_atom_str)
817 }
818
819 #[test]
820 fn test_format_atom_string_multiple() {
821 let atom_str = format_atom_string(&Some(vec!["O".to_string(), "CA".to_string()]));
822 let expected_atom_str = " and (name O or name CA)".to_string();
823 assert_eq!(atom_str, expected_atom_str)
824 }
825
826 #[test]
827 fn test_format_atom_string_special_chars() {
828 let atom_str = format_atom_string(&Some(vec!["ZN+2".to_string()]));
829 let expected_atom_str = " and name \"ZN+2\"".to_string();
830 assert_eq!(atom_str, expected_atom_str)
831 }
832
833 #[test]
834 fn test_format_atom_string_multiple_special_chars() {
835 let atom_str = format_atom_string(&Some(vec!["ZN+2".to_string(), "FE-3".to_string()]));
836 let expected_atom_str = " and (name \"ZN+2\" or name \"FE-3\")".to_string();
837 assert_eq!(atom_str, expected_atom_str)
838 }
839
840 #[test]
841 fn test_format_atom_string_multiple_hybrid_chars() {
842 let atom_str = format_atom_string(&Some(vec!["ZN+2".to_string(), "CA".to_string()]));
843 let expected_atom_str = " and (name \"ZN+2\" or name CA)".to_string();
844 assert_eq!(atom_str, expected_atom_str)
845 }
846
847 #[test]
848 fn test_valid_interactor() {
849 let mut interactor = Interactor::new(1);
850 interactor.set_active(vec![1]);
851 interactor.set_passive(vec![2]);
852 interactor.add_target(2);
853
854 assert_eq!(interactor.is_valid(), Ok(true));
855 }
856
857 #[test]
858 fn test_invalid_interactor_empty() {
859 let interactor = Interactor::new(1);
860
861 assert_eq!(interactor.is_valid(), Err("Target residues are empty"));
862 }
863
864 #[test]
865 fn test_invalid_interactor_overlap() {
866 let mut interactor = Interactor::new(1);
867 interactor.set_active(vec![1]);
868 interactor.set_passive(vec![1]);
869 interactor.add_target(2);
870
871 assert_eq!(
872 interactor.is_valid(),
873 Err("Active/Passive selections overlap")
874 );
875 }
876
877 #[test]
878 fn test_set_passive_from_active() {
879 let mut interactor = Interactor::new(1);
880 interactor.load_structure("tests/data/complex.pdb").unwrap();
881 interactor.set_active(vec![1]);
882 interactor.passive_from_active_radius = Some(5.0);
883 interactor.set_passive_from_active();
884
885 let expected_passive = [16, 15, 18, 3, 19, 61, 56, 17, 2, 62, 63];
886
887 assert_eq!(
888 interactor.passive(),
889 &expected_passive.iter().cloned().collect()
890 );
891 }
892
893 #[test]
894 fn test_set_surface_as_passive() {
895 let mut interactor = Interactor::new(1);
896 interactor.load_structure("tests/data/complex.pdb").unwrap();
897 interactor.set_chain("A");
898 interactor.set_surface_as_passive();
899
900 let expected = HashSet::from([
906 929, 930, 931, 932, 933, 934, 935, 936, 938, 940, 941, 942, 943, 944, 945, 946, 947,
907 948, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 964, 965, 966,
908 967, 968, 969, 970, 971, 972,
909 ]);
910
911 let in_expected_and_not_in_observed: HashSet<_> =
912 expected.difference(interactor.passive()).collect();
913 let in_observed_and_not_in_expected: HashSet<_> =
914 interactor.passive().difference(&expected).collect();
915
916 assert_eq!(
917 in_expected_and_not_in_observed,
918 HashSet::new(),
919 "resnums that were expected were not observed"
920 );
921 assert_eq!(
922 in_observed_and_not_in_expected,
923 HashSet::new(),
924 "resnums observed were not expected"
925 );
926 }
927
928 #[test]
929 fn test_remove_buried_active_residues() {
930 let mut interactor = Interactor::new(1);
931
932 interactor.load_structure("tests/data/complex.pdb").unwrap();
933 interactor.set_chain("A");
934 interactor.filter_buried = Some(true);
935 interactor.filter_buried_cutoff = Some(0.7);
936 interactor.set_active(vec![949, 931]);
937 interactor.remove_buried_residues();
938
939 let expected_active = [931];
940
941 assert_eq!(
942 interactor.active(),
943 &expected_active.iter().cloned().collect()
944 );
945 }
946
947 #[test]
948 fn test_create_block_multiline() {
949 let mut interactor = Interactor::new(1);
950 interactor.set_active(vec![1]);
951 interactor.set_chain("A");
952
953 let observed = interactor.create_block(vec![
954 PassiveResidues {
955 chain_id: "B",
956 res_number: Some(2),
957 wildcard: "",
958 atom_str: &None,
959 },
960 PassiveResidues {
961 chain_id: "B",
962 res_number: Some(3),
963 wildcard: "",
964 atom_str: &None,
965 },
966 ]);
967
968 let block = "assign ( resid 1 and segid A )\n (\n ( resid 2 and segid B )\n or\n ( resid 3 and segid B )\n ) 2.0 2.0 0.0\n\n";
969
970 assert_eq!(observed, block);
971 }
972
973 #[test]
974 fn test_create_block_oneline() {
975 let mut interactor = Interactor::new(1);
976 interactor.set_active(vec![1]);
977 interactor.set_chain("A");
978
979 let observed = interactor.create_block(vec![PassiveResidues {
980 chain_id: "B",
981 res_number: Some(2),
982 wildcard: "",
983 atom_str: &None,
984 }]);
985
986 let block = "assign ( resid 1 and segid A ) ( resid 2 and segid B ) 2.0 2.0 0.0\n\n";
987
988 assert_eq!(observed, block);
989 }
990
991 #[test]
992 fn test_create_block_oneline_atom_subset() {
993 let mut interactor = Interactor::new(1);
994 interactor.set_active(vec![1]);
995 interactor.set_chain("A");
996 interactor.set_active_atoms(vec!["CA".to_string(), "CB".to_string()]);
997
998 let observed = interactor.create_block(vec![PassiveResidues {
999 chain_id: "B",
1000 res_number: Some(2),
1001 wildcard: "",
1002 atom_str: &None,
1003 }]);
1004
1005 let block = "assign ( resid 1 and segid A and (name CA or name CB) ) ( resid 2 and segid B ) 2.0 2.0 0.0\n\n";
1006
1007 assert_eq!(observed, block);
1008 }
1009
1010 #[test]
1011 fn test_create_block_multiline_atom_subset() {
1012 let mut interactor = Interactor::new(1);
1013 interactor.set_active(vec![1]);
1014 interactor.set_chain("A");
1015 interactor.set_active_atoms(vec!["CA".to_string(), "CB".to_string()]);
1016 interactor.set_passive_atoms(vec!["CA".to_string(), "CB".to_string()]);
1017 let observed = interactor.create_block(vec![
1018 PassiveResidues {
1019 chain_id: "B",
1020 res_number: Some(2),
1021 wildcard: "",
1022 atom_str: &None,
1023 },
1024 PassiveResidues {
1025 chain_id: "B",
1026 res_number: Some(3),
1027 wildcard: "",
1028 atom_str: &None,
1029 },
1030 ]);
1031
1032 let block = "assign ( resid 1 and segid A and (name CA or name CB) )\n (\n ( resid 2 and segid B )\n or\n ( resid 3 and segid B )\n ) 2.0 2.0 0.0\n\n";
1033
1034 assert_eq!(observed, block);
1035 }
1036
1037 #[test]
1038 fn test_create_block_multiline_atom_subset_passive() {
1039 let mut interactor = Interactor::new(1);
1040 interactor.set_active(vec![1]);
1041 interactor.set_chain("A");
1042 interactor.set_active_atoms(vec!["CA".to_string(), "CB".to_string()]);
1043 let observed = interactor.create_block(vec![
1044 PassiveResidues {
1045 chain_id: "B",
1046 res_number: Some(2),
1047 wildcard: "",
1048 atom_str: &Some(vec!["N".to_string(), "C".to_string()]),
1049 },
1050 PassiveResidues {
1051 chain_id: "B",
1052 res_number: Some(3),
1053 wildcard: "",
1054 atom_str: &None,
1055 },
1056 ]);
1057
1058 let block = "assign ( resid 1 and segid A and (name CA or name CB) )\n (\n ( resid 2 and segid B and (name N or name C) )\n or\n ( resid 3 and segid B )\n ) 2.0 2.0 0.0\n\n";
1059
1060 assert_eq!(observed, block);
1061 }
1062
1063 #[test]
1064 fn test_create_block_active_atoms() {
1065 let mut interactor = Interactor::new(1);
1066 interactor.set_active(vec![1]);
1067 interactor.set_chain("A");
1068 interactor.set_active_atoms(vec!["CA".to_string()]);
1069
1070 let observed = interactor.create_block(vec![PassiveResidues {
1071 chain_id: "B",
1072 res_number: Some(2),
1073 wildcard: "",
1074 atom_str: &None,
1075 }]);
1076
1077 let block =
1078 "assign ( resid 1 and segid A and name CA ) ( resid 2 and segid B ) 2.0 2.0 0.0\n\n";
1079
1080 assert_eq!(observed, block);
1081 }
1082
1083 #[test]
1084 fn test_create_block_passive_atoms() {
1085 let mut interactor = Interactor::new(1);
1086 interactor.set_active(vec![1]);
1087 interactor.set_chain("A");
1088
1089 let observed = interactor.create_block(vec![PassiveResidues {
1090 chain_id: "B",
1091 res_number: Some(2),
1092 wildcard: "",
1093 atom_str: &Some(vec!["CA".to_string()]),
1094 }]);
1095
1096 let block =
1097 "assign ( resid 1 and segid A ) ( resid 2 and segid B and name CA ) 2.0 2.0 0.0\n\n";
1098
1099 assert_eq!(observed, block);
1100 }
1101
1102 #[test]
1103 fn test_create_block_active_passive_atoms() {
1104 let mut interactor = Interactor::new(1);
1105 interactor.set_active(vec![1]);
1106 interactor.set_chain("A");
1107 interactor.set_active_atoms(vec!["CA".to_string()]);
1108
1109 let observed = interactor.create_block(vec![PassiveResidues {
1110 chain_id: "B",
1111 res_number: Some(2),
1112 wildcard: "",
1113 atom_str: &None,
1114 }]);
1115
1116 let block =
1117 "assign ( resid 1 and segid A and name CA ) ( resid 2 and segid B ) 2.0 2.0 0.0\n\n";
1118
1119 assert_eq!(observed, block);
1120 }
1121
1122 #[test]
1123 fn test_create_multiline_block_active_passive_atoms() {
1124 let mut interactor = Interactor::new(1);
1125 interactor.set_active(vec![1]);
1126 interactor.set_chain("A");
1127 interactor.set_active_atoms(vec!["CA".to_string()]);
1128
1129 let observed = interactor.create_block(vec![
1130 PassiveResidues {
1131 chain_id: "B",
1132 res_number: Some(2),
1133 wildcard: "",
1134 atom_str: &Some(vec!["CB".to_string()]),
1135 },
1136 PassiveResidues {
1137 chain_id: "B",
1138 res_number: Some(3),
1139 wildcard: "",
1140 atom_str: &Some(vec!["N".to_string()]),
1141 },
1142 ]);
1143
1144 let block = "assign ( resid 1 and segid A and name CA )\n (\n ( resid 2 and segid B and name CB )\n or\n ( resid 3 and segid B and name N )\n ) 2.0 2.0 0.0\n\n";
1145
1146 assert_eq!(observed, block);
1147 }
1148
1149 #[test]
1150 fn test_create_block_with_distance() {
1151 let mut interactor = Interactor::new(1);
1152 interactor.set_active(vec![1]);
1153 interactor.set_chain("A");
1154 interactor.set_target_distance(5.0);
1155 interactor.set_lower_margin(0.0);
1156
1157 let observed = interactor.create_block(vec![PassiveResidues {
1158 chain_id: "B",
1159 res_number: Some(2),
1160 wildcard: "",
1161 atom_str: &None,
1162 }]);
1163
1164 let block = "assign ( resid 1 and segid A ) ( resid 2 and segid B ) 5.0 0.0 0.0\n\n";
1165
1166 assert_eq!(observed, block);
1167 }
1168
1169 #[test]
1170 fn test_create_block_with_wildcard() {
1171 let mut interactor = Interactor::new(1);
1172 interactor.set_active(vec![1]);
1173 interactor.set_chain("A");
1174 interactor.set_wildcard("and attr z gt 42.00 ");
1175
1176 let observed = interactor.create_block(vec![PassiveResidues {
1177 chain_id: "B",
1178 res_number: Some(2),
1179 wildcard: "",
1180 atom_str: &None,
1181 }]);
1182
1183 let block = "assign ( resid 1 and segid A and attr z gt 42.00 ) ( resid 2 and segid B ) 2.0 2.0 0.0\n\n";
1184
1185 assert_eq!(observed, block);
1186 }
1187
1188 #[test]
1189 fn test_make_pml_string() {
1190 let mut interactor = Interactor::new(1);
1191 interactor.set_active(vec![1]);
1192 interactor.set_chain("A");
1193
1194 let observed = interactor.make_pml_string(vec![PassiveResidues {
1195 chain_id: "B",
1196 res_number: Some(2),
1197 wildcard: "",
1198 atom_str: &None,
1199 }]);
1200
1201 let expected = "distance 1-A, (resi 1 and (name CA or name C1') and chain A), (resi 2 and (name CA or name C1') and chain B)\n";
1202
1203 assert_eq!(observed, expected);
1204 }
1205}