From 5e07329bc2b2b05faef8765aeb5299741a91bc0b Mon Sep 17 00:00:00 2001
From: Gabriel Studer <gabriel.studer@unibas.ch>
Date: Tue, 2 Jul 2024 10:36:53 +0200
Subject: [PATCH] avoid usage of n_ter/c_ter generic props when reconstructing
 sidechains

There was a collision of properties with same name that are set in
OpenStructure energy minimization. If you did a sidechain reconstruction
where the properties are set, then you attach some stuff to terminal
residues, non-terminal residues still have the n_ter/c_ter property set.
This crashed the parameterization of the energy minimization.
---
 modelling/pymod/_reconstruct_sidechains.py | 79 ++++++++++++++--------
 1 file changed, 51 insertions(+), 28 deletions(-)

diff --git a/modelling/pymod/_reconstruct_sidechains.py b/modelling/pymod/_reconstruct_sidechains.py
index e10f1948..f50f2860 100644
--- a/modelling/pymod/_reconstruct_sidechains.py
+++ b/modelling/pymod/_reconstruct_sidechains.py
@@ -84,14 +84,17 @@ def _GetDihedrals(res_list):
     return phi_angles, psi_angles
 
 def _AddBackboneFrameResidues(frame_residues, res_list, rotamer_ids,
-                              rotamer_constructor, phi_angles, psi_angles):
+                              rotamer_constructor, phi_angles, psi_angles,
+                              n_ter_residues, c_ter_residues):
     '''Update frame_residues (list) with BackboneFrameResidues for res_list.'''
     for i,r in enumerate(res_list):
         try:
+            is_n_ter = r.handle.GetHashCode() in n_ter_residues
+            is_c_ter = r.handle.GetHashCode() in c_ter_residues
             frame_residue = rotamer_constructor.ConstructBackboneFrameResidue(\
                                 r.handle, rotamer_ids[i], i,
                                 phi_angles[i], psi_angles[i], 
-                                r.HasProp("n_ter"), r.HasProp("c_ter"))
+                                is_n_ter, is_c_ter)
             frame_residues.append(frame_residue)
         except:
             continue
@@ -149,7 +152,8 @@ def _AddLigandFrameResidues(frame_residues, ent_lig, rotamer_constructor, offset
 def _AddSidechainFrameResidues(frame_residues, incomplete_sidechains,
                                keep_sidechains, res_list, rotamer_ids,
                                phi_angles, psi_angles,
-                               rotamer_constructor, cystein_indices=None):
+                               rotamer_constructor, cystein_indices,
+                               n_ter_residues, c_ter_residues):
     '''Update frame_residues (list) with SidechainFrameResidues for res_list,
     incomplete_sidechains (list of indices) with sidechains to be constructed,
     and (if given) cystein_indices (list of indices) with all CYS (appended).
@@ -166,11 +170,13 @@ def _AddSidechainFrameResidues(frame_residues, incomplete_sidechains,
                 continue
 
             try:
+                is_n_ter = r.handle.GetHashCode() in n_ter_residues
+                is_c_ter = r.handle.GetHashCode() in c_ter_residues
                 frame_residue = rotamer_constructor.ConstructSidechainFrameResidue(\
                                     r.handle, rotamer_ids[i], i, phi_angles[i],
-                                    psi_angles[i], r.HasProp("n_ter"), r.HasProp("c_ter"))
+                                    psi_angles[i], is_n_ter, is_c_ter)
                 frame_residues.append(frame_residue)
-            except:
+            except Exception as e:
                 incomplete_sidechains.append(i)
     else:
         # no frame residues to create, just update incomplete_sidechains
@@ -187,7 +193,8 @@ def _AddCysteinFrameResidues(frame_residues, incomplete_sidechains,
                              keep_sidechains, res_list, rotamer_ids,
                              phi_angles, psi_angles,
                              rot_constructor, cystein_indices,
-                             disulfid_indices, disulfid_rotamers):
+                             disulfid_indices, disulfid_rotamers,
+                             n_ter_residues, c_ter_residues):
     '''Update frame_residues (list) with cysteins.
     Parameters as in _AddSidechainFrameResidues.
     Some cysteins (in disulfid_indices) get special treatment as disulfid
