@@ -4,7 +4,9 @@ use super::ids::{AtomId, ChainId, ResidueId};
44use super :: residue:: { Residue , ResidueType } ;
55use super :: topology:: { Bond , BondOrder } ;
66use slotmap:: { SecondaryMap , SlotMap } ;
7- use std:: collections:: HashMap ;
7+ use std:: collections:: { HashMap , HashSet } ;
8+
9+ const CYSTEINE_SULFUR_GAMMA_ATOM_NAME : & str = "SG" ;
810
911/// Represents a complete molecular system with atoms, residues, chains, and bonds.
1012///
@@ -470,6 +472,68 @@ impl MolecularSystem {
470472 pub fn background_atom_ids ( & self ) -> Vec < AtomId > {
471473 self . background_atoms ( ) . map ( |( id, _) | id) . collect ( )
472474 }
475+
476+ /// Detects and returns the residue IDs of all Cysteine residues involved in disulfide bonds.
477+ ///
478+ /// A disulfide bond is identified by a covalent bond between the Sulfur-Gamma (SG)
479+ /// atoms of two different Cysteine residues. This method correctly handles both
480+ /// `CYS` and `CYX` residue names by relying on the `ResidueType`.
481+ ///
482+ /// # Returns
483+ ///
484+ /// A `HashSet<ResidueId>` containing the IDs of all residues participating in
485+ /// any disulfide bond within the system.
486+ pub fn find_disulfide_bonded_residues ( & self ) -> HashSet < ResidueId > {
487+ let mut bonded_residue_ids = HashSet :: new ( ) ;
488+
489+ // 1. Collect all Cysteine residues and their SG atoms
490+ let cysteine_sg_atoms: HashMap < ResidueId , AtomId > = self
491+ . residues_iter ( )
492+ . filter_map ( |( res_id, residue) | {
493+ if matches ! ( residue. residue_type, Some ( ResidueType :: Cysteine ) ) {
494+ residue
495+ . get_first_atom_id_by_name ( CYSTEINE_SULFUR_GAMMA_ATOM_NAME )
496+ . map ( |sg_id| ( res_id, sg_id) )
497+ } else {
498+ None
499+ }
500+ } )
501+ . collect ( ) ;
502+
503+ // If there are fewer than two Cysteines, no disulfide bonds are possible
504+ if cysteine_sg_atoms. len ( ) < 2 {
505+ return bonded_residue_ids;
506+ }
507+
508+ // Create a reverse map from SG AtomId to ResidueId for quick lookups
509+ let sg_atom_to_residue: HashMap < AtomId , ResidueId > = cysteine_sg_atoms
510+ . iter ( )
511+ . map ( |( & res_id, & atom_id) | ( atom_id, res_id) )
512+ . collect ( ) ;
513+
514+ // 2. Iterate through the collected SG atoms and check their bonds
515+ for ( res_id_a, sg_atom_id_a) in & cysteine_sg_atoms {
516+ if bonded_residue_ids. contains ( res_id_a) {
517+ continue ;
518+ }
519+
520+ if let Some ( neighbors) = self . get_bonded_neighbors ( * sg_atom_id_a) {
521+ for & neighbor_atom_id in neighbors {
522+ // 3. Check if the neighbor is also an SG atom of another Cysteine
523+ if let Some ( res_id_b) = sg_atom_to_residue. get ( & neighbor_atom_id) {
524+ if res_id_a != res_id_b {
525+ // Found a disulfide bond!
526+ bonded_residue_ids. insert ( * res_id_a) ;
527+ bonded_residue_ids. insert ( * res_id_b) ;
528+ break ;
529+ }
530+ }
531+ }
532+ }
533+ }
534+
535+ bonded_residue_ids
536+ }
473537}
474538
475539#[ cfg( test) ]
@@ -897,4 +961,174 @@ mod tests {
897961 assert ! ( background_ids. contains( id_map. get( "UNKNOWN" ) . unwrap( ) ) ) ;
898962 }
899963 }
964+
965+ mod disulfide_bond_detection {
966+ use super :: * ;
967+ use crate :: core:: models:: topology:: BondOrder ;
968+ use nalgebra:: Point3 ;
969+
970+ fn create_disulfide_test_system ( )
971+ -> ( MolecularSystem , ResidueId , ResidueId , ResidueId , ResidueId ) {
972+ let mut system = MolecularSystem :: new ( ) ;
973+ let chain_a_id = system. add_chain ( 'A' , ChainType :: Protein ) ;
974+
975+ let cys1_id = system
976+ . add_residue ( chain_a_id, 1 , "CYS" , Some ( ResidueType :: Cysteine ) )
977+ . unwrap ( ) ;
978+ let cys1_sg_id = system
979+ . add_atom_to_residue (
980+ cys1_id,
981+ Atom :: new (
982+ CYSTEINE_SULFUR_GAMMA_ATOM_NAME ,
983+ cys1_id,
984+ Point3 :: new ( 0.0 , 0.0 , 0.0 ) ,
985+ ) ,
986+ )
987+ . unwrap ( ) ;
988+
989+ let cys2_id = system
990+ . add_residue ( chain_a_id, 2 , "CYX" , Some ( ResidueType :: Cysteine ) )
991+ . unwrap ( ) ;
992+ let cys2_sg_id = system
993+ . add_atom_to_residue (
994+ cys2_id,
995+ Atom :: new (
996+ CYSTEINE_SULFUR_GAMMA_ATOM_NAME ,
997+ cys2_id,
998+ Point3 :: new ( 2.0 , 0.0 , 0.0 ) ,
999+ ) ,
1000+ )
1001+ . unwrap ( ) ;
1002+
1003+ let cys3_id = system
1004+ . add_residue ( chain_a_id, 3 , "CYS" , Some ( ResidueType :: Cysteine ) )
1005+ . unwrap ( ) ;
1006+ system
1007+ . add_atom_to_residue (
1008+ cys3_id,
1009+ Atom :: new (
1010+ CYSTEINE_SULFUR_GAMMA_ATOM_NAME ,
1011+ cys3_id,
1012+ Point3 :: new ( 10.0 , 0.0 , 0.0 ) ,
1013+ ) ,
1014+ )
1015+ . unwrap ( ) ;
1016+
1017+ let ala4_id = system
1018+ . add_residue ( chain_a_id, 4 , "ALA" , Some ( ResidueType :: Alanine ) )
1019+ . unwrap ( ) ;
1020+ system
1021+ . add_atom_to_residue (
1022+ ala4_id,
1023+ Atom :: new ( "CB" , ala4_id, Point3 :: new ( 12.0 , 0.0 , 0.0 ) ) ,
1024+ )
1025+ . unwrap ( ) ;
1026+
1027+ system
1028+ . add_bond ( cys1_sg_id, cys2_sg_id, BondOrder :: Single )
1029+ . unwrap ( ) ;
1030+
1031+ ( system, cys1_id, cys2_id, cys3_id, ala4_id)
1032+ }
1033+
1034+ #[ test]
1035+ fn find_disulfide_bonded_residues_identifies_correct_pair ( ) {
1036+ let ( system, cys1_id, cys2_id, cys3_id, ala4_id) = create_disulfide_test_system ( ) ;
1037+
1038+ let bonded_residues = system. find_disulfide_bonded_residues ( ) ;
1039+
1040+ assert_eq ! ( bonded_residues. len( ) , 2 ) ;
1041+ assert ! ( bonded_residues. contains( & cys1_id) ) ;
1042+ assert ! ( bonded_residues. contains( & cys2_id) ) ;
1043+ assert ! ( !bonded_residues. contains( & cys3_id) ) ;
1044+ assert ! ( !bonded_residues. contains( & ala4_id) ) ;
1045+ }
1046+
1047+ #[ test]
1048+ fn find_disulfide_bonded_residues_returns_empty_for_no_bonds ( ) {
1049+ let ( mut system, cys1_id, cys2_id, _, _) = create_disulfide_test_system ( ) ;
1050+ let cys1_sg_id = system
1051+ . residue ( cys1_id)
1052+ . unwrap ( )
1053+ . get_first_atom_id_by_name ( CYSTEINE_SULFUR_GAMMA_ATOM_NAME )
1054+ . unwrap ( ) ;
1055+ let cys2_sg_id = system
1056+ . residue ( cys2_id)
1057+ . unwrap ( )
1058+ . get_first_atom_id_by_name ( CYSTEINE_SULFUR_GAMMA_ATOM_NAME )
1059+ . unwrap ( ) ;
1060+
1061+ system
1062+ . bonds
1063+ . retain ( |bond| !( bond. contains ( cys1_sg_id) && bond. contains ( cys2_sg_id) ) ) ;
1064+ system. bond_adjacency . get_mut ( cys1_sg_id) . unwrap ( ) . clear ( ) ;
1065+ system. bond_adjacency . get_mut ( cys2_sg_id) . unwrap ( ) . clear ( ) ;
1066+
1067+ let bonded_residues = system. find_disulfide_bonded_residues ( ) ;
1068+ assert ! (
1069+ bonded_residues. is_empty( ) ,
1070+ "Expected no bonded residues after removing the bond"
1071+ ) ;
1072+ }
1073+
1074+ #[ test]
1075+ fn find_disulfide_bonded_residues_returns_empty_for_no_cysteines ( ) {
1076+ let mut system = MolecularSystem :: new ( ) ;
1077+ let chain_a_id = system. add_chain ( 'A' , ChainType :: Protein ) ;
1078+ system
1079+ . add_residue ( chain_a_id, 4 , "ALA" , Some ( ResidueType :: Alanine ) )
1080+ . unwrap ( ) ;
1081+
1082+ let bonded_residues = system. find_disulfide_bonded_residues ( ) ;
1083+ assert ! ( bonded_residues. is_empty( ) ) ;
1084+ }
1085+
1086+ #[ test]
1087+ fn find_disulfide_bonded_residues_handles_multiple_bonds ( ) {
1088+ let ( mut system, cys1_id, cys2_id, cys3_id, ala4_id) = create_disulfide_test_system ( ) ;
1089+
1090+ let chain_b_id = system. add_chain ( 'B' , ChainType :: Protein ) ;
1091+ let cys10_id = system
1092+ . add_residue ( chain_b_id, 10 , "CYS" , Some ( ResidueType :: Cysteine ) )
1093+ . unwrap ( ) ;
1094+ let cys10_sg_id = system
1095+ . add_atom_to_residue (
1096+ cys10_id,
1097+ Atom :: new (
1098+ CYSTEINE_SULFUR_GAMMA_ATOM_NAME ,
1099+ cys10_id,
1100+ Point3 :: new ( 50.0 , 0.0 , 0.0 ) ,
1101+ ) ,
1102+ )
1103+ . unwrap ( ) ;
1104+
1105+ let cys20_id = system
1106+ . add_residue ( chain_b_id, 20 , "CYS" , Some ( ResidueType :: Cysteine ) )
1107+ . unwrap ( ) ;
1108+ let cys20_sg_id = system
1109+ . add_atom_to_residue (
1110+ cys20_id,
1111+ Atom :: new (
1112+ CYSTEINE_SULFUR_GAMMA_ATOM_NAME ,
1113+ cys20_id,
1114+ Point3 :: new ( 52.0 , 0.0 , 0.0 ) ,
1115+ ) ,
1116+ )
1117+ . unwrap ( ) ;
1118+
1119+ system
1120+ . add_bond ( cys10_sg_id, cys20_sg_id, BondOrder :: Single )
1121+ . unwrap ( ) ;
1122+
1123+ let bonded_residues = system. find_disulfide_bonded_residues ( ) ;
1124+
1125+ assert_eq ! ( bonded_residues. len( ) , 4 ) ;
1126+ assert ! ( bonded_residues. contains( & cys1_id) ) ;
1127+ assert ! ( bonded_residues. contains( & cys2_id) ) ;
1128+ assert ! ( !bonded_residues. contains( & cys3_id) ) ;
1129+ assert ! ( !bonded_residues. contains( & ala4_id) ) ;
1130+ assert ! ( bonded_residues. contains( & cys10_id) ) ;
1131+ assert ! ( bonded_residues. contains( & cys20_id) ) ;
1132+ }
1133+ }
9001134}
0 commit comments