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!("resi {} and name CA and chain {}", resnum, self.chain);
644
645 for passive_resnum in &passive_res {
646 let passive_sel = format!(
647 "resi {} and name CA and chain {}",
648 passive_resnum.res_number.unwrap(),
649 passive_resnum.chain_id
650 );
651
652 pml.push_str(
653 format!(
654 "distance {}, ({}), ({})\n",
655 identifier, active_sel, passive_sel
656 )
657 .as_str(),
658 )
659 }
660 }
661
662 pml
663 }
664}
665
666#[derive(Debug, Clone)]
667pub struct PassiveResidues<'a> {
668 pub chain_id: &'a str,
669 pub res_number: Option<i16>,
670 wildcard: &'a str,
671 atom_str: &'a Option<Vec<String>>,
673}
674
675pub fn collect_residues(interactors: Vec<&Interactor>) -> Vec<PassiveResidues<'_>> {
691 let mut resnums = Vec::new();
692 for interactor in interactors {
693 let active = interactor.active().iter().map(|&x| PassiveResidues {
694 chain_id: interactor.chain(),
695 res_number: Some(x),
696 wildcard: interactor.wildcard(),
697 atom_str: interactor.active_atoms(),
698 });
699
700 let passive = interactor.passive().iter().map(|&x| PassiveResidues {
701 chain_id: interactor.chain(),
702 res_number: Some(x),
703 wildcard: interactor.wildcard(),
704 atom_str: interactor.passive_atoms(),
705 });
706
707 resnums.extend(active);
708 resnums.extend(passive);
709
710 if interactor.active().is_empty() && interactor.passive().is_empty() {
712 resnums.push(PassiveResidues {
713 chain_id: interactor.chain(),
714 res_number: None,
715 wildcard: interactor.wildcard(),
716 atom_str: &None,
717 });
718 }
719 }
720 resnums
721}
722
723pub fn format_distance_string(
739 target: &Option<f64>,
740 lower: &Option<f64>,
741 upper: &Option<f64>,
742) -> String {
743 let target = match target {
744 Some(target) => target,
745 None => &2.0,
746 };
747
748 let lower = match lower {
749 Some(lower) => lower,
750 None => &2.0,
751 };
752
753 let upper = match upper {
754 Some(upper) => upper,
755 None => &0.0,
756 };
757
758 format!("{:.1} {:.1} {:.1}", target, lower, upper)
759}
760
761pub fn format_atom_string(atoms: &Option<Vec<String>>) -> String {
775 match atoms {
776 Some(atoms) if atoms.len() > 1 => {
777 let atoms: String = atoms
778 .iter()
779 .map(|x| {
780 if x.contains("-") || x.contains("+") {
781 format!(r#"name "{}""#, x)
782 } else {
783 format!("name {}", x)
784 }
785 })
786 .collect::<Vec<String>>()
787 .join(" or ");
788
789 format!(" and ({})", atoms)
790 }
791 Some(atoms) if atoms.len() == 1 => {
792 if atoms[0].contains("-") || atoms[0].contains("+") {
793 format!(r#" and name "{}""#, atoms[0])
794 } else {
795 format!(" and name {}", atoms[0])
796 }
797 }
798 _ => "".to_string(),
799 }
800}
801
802#[cfg(test)]
803mod tests {
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_passive = [
896 938, 965, 953, 944, 933, 958, 966, 972, 931, 936, 961, 929, 943, 954, 932, 945, 942,
897 957, 955, 947, 940, 941, 937, 964, 970, 930, 969, 968, 950, 952, 959, 971, 967, 956,
898 946, 960, 962, 935, 948, 951, 934, 939,
899 ];
900
901 assert_eq!(
902 interactor.passive(),
903 &expected_passive.iter().cloned().collect()
904 );
905 }
906
907 #[test]
908 fn test_remove_buried_active_residues() {
909 let mut interactor = Interactor::new(1);
910
911 interactor.load_structure("tests/data/complex.pdb").unwrap();
912 interactor.set_chain("A");
913 interactor.filter_buried = Some(true);
914 interactor.filter_buried_cutoff = Some(0.7);
915 interactor.set_active(vec![949, 931]);
916 interactor.remove_buried_residues();
917
918 let expected_active = [931];
919
920 assert_eq!(
921 interactor.active(),
922 &expected_active.iter().cloned().collect()
923 );
924 }
925
926 #[test]
927 fn test_create_block_multiline() {
928 let mut interactor = Interactor::new(1);
929 interactor.set_active(vec![1]);
930 interactor.set_chain("A");
931
932 let observed = interactor.create_block(vec![
933 PassiveResidues {
934 chain_id: "B",
935 res_number: Some(2),
936 wildcard: "",
937 atom_str: &None,
938 },
939 PassiveResidues {
940 chain_id: "B",
941 res_number: Some(3),
942 wildcard: "",
943 atom_str: &None,
944 },
945 ]);
946
947 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";
948
949 assert_eq!(observed, block);
950 }
951
952 #[test]
953 fn test_create_block_oneline() {
954 let mut interactor = Interactor::new(1);
955 interactor.set_active(vec![1]);
956 interactor.set_chain("A");
957
958 let observed = interactor.create_block(vec![PassiveResidues {
959 chain_id: "B",
960 res_number: Some(2),
961 wildcard: "",
962 atom_str: &None,
963 }]);
964
965 let block = "assign ( resid 1 and segid A ) ( resid 2 and segid B ) 2.0 2.0 0.0\n\n";
966
967 assert_eq!(observed, block);
968 }
969
970 #[test]
971 fn test_create_block_oneline_atom_subset() {
972 let mut interactor = Interactor::new(1);
973 interactor.set_active(vec![1]);
974 interactor.set_chain("A");
975 interactor.set_active_atoms(vec!["CA".to_string(), "CB".to_string()]);
976
977 let observed = interactor.create_block(vec![PassiveResidues {
978 chain_id: "B",
979 res_number: Some(2),
980 wildcard: "",
981 atom_str: &None,
982 }]);
983
984 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";
985
986 assert_eq!(observed, block);
987 }
988
989 #[test]
990 fn test_create_block_multiline_atom_subset() {
991 let mut interactor = Interactor::new(1);
992 interactor.set_active(vec![1]);
993 interactor.set_chain("A");
994 interactor.set_active_atoms(vec!["CA".to_string(), "CB".to_string()]);
995 interactor.set_passive_atoms(vec!["CA".to_string(), "CB".to_string()]);
996 let observed = interactor.create_block(vec![
997 PassiveResidues {
998 chain_id: "B",
999 res_number: Some(2),
1000 wildcard: "",
1001 atom_str: &None,
1002 },
1003 PassiveResidues {
1004 chain_id: "B",
1005 res_number: Some(3),
1006 wildcard: "",
1007 atom_str: &None,
1008 },
1009 ]);
1010
1011 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";
1012
1013 assert_eq!(observed, block);
1014 }
1015
1016 #[test]
1017 fn test_create_block_multiline_atom_subset_passive() {
1018 let mut interactor = Interactor::new(1);
1019 interactor.set_active(vec![1]);
1020 interactor.set_chain("A");
1021 interactor.set_active_atoms(vec!["CA".to_string(), "CB".to_string()]);
1022 let observed = interactor.create_block(vec![
1023 PassiveResidues {
1024 chain_id: "B",
1025 res_number: Some(2),
1026 wildcard: "",
1027 atom_str: &Some(vec!["N".to_string(), "C".to_string()]),
1028 },
1029 PassiveResidues {
1030 chain_id: "B",
1031 res_number: Some(3),
1032 wildcard: "",
1033 atom_str: &None,
1034 },
1035 ]);
1036
1037 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";
1038
1039 assert_eq!(observed, block);
1040 }
1041
1042 #[test]
1043 fn test_create_block_active_atoms() {
1044 let mut interactor = Interactor::new(1);
1045 interactor.set_active(vec![1]);
1046 interactor.set_chain("A");
1047 interactor.set_active_atoms(vec!["CA".to_string()]);
1048
1049 let observed = interactor.create_block(vec![PassiveResidues {
1050 chain_id: "B",
1051 res_number: Some(2),
1052 wildcard: "",
1053 atom_str: &None,
1054 }]);
1055
1056 let block =
1057 "assign ( resid 1 and segid A and name CA ) ( resid 2 and segid B ) 2.0 2.0 0.0\n\n";
1058
1059 assert_eq!(observed, block);
1060 }
1061
1062 #[test]
1063 fn test_create_block_passive_atoms() {
1064 let mut interactor = Interactor::new(1);
1065 interactor.set_active(vec![1]);
1066 interactor.set_chain("A");
1067
1068 let observed = interactor.create_block(vec![PassiveResidues {
1069 chain_id: "B",
1070 res_number: Some(2),
1071 wildcard: "",
1072 atom_str: &Some(vec!["CA".to_string()]),
1073 }]);
1074
1075 let block =
1076 "assign ( resid 1 and segid A ) ( resid 2 and segid B and name CA ) 2.0 2.0 0.0\n\n";
1077
1078 assert_eq!(observed, block);
1079 }
1080
1081 #[test]
1082 fn test_create_block_active_passive_atoms() {
1083 let mut interactor = Interactor::new(1);
1084 interactor.set_active(vec![1]);
1085 interactor.set_chain("A");
1086 interactor.set_active_atoms(vec!["CA".to_string()]);
1087
1088 let observed = interactor.create_block(vec![PassiveResidues {
1089 chain_id: "B",
1090 res_number: Some(2),
1091 wildcard: "",
1092 atom_str: &None,
1093 }]);
1094
1095 let block =
1096 "assign ( resid 1 and segid A and name CA ) ( resid 2 and segid B ) 2.0 2.0 0.0\n\n";
1097
1098 assert_eq!(observed, block);
1099 }
1100
1101 #[test]
1102 fn test_create_multiline_block_active_passive_atoms() {
1103 let mut interactor = Interactor::new(1);
1104 interactor.set_active(vec![1]);
1105 interactor.set_chain("A");
1106 interactor.set_active_atoms(vec!["CA".to_string()]);
1107
1108 let observed = interactor.create_block(vec![
1109 PassiveResidues {
1110 chain_id: "B",
1111 res_number: Some(2),
1112 wildcard: "",
1113 atom_str: &Some(vec!["CB".to_string()]),
1114 },
1115 PassiveResidues {
1116 chain_id: "B",
1117 res_number: Some(3),
1118 wildcard: "",
1119 atom_str: &Some(vec!["N".to_string()]),
1120 },
1121 ]);
1122
1123 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";
1124
1125 assert_eq!(observed, block);
1126 }
1127
1128 #[test]
1129 fn test_create_block_with_distance() {
1130 let mut interactor = Interactor::new(1);
1131 interactor.set_active(vec![1]);
1132 interactor.set_chain("A");
1133 interactor.set_target_distance(5.0);
1134 interactor.set_lower_margin(0.0);
1135
1136 let observed = interactor.create_block(vec![PassiveResidues {
1137 chain_id: "B",
1138 res_number: Some(2),
1139 wildcard: "",
1140 atom_str: &None,
1141 }]);
1142
1143 let block = "assign ( resid 1 and segid A ) ( resid 2 and segid B ) 5.0 0.0 0.0\n\n";
1144
1145 assert_eq!(observed, block);
1146 }
1147
1148 #[test]
1149 fn test_create_block_with_wildcard() {
1150 let mut interactor = Interactor::new(1);
1151 interactor.set_active(vec![1]);
1152 interactor.set_chain("A");
1153 interactor.set_wildcard("and attr z gt 42.00 ");
1154
1155 let observed = interactor.create_block(vec![PassiveResidues {
1156 chain_id: "B",
1157 res_number: Some(2),
1158 wildcard: "",
1159 atom_str: &None,
1160 }]);
1161
1162 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";
1163
1164 assert_eq!(observed, block);
1165 }
1166}