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