@@ -208,11 +215,12 @@ def _AddCysteinFrameResidues(frame_residues, incomplete_sidechains,
             continue # already handled
         if keep_sidechains:
             try:
+                is_n_ter = res_list[idx].handle.GetHashCode() in n_ter_residues
+                is_c_ter = res_list[idx].handle.GetHashCode() in c_ter_residues
                 frame_residue = rot_constructor.ConstructSidechainFrameResidue(\
                                     res_list[idx].handle, rotamer_ids[idx],
                                     idx, phi_angles[idx], psi_angles[idx],
-                                    res_list[idx].handle.HasProp("n_ter"),
-                                    res_list[idx].handle.HasProp("c_ter"))
+                                    is_n_ter, is_c_ter)
                 frame_residues.append(frame_residue)
             except:
                 incomplete_sidechains.append(idx) 
@@ -220,27 +228,29 @@ def _AddCysteinFrameResidues(frame_residues, incomplete_sidechains,
             incomplete_sidechains.append(idx)
 
 def _GetRotamerGroup(res_handle, rot_id, res_idx, rot_lib, rot_constructor,
-                     phi, psi, use_frm, bbdep, 
-                     probability_cutoff = 0.98):
+                     phi, psi, use_frm, bbdep, probability_cutoff,
+                     n_ter_residues, c_ter_residues):
+
+    is_n_ter = res_handle.handle.GetHashCode() in n_ter_residues 
+    is_c_ter = res_handle.handle.GetHashCode() in c_ter_residues 
 
     if use_frm:
         return rot_constructor.ConstructFRMRotamerGroup(res_handle, rot_id,
                                                         res_idx, rot_lib,
                                                         phi, psi, 
-                                                        res_handle.HasProp("n_ter"),
-                                                        res_handle.HasProp("c_ter"),
+                                                        is_n_ter, is_c_ter,
                                                         probability_cutoff)
     else:        
         return rot_constructor.ConstructRRMRotamerGroup(res_handle, rot_id,
                                                         res_idx, rot_lib,
                                                         phi, psi,
-                                                        res_handle.HasProp("n_ter"),
-                                                        res_handle.HasProp("c_ter"),
+                                                        is_n_ter, is_c_ter,
                                                         probability_cutoff)
 
 
 def _GetRotamerGroups(res_list, rot_ids, indices, rot_lib, rot_constructor,
-                      phi_angles, psi_angles, use_frm, bbdep, frame_residues):
+                      phi_angles, psi_angles, use_frm, bbdep, frame_residues,
+                      n_ter_residues, c_ter_residues):
     '''Get list of rotamer groups from subset of res_list.
     Residues are chosen as res_list[i] for i in indices and only if a rotamer
     group can be created.
@@ -295,7 +305,8 @@ def _GetRotamerGroups(res_list, rot_ids, indices, rot_lib, rot_constructor,
         try:
             rot_group = _GetRotamerGroup(r.handle, rot_id, i, rot_lib,
                                          rot_constructor, phi_angles[i],
-                                         psi_angles[i], use_frm, bbdep)
+                                         psi_angles[i], use_frm, bbdep, 0.98,
+                                         n_ter_residues, c_ter_residues)
         except:
             continue
         # keep best ones
@@ -308,7 +319,8 @@ def _GetRotamerGroups(res_list, rot_ids, indices, rot_lib, rot_constructor,
 
 def _GetDisulfidBridges(frame_residues, keep_sidechains, cystein_indices, 
                         res_list, rotamer_library, use_frm, bbdep, 
-                        rotamer_constructor, phi_angles, psi_angles):
+                        rotamer_constructor, phi_angles, psi_angles,
+                        n_ter_residues, c_ter_residues):
     '''Get disulfid bridges for CYS and according rotamers.
     CYS are identified by by items in cystein_indices (into res_list).
     Returns: disulfid_indices: list of res. index in bridge,
@@ -346,12 +358,13 @@ def _GetDisulfidBridges(frame_residues, keep_sidechains, cystein_indices,
                 # residue in the rotamer constructor
                 phi = _GetPhiAngle(r)
                 psi = _GetPsiAngle(r)
+                is_n_ter = r.handle.GetHashCode() in n_ter_residues
+                is_c_ter = r.handle.GetHashCode() in c_ter_residues
                 cys_frame_res = \
                 rotamer_constructor.ConstructSidechainFrameResidue(r.handle, 
                                                                    sidechain.CYD, 
                                                                    0, phi, psi,
-                                                                   r.HasProp("n_ter"),
-                                                                   r.HasProp("c_ter"))
+                                                                   is_n_ter, is_c_ter)
                 for j in range(len(cys_frame_res)):
                     if cys_frame_res[j].GetName() == "SG":
                         particle_list = [cys_frame_res[j]]
