From 76f1bfc9967d3a74b9db3c6d90997ae2be395168 Mon Sep 17 00:00:00 2001
From: Gerardo Tauriello <gerardo.tauriello@unibas.ch>
Date: Wed, 27 Apr 2016 13:13:52 +0200
Subject: [PATCH] SCHWED-872: take over ligands of templates and (for now)
 ignore them while modelling

---
 loop/src/backbone_loop_score.cc            |  6 +-
 modelling/doc/index.rst                    |  8 ++-
 modelling/pymod/_pipeline.py               | 80 +++++++++++++++-------
 modelling/src/model.cc                     |  8 ++-
 sidechain/pymod/_reconstruct_sidechains.py |  3 +-
 5 files changed, 74 insertions(+), 31 deletions(-)

diff --git a/loop/src/backbone_loop_score.cc b/loop/src/backbone_loop_score.cc
index 5400261e..8c432337 100644
--- a/loop/src/backbone_loop_score.cc
+++ b/loop/src/backbone_loop_score.cc
@@ -1492,9 +1492,9 @@ void BackboneLoopScorer::SetEnvironment(const ost::mol::EntityHandle& env){
   ost::mol::ChainHandleList chain_list = env.GetChainList();
   ost::mol::ResidueHandleList res_list;
 
-  if(chain_list.size() > num_chains_){
+  if(chain_list.size() < num_chains_){
     std::stringstream ss;
-    ss << "More chains than seqres sequences when setting the loop scorers environment! ";
+    ss << "Less chains than seqres sequences when setting the loop scorers environment! ";
     ss << "Don't do anything...";
     throw promod3::Error(ss.str());
   }
@@ -1548,7 +1548,7 @@ void BackboneLoopScorer::SetEnvironment(const ost::mol::EntityHandle& env){
   geom::Vec3 n_pos, ca_pos, c_pos, o_pos, cb_pos, old_pos;
   uint num, index;
 
-  for(uint i = 0; i < chain_list.size(); ++i){
+  for(uint i = 0; i < num_chains_; ++i){
 
     res_list = chain_list[i].GetResidueList();
     for(ost::mol::ResidueHandleList::iterator j = res_list.begin(),e = res_list.end();
diff --git a/modelling/doc/index.rst b/modelling/doc/index.rst
index 3ea3dff2..b5415e77 100644
--- a/modelling/doc/index.rst
+++ b/modelling/doc/index.rst
@@ -84,7 +84,13 @@ Modelling Pipeline
               list of alignment handles for raw model with multiple chains.
   :type aln:  :class:`~ost.seq.AlignmentHandle` / :class:`~ost.seq.AlignmentList`
 
-  :param include_ligands: True, if we wish to include ligands in the model.
+  :param include_ligands: True, if we wish to include ligands in the model. This
+                          searches for ligands in all OST handles of the views
+                          attached to the alignments. Ligands are identified
+                          with the "ligand" property in the handle (set by OST
+                          based on HET records) or by the chain name '_' (as set
+                          in SMTL). All ligands are added to a new chain named
+                          '_'.
   :type include_ligands:  :class:`bool`
 
   :param chain_names: Chains are named by a single chanacter taken from this.
diff --git a/modelling/pymod/_pipeline.py b/modelling/pymod/_pipeline.py
index a09efd02..9790e2ab 100644
--- a/modelling/pymod/_pipeline.py
+++ b/modelling/pymod/_pipeline.py
@@ -15,6 +15,56 @@ from ost import mol
 from ost.mol import mm
 import os
 
+###############################################################################
+# helper functions
+def _RemoveHydrogens(ent):
+    '''Hydrogen naming depends on the force field used.
+    To be on the safe side, we simply remove all of them.
+    Note that the pipeline ignores hydrogens when building the raw model and
+    when rebuilding sidechains.
+    '''
+    edi = ent.EditXCS()
+    ha = ent.Select('ele=H')
+    for a in ha.atoms:
+      edi.DeleteAtom(a.handle)
+
+def _SetupMmSimulation(mhandle):
+    '''Get mm simulation object for the model.'''
+    # get general settings 
+    settings = mm.Settings()
+    settings.integrator = mm.LangevinIntegrator(310, 1, 0.002)
+    settings.init_temperature = 0
+    settings.nonbonded_method = mm.NonbondedMethod.CutoffNonPeriodic
+    settings.keep_ff_specific_naming = False
+    # use fast CPU platform by default
+    settings.platform = mm.Platform.CPU
+    if os.getenv('PM3_OPENMM_CPU_THREADS') is None:
+        settings.cpu_properties["CpuThreads"] = "1"
+    else:
+        settings.cpu_properties["CpuThreads"] = os.getenv('PM3_OPENMM_CPU_THREADS')
+    # prepare protein
+    model = mol.CreateEntityFromView(mhandle.model.Select("cname!='_'"), True)
+    _RemoveHydrogens(model)
+    # NOTE: both FF work, performance is similar, CHARMM slightly better
+    settings.forcefield = mm.LoadCHARMMForcefield()
+    #settings.forcefield = mm.LoadAMBERForcefield()
+
+    # TODO: add ligands
+    # TODO: check when platform problem appears when having multiple topologies!
+
+    # finally set up the simulation
+    try:
+        sim = mm.Simulation(model, settings)
+    except RuntimeError as ex:
+        ost.LogWarning("OpenMM failed to initialize with error: %s" % str(ex))
+        # switch to "mm.Platform.Reference" as fallback
+        settings.platform = mm.Platform.Reference
+        sim = mm.Simulation(model, settings)
+        ost.LogWarning("Switched to slower reference platform of OpenMM!")
+    return sim
+
+###############################################################################
+
 def BuildSidechains(mhandle, merge_distance=4, scorer=None, fragment_db=None,
                     structure_db=None, torsion_sampler=None):
     '''Build sidechains for model.
@@ -121,26 +171,8 @@ def MinimizeModelEnergy(mhandle, max_iterations=12, max_iter_sd=20,
     # ignore LogInfo in stereochemical problems if output up to info done
     ignore_stereo_log = (ost.GetVerbosityLevel() == 3)
     # setup mm simulation
-    settings = mm.Settings()
-    settings.integrator = mm.LangevinIntegrator(310, 1, 0.002)
-    settings.init_temperature = 0
-    settings.forcefield = mm.LoadCHARMMForcefield()
-    settings.nonbonded_method = mm.NonbondedMethod.CutoffNonPeriodic
-    settings.keep_ff_specific_naming = False
-    # use fast CPU platform by default
-    settings.platform = mm.Platform.CPU
-    if os.getenv('PM3_OPENMM_CPU_THREADS') is None:
-        settings.cpu_properties["CpuThreads"] = "1"
-    else:
-        settings.cpu_properties["CpuThreads"] = os.getenv('PM3_OPENMM_CPU_THREADS')
-    try:
-        sim = mm.Simulation(mhandle.model, settings)
-    except RuntimeError as ex:
-        ost.LogWarning("OpenMM failed to initialize with error: %s" % str(ex))
-        # switch to "mm.Platform.Reference" as fallback
-        settings.platform = mm.Platform.Reference
-        sim = mm.Simulation(mhandle.model, settings)
-        ost.LogWarning("Switched to slower reference platform of OpenMM!")
+    sim = _SetupMmSimulation(mhandle)
+    
     # settings to check for stereochemical problems
     clashing_distances = mol.alg.DefaultClashingDistances()
     bond_stereo_chemical_param = mol.alg.DefaultBondStereoChemicalParams()
@@ -148,7 +180,8 @@ def MinimizeModelEnergy(mhandle, max_iterations=12, max_iter_sd=20,
 
     for i in range(max_iterations):
         # update atoms
-        ost.LogInfo("Perform energy minimization (iteration %d)" % (i+1))
+        ost.LogInfo("Perform energy minimization "
+                    "(iteration %d, energy: %g)" % (i+1, sim.GetEnergy()))
         sim.ApplySD(tolerance = 1.0, max_iterations = max_iter_sd)
         sim.ApplyLBFGS(tolerance = 1.0, max_iterations = max_iter_lbfgs)
         sim.UpdatePositions()
@@ -171,10 +204,9 @@ def MinimizeModelEnergy(mhandle, max_iterations=12, max_iter_sd=20,
         # checks above would remove bad atoms
         if len(temp_ent_stereo_checked.Select("ele!=H").atoms) \
            == len(temp_ent.Select("ele!=H").atoms):
+            ost.LogInfo("No more stereo-chemical problems "
+                        "-> final energy: %g" % (sim.GetEnergy()))
             break
-        else:
-            ost.LogInfo("Stereo-chemical problems found, need more energy"
-                        + " minimization (iteration %d)" % (i+1))
 
     # update model
     simulation_ent = sim.GetEntity()
diff --git a/modelling/src/model.cc b/modelling/src/model.cc
index a90685cc..d53e4d2a 100644
--- a/modelling/src/model.cc
+++ b/modelling/src/model.cc
@@ -431,17 +431,21 @@ ModellingHandle BuildRawModel(const ost::seq::AlignmentList& aln,
 void AddLigands(const ost::seq::AlignmentList& aln, XCSEditor& edi,
                 EntityHandle& model)
 {
-
+  // make sure we handle duplicates
+  std::set<mol::EntityHandle> unique_handles;
   std::vector<mol::ResidueViewList> ligand_views;
   for(ost::seq::AlignmentList::const_iterator it=aln.begin();
       it!=aln.end();
       ++it) {
     mol::EntityHandle handle = it->GetSequence(1).GetAttachedView().GetHandle();
-    mol::EntityView ligand_view = handle.Select("ligand=true");
+    // did we already do that?
+    if (unique_handles.find(handle) != unique_handles.end()) continue;
+    mol::EntityView ligand_view = handle.Select("ligand=true or cname='_'");
     mol::ResidueViewList ligands = ligand_view.GetResidueList();
     if(!ligands.empty()) {
       ligand_views.push_back(ligands);
     }
+    unique_handles.insert(handle);
   }
 
   if (ligand_views.size()>0) {
diff --git a/sidechain/pymod/_reconstruct_sidechains.py b/sidechain/pymod/_reconstruct_sidechains.py
index 9b147775..c385aac2 100644
--- a/sidechain/pymod/_reconstruct_sidechains.py
+++ b/sidechain/pymod/_reconstruct_sidechains.py
@@ -23,7 +23,8 @@ def Reconstruct(ent, keep_sidechains = False, build_disulfids = True,
         bbdep = True
     residues_with_rotamer_group = list()
     rotamer_groups = list()
-    prot = ent.Select("peptide=true")
+    # ignore ligand chain and any non-peptides
+    prot = ent.Select("peptide=true and cname!='_'")
     incomplete_sidechains = list()
 
     #extract rotamer ids
-- 
GitLab