From 79df8803dae93be6b869b319bad55d0071566b39 Mon Sep 17 00:00:00 2001
From: Gabriel Studer <gabriel.studer@unibas.ch>
Date: Fri, 9 Aug 2024 11:20:15 +0200
Subject: [PATCH] scoring: force peptide/nucleotide connectivity based on
 residue numbers

If residue number alignments are enabled, one can assume that two
consecutive residues in terms of residue numbers are connected.
The conop Processor does not necessarily connect them if the bond
is considered unfeasible. This gives inaccurate results in
subsequent stereochemistry checks.
This change forces these connections if and only if resnum_alignments
are enabled
---
 modules/mol/alg/pymod/scoring.py | 38 ++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/modules/mol/alg/pymod/scoring.py b/modules/mol/alg/pymod/scoring.py
index 3c36b80ae..b6c483270 100644
--- a/modules/mol/alg/pymod/scoring.py
+++ b/modules/mol/alg/pymod/scoring.py
@@ -238,6 +238,20 @@ class Scorer:
         LogScript("Cleaning up input structures")
         Molck(self._model, conop.GetDefaultLib(), molck_settings)
         Molck(self._target, conop.GetDefaultLib(), molck_settings)
+
+        if resnum_alignments:
+            # If we're dealing with resnum alignments, we ensure that
+            # consecutive peptide and nucleotide residues are connected based
+            # on residue number information. The conop.Processor only connects
+            # these things if the bonds are actually feasible which can lead to
+            # weird behaviour in stereochemistry checks. Let's say N and C are
+            # too close, it's reported as a clash. If they're too far apart,
+            # they're not reported at all. If we're not dealing with resnum
+            # alignments, we're out of luck as we have no direct residue
+            # connectivity information from residue numbers
+            self._resnum_connect(self._model)
+            self._resnum_connect(self._target)
+
         self._model = self._model.Select("peptide=True or nucleotide=True")
         self._target = self._target.Select("peptide=True or nucleotide=True")
 
@@ -2685,5 +2699,29 @@ class Scorer:
         for a,b in zip(res.ent1_mapped_chains, res.ent2_mapped_chains):
             self._usalign_mapping[b] = a
 
+    def _resnum_connect(self, ent):
+        ed = None
+        for ch in ent.chains:
+            res_list = ch.residues
+            for i in range(len(res_list) - 1):
+                ra = res_list[i]
+                rb = res_list[i+1]
+                if ra.GetNumber().GetNum() + 1 == rb.GetNumber().GetNum():
+                    if ra.IsPeptideLinking() and rb.IsPeptideLinking():
+                        c = ra.FindAtom("C")
+                        n = rb.FindAtom("N")
+                        if c.IsValid() and n.IsValid() and not mol.BondExists(c, n):
+                            if ed is None:
+                                ed = ent.EditXCS(mol.BUFFERED_EDIT)
+                            ed.Connect(c,n,1)
+                    elif ra.IsNucleotideLinking() and rb.IsNucleotideLinking():
+                        o = ra.FindAtom("O3'")
+                        p = rb.FindAtom("P")
+                        if o.IsValid() and p.IsValid()and not mol.BondExists(o, p):
+                            if ed is None:
+                                ed = ent.EditXCS(mol.BUFFERED_EDIT)
+                            ed.Connect(o,p,1)
+
+
 # specify public interface
 __all__ = ('lDDTBSScorer', 'Scorer',)
-- 
GitLab