@@ -372,7 +385,9 @@ def _GetDisulfidBridges(frame_residues, keep_sidechains, cystein_indices,
                                          rotamer_library,
                                          rotamer_constructor, 
                                          phi_angles[i],
-                                         psi_angles[i], True, bbdep)
+                                         psi_angles[i], True, bbdep, 0.98,
+                                         n_ter_residues, c_ter_residues)
+
         rot_group.AddFrameEnergy(frame)
         cystein_rot_groups.append(rot_group)
         indices_with_rot_groups.append(i)
@@ -513,15 +528,18 @@ def ReconstructSidechains(ent, keep_sidechains=False, build_disulfids=True,
     phi_angles, psi_angles = _GetDihedrals(prot.residues)
 
     # set nter and cter (needed in _AddBackboneFrameResidues)
+    n_ter_residues = set()
+    c_ter_residues = set()
     for c in prot.chains:
-        c.residues[0].SetIntProp("n_ter",1)
-        c.residues[-1].SetIntProp("c_ter",1)
+        n_ter_residues.add(c.residues[0].handle.GetHashCode())
+        c_ter_residues.add(c.residues[-1].handle.GetHashCode())
 
     # build up frame
     frame_residues = list()         # list of frame residues connected to frame
     incomplete_sidechains = list()  # residue indices
     _AddBackboneFrameResidues(frame_residues, prot.residues, rotamer_ids,
-                              rotamer_constructor, phi_angles, psi_angles)
+                              rotamer_constructor, phi_angles, psi_angles,
+                              n_ter_residues, c_ter_residues)
     
     # add ligands?
     if consider_ligands:
@@ -537,7 +555,8 @@ def ReconstructSidechains(ent, keep_sidechains=False, build_disulfids=True,
         _AddSidechainFrameResidues(frame_residues, incomplete_sidechains,
                                    keep_sidechains, prot.residues, rotamer_ids,
                                    phi_angles, psi_angles,
-                                   rotamer_constructor, cystein_indices)
+                                   rotamer_constructor, cystein_indices,
+                                   n_ter_residues, c_ter_residues)
         # update frame_residues, incomplete_sidechains with cysteins (if needed)
         if len(cystein_indices) > 0:
             # get disulfid bridges and according rotamers
@@ -545,25 +564,29 @@ def ReconstructSidechains(ent, keep_sidechains=False, build_disulfids=True,
                 _GetDisulfidBridges(frame_residues, keep_sidechains, 
                                     cystein_indices, prot.residues,
                                     rotamer_library, use_frm, bbdep,
-                                    rotamer_constructor, phi_angles, psi_angles)
+                                    rotamer_constructor, phi_angles, psi_angles,
+                                    n_ter_residues, c_ter_residues)
             # update frame_residues, incomplete_sidechains
             _AddCysteinFrameResidues(frame_residues, incomplete_sidechains,
                                      keep_sidechains, prot.residues, rotamer_ids,
                                      phi_angles, psi_angles,
                                      rotamer_constructor, cystein_indices,
-                                     disulfid_indices, disulfid_rotamers)
+                                     disulfid_indices, disulfid_rotamers,
+                                     n_ter_residues, c_ter_residues)
     else:
         # update frame_residues, incomplete_sidechains
         _AddSidechainFrameResidues(frame_residues, incomplete_sidechains,
                                    keep_sidechains, prot.residues, rotamer_ids,
                                    phi_angles, psi_angles,
-                                   rotamer_constructor)
+                                   rotamer_constructor, None,
+                                   n_ter_residues, c_ter_residues)
     
     # get rotamer groups and residues they're linked to
     rotamer_groups, residues_with_rotamer_group = \
         _GetRotamerGroups(prot.residues, rotamer_ids, incomplete_sidechains,
                           rotamer_library, rotamer_constructor, phi_angles,
-                          psi_angles, use_frm, bbdep, frame_residues)
+                          psi_angles, use_frm, bbdep, frame_residues,
+                          n_ter_residues, c_ter_residues)
 
     # set up graph and solve to get best rotamers
     if use_frm:
-- 
GitLab