MayaChemTools

    1 #!/bin/env python
    2 # File: TorsionStrainEnergyAlerts.py
    3 # Author: Manish Sud <msud@san.rr.com>
    4 #
    5 # Collaborator: Pat Walters
    6 #
    7 # Copyright (C) 2024 Manish Sud. All rights reserved.
    8 #
    9 # This module uses the torsion strain energy library developed by Gu, S.;
   10 # Smith, M. S.; Yang, Y.; Irwin, J. J.; Shoichet, B. K. [ Ref 153 ].
   11 #
   12 # The torsion strain enegy library is based on the Torsion Library jointly
   13 # developed by the University of Hamburg, Center for Bioinformatics,
   14 # Hamburg, Germany and F. Hoffmann-La-Roche Ltd., Basel, Switzerland.
   15 #
   16 # This file is part of MayaChemTools.
   17 #
   18 # MayaChemTools is free software; you can redistribute it and/or modify it under
   19 # the terms of the GNU Lesser General Public License as published by the Free
   20 # Software Foundation; either version 3 of the License, or (at your option) any
   21 # later version.
   22 #
   23 # MayaChemTools is distributed in the hope that it will be useful, but without
   24 # any warranty; without even the implied warranty of merchantability of fitness
   25 # for a particular purpose.  See the GNU Lesser General Public License for more
   26 # details.
   27 #
   28 # You should have received a copy of the GNU Lesser General Public License
   29 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
   30 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
   31 # Boston, MA, 02111-1307, USA.
   32 #
   33 
   34 import os
   35 import re
   36 import math
   37 
   38 from rdkit import Chem
   39 from rdkit.Chem import rdMolTransforms
   40 
   41 from . import TorsionAlertsUtil
   42 
   43 class TorsionStrainEnergyAlerts():
   44     def __init__(self, AlertsMode = "TotalEnergy", TotalEnergyCutoff = 6.0, MaxSingleEnergyCutoff = 1.8, RotBondsSMARTSMode = "SemiStrict", RotBondsSMARTSPattern = None, TorsionLibraryFilePath = "auto", AlertTorsionsNotObserved = False):
   45         """Identify strained molecules based on torsion strain energy library [ Ref 153 ]
   46         alerts by matching rotatable bonds against SMARTS patterns specified for
   47         torsion rules in a torsion energy library file. The molecules must have 3D coordinates.
   48         The default torsion strain energy library file, TorsionStrainEnergyLibrary.xml, is
   49         ia available in the directory containing this file.
   50         
   51         The data in torsion strain energy library file is organized in a hierarchical
   52         manner. It consists of one generic class and six specific classes at the highest
   53         level. Each class contains multiple subclasses corresponding to named functional
   54         groups or substructure patterns. The subclasses consist of torsion rules sorted
   55         from specific to generic torsion patterns. The torsion rule, in turn, contains a
   56         list of peak values for torsion angles and two tolerance values. A pair of tolerance
   57         values define torsion bins around a torsion peak value.
   58         
   59         A strain energy calculation method, 'exact' or 'approximate' [ Ref 153 ], is 
   60         associated with each torsion rule for calculating torsion strain energy. The 'exact'
   61         stain energy calculation relies on the energy bins available under the energy histogram
   62         consisting of 36 bins covering angles from -180 to 180. The width of each bin is 10
   63         degree. The energy bins are are defined at the right end points. The first and the
   64         last energy bins correspond to -170 and 180 respectively. The torsion angle is mapped
   65         to a energy bin. An angle offset is calculated for the torsion angle from the the right
   66         end point angle of the bin. The strain energy is estimated for the angle offset based
   67         on the energy difference between the current and previous bins. The torsion strain
   68         energy, in terms of torsion energy units (TEUs), corresponds to the sum of bin strain
   69         energy and the angle offset strain energy.
   70             
   71             Energy = BinEnergyDiff/10.0 * BinAngleOffset + BinEnergy[BinNum]
   72             
   73             Where:
   74             
   75             BinEnergyDiff = BinEnergy[BinNum] - BinEnergy[PreviousBinNum]
   76             BinAngleOffset = TorsionAngle - BinAngleRightSide
   77             
   78         The 'approximate' strain energy calculation relies on the angle difference between a
   79         torsion angle and the torsion peaks observed for the torsion rules in the torsion
   80         energy library. The torsion angle is matched to a torsion peak based on the value of
   81         torsion angle difference. It must be less than or equal to the value for the second
   82         tolerance 'tolerance2'. Otherwise, the torsion angle is not observed in the torsion
   83         energy library and a value of 'NA' is assigned for torsion energy along with the lower
   84         and upper bounds on energy at 95% confidence interval. The 'approximate' torsion
   85         energy (TEUs) for observed torsion angle is calculated using the following formula:
   86             
   87             Energy = beta_1 * (AngleDiff ** 2) + beta_2 * (AngleDiff ** 4)
   88             
   89         The coefficients 'beta_1' and 'beta_2' are available for the observed angles in 
   90         the torsion strain energy library. The 'AngleDiff' is the difference between the
   91         torsion angle and the matched torsion peak.
   92         
   93         For example:
   94              
   95             <library>
   96                 <hierarchyClass id1="G" id2="G" name="GG">
   97                 ...
   98                 </hierarchyClass>
   99                 <hierarchyClass id1="C" id2="O" name="CO">
  100                     <hierarchySubClass name="Ester bond I" smarts="O=[C:2][O:3]">
  101                         <torsionRule method="exact" smarts=
  102                             "[O:1]=[C:2]!@[O:3]~[CH0:4]">
  103                             <angleList>
  104                                 <angle score="56.52" tolerance1="20.00"
  105                                 tolerance2="25.00" value="0.0"/>
  106                             </angleList>
  107                             <histogram>
  108                                 <bin count="1"/>
  109                                 ...
  110                             </histogram>
  111                             <histogram_shifted>
  112                                 <bin count="0"/>
  113                                 ...
  114                             </histogram_shifted>
  115                             <histogram_converted>
  116                                 <bin energy="4.67... lower="2.14..." upper="Inf"/>
  117                                 ...
  118                                 <bin energy="1.86..." lower="1.58..." upper="2.40..."/>
  119                                 ...
  120                                </histogram_converted>
  121                         </torsionRule>
  122                         <torsionRule method="approximate" smarts=
  123                             "[cH0:1][c:2]([cH0])!@[O:3][p:4]">
  124                             <angleList>
  125                             <angle beta_1="0.002..." beta_2="-7.843...e-07"
  126                                 score="27.14" theta_0="-90.0" tolerance1="30.00"
  127                                 tolerance2="45.00" value="-90.0"/>
  128                             ...
  129                             </angleList>
  130                             <histogram>
  131                                 <bin count="0"/>
  132                                  ...
  133                             </histogram>
  134                             <histogram_shifted>
  135                                 <bin count="0"/>
  136                                 ...
  137                             </histogram_shifted>
  138                         </torsionRule>
  139                     ...
  140                  ...
  141                 </hierarchyClass>
  142                  <hierarchyClass id1="N" id2="C" name="NC">
  143                  ...
  144                 </hierarchyClass>
  145                 <hierarchyClass id1="S" id2="N" name="SN">
  146                 ...
  147                 </hierarchyClass>
  148                 <hierarchyClass id1="C" id2="S" name="CS">
  149                 ...
  150                 </hierarchyClass>
  151                 <hierarchyClass id1="C" id2="C" name="CC">
  152                 ...
  153                 </hierarchyClass>
  154                 <hierarchyClass id1="S" id2="S" name="SS">
  155                  ...
  156                 </hierarchyClass>
  157             </library>
  158             
  159         The rotatable bonds in a 3D molecule are identified using a default SMARTS pattern.
  160         A custom SMARTS pattern may be optionally specified to detect rotatable bonds.
  161         Each rotatable bond is matched to a torsion rule in the torsion strain energy library.
  162         The strain energy is calculated for each rotatable bond using the calculation
  163         method, 'exact' or 'approximate', associated with the matched torsion rule.
  164         
  165         The total strain energy (TEUs) of a molecule corresponds to the sum of  'exact' and
  166         'approximate' strain energies calculated for all matched rotatable bonds in the
  167         molecule. The total strain energy is set to 'NA' for molecules containing a 'approximate'
  168         energy estimate for a torsion angle not observed in the torsion energy library. In
  169         addition, the lower and upper bounds on energy at 95% confidence interval are
  170         set to 'NA'.
  171 
  172         Arguments:
  173             AlertsMode (str): Torsion strain energy library alert types to use
  174                 for issuing alerts. Possible values: TotalEnergy,
  175                 MaxSingleEnergy, or TotalOrMaxSingleEnergy.
  176             TotalEnergyCutoff (float): Total strain strain energy (TEUs) cutoff.
  177             MaxSingleEnergyCutoff (float): Maximum single strain energy (TEUs)
  178                 cutoff.
  179             RotBondsSMARTSMode (str): SMARTS pattern to use for identifying
  180                 rotatable bonds in a molecule. Possible values: NonStrict,
  181                 SemiStrict, Strict or Specify.
  182             RotBondsSMARTSPattern (str):SMARTS pattern for identifying rotatable
  183                 bonds. This paramater is only valid for 'Specify' value
  184                 'RotBondsSMARTSMode'.
  185             TorsionLibraryFilePath (str): A XML file name containing data for
  186                 torsion starin energy library.
  187             AlertTorsionsNotObserved (bool): Issue alerts about molecules
  188                 containing torsion angles not observed in torsion strain energy
  189                 library.
  190 
  191         Returns:
  192             object: An instantiated class object.
  193 
  194         Examples:
  195             
  196             StrainEnergyAlertsHandle = TorsionStrainEnergyAlerts()
  197 
  198             StrainEnergyAlertsHandle = TorsionStrainEnergyAlerts(AlertsMode = "TotalEnergy",
  199                 TotalEnergyCutoff = 6.0, MaxSingleEnergyCutoff = 1.8, RotBondsSMARTSMode = "SemiStrict",
  200                 RotBondsSMARTSPattern = None, TorsionLibraryFilePath = "auto", AlertTorsionsNotObserved = False)
  201 
  202         Notes:
  203             The following sections provide additional details for the parameters.
  204             
  205             AlertsMode: Torsion strain energy library alert types to use for issuing 
  206             alerts about molecules containing rotatable bonds based on the calculated
  207             values for the total torsion strain energy of a molecule and  the maximum
  208             single strain energy of a rotatable bond in a molecule.
  209             
  210             Possible values: TotalEnergy, MaxSingleEnergy, or TotalOrMaxSingleEnergy
  211             
  212             The strain energy cutoff values in terms of torsion energy units (TEUs) are
  213             used to filter molecules as shown below:
  214                 
  215                 AlertsMode                AlertsEnergyCutoffs (TEUs)
  216                 
  217                 TotalEnergy               >= TotalEnergyCutoff
  218                 
  219                 MaxSingleEnergy           >= MaxSingleEnergyCutoff
  220                 
  221                 TotalOrMaxSingleEnergy    >= TotalEnergyCutoff
  222                                           or >= MaxSingleEnergyCutoff
  223             
  224             TotalEnergyCutoff: Total strain strain energy (TEUs) cutoff [ Ref 153 ] for
  225             issuing alerts based on total strain energy for all rotatable bonds in a
  226             molecule. This option is used during 'TotalEnergy' or 'TotalOrMaxSingleEnergy'
  227             values of 'AlertsMode' parameter.
  228             
  229             The total strain energy must be greater than or equal to the specified
  230             cutoff value to identify a molecule containing strained torsions.
  231             
  232             MaxSingleEnergyCutoff: Maximum single strain energy (TEUs) cutoff [ Ref 153 ]
  233             for issuing alerts based on the maximum value of a single strain energy of a
  234             rotatable bond in  a molecule. This option is used during 'MaxSingleEnergy' or
  235             'TotalOrMaxSingleEnergy' values of 'AlertsMode' parameter.
  236             
  237             The maximum single strain energy must be greater than or equal to the
  238             specified cutoff valueo to identify a molecule containing strained torsions.
  239             
  240             RotBondsSMARTSMode: SMARTS pattern to use for identifying rotatable bonds in
  241             a molecule for matching against torsion rules in the torsion library. Possible values:
  242             NonStrict, SemiStrict, Strict or Specify. The rotatable bond SMARTS matches
  243             are filtered to ensure that each atom in the rotatable bond is attached to
  244             at least two heavy atoms.
  245             
  246             The following SMARTS patterns are used to identify rotatable bonds for
  247             different modes:
  248                 
  249                 NonStrict: [!$(*#*)&!D1]-&!@[!$(*#*)&!D1]
  250                 
  251                 SemiStrict:
  252                 [!$(*#*)&!D1&!$(C(F)(F)F)&!$(C(Cl)(Cl)Cl)&!$(C(Br)(Br)Br)
  253                 &!$(C([CH3])([CH3])[CH3])]-!@[!$(*#*)&!D1&!$(C(F)(F)F)
  254                 &!$(C(Cl)(Cl)Cl)&!$(C(Br)(Br)Br)&!$(C([CH3])([CH3])[CH3])]
  255                 
  256                 Strict:
  257                 [!$(*#*)&!D1&!$(C(F)(F)F)&!$(C(Cl)(Cl)Cl)&!$(C(Br)(Br)Br)
  258                 &!$(C([CH3])([CH3])[CH3])&!$([CD3](=[N,O,S])-!@[#7,O,S!D1])
  259                 &!$([#7,O,S!D1]-!@[CD3]=[N,O,S])&!$([CD3](=[N+])-!@[#7!D1])
  260                 &!$([#7!D1]-!@[CD3]=[N+])]-!@[!$(*#*)&!D1&!$(C(F)(F)F)
  261                 &!$(C(Cl)(Cl)Cl)&!$(C(Br)(Br)Br)&!$(C([CH3])([CH3])[CH3])]
  262                 
  263             The 'NonStrict' and 'Strict' SMARTS patterns are available in RDKit. The 
  264             'NonStrict' SMARTS pattern corresponds to original Daylight SMARTS
  265             specification for rotatable bonds. The 'SemiStrict' SMARTS pattern is 
  266             derived from 'Strict' SMARTS pattern.
  267             
  268             TorsionLibraryFilePath (str): Specify a XML file name containing data for
  269             torsion starin energy library hierarchy or use default file,
  270             TorsionEnergyLibrary.xml, available in the directory containing this file.
  271         
  272             AlertTorsionsNotObserved: Issue alerts abpout molecules con ntaining torsion
  273             angles not observed in torsion strain energy library. It's not possible to
  274             calculate torsion strain energies for these torsions during 'approximate'
  275             match to a specified torsion in the library.
  276             
  277             The 'approximate' strain energy calculation relies on the angle difference
  278             between a torsion angle and the torsion peaks observed for the torsion
  279             rules in the torsion energy library. The torsion angle is matched to a
  280             torsion peak based on the value of torsion angle difference. It must be
  281             less than or equal to the value for the second tolerance 'tolerance2'.
  282             Otherwise, the torsion angle is not observed in the torsion energy library
  283             and a value of 'NA' is assigned for torsion energy along with the lower and
  284             upper bounds on energy at 95% confidence interval.
  285 
  286         """
  287 
  288         self._ProcessTorsionStrainEnergyAlertsParameters(AlertsMode, TotalEnergyCutoff, MaxSingleEnergyCutoff, RotBondsSMARTSMode, RotBondsSMARTSPattern, TorsionLibraryFilePath, AlertTorsionsNotObserved)
  289 
  290         self._InitializeTorsionStrainEnergyAlerts()
  291 
  292 
  293     def _ProcessTorsionStrainEnergyAlertsParameters(self, AlertsMode, TotalEnergyCutoff, MaxSingleEnergyCutoff, RotBondsSMARTSMode, RotBondsSMARTSPattern, TorsionLibraryFilePath, AlertTorsionsNotObserved):
  294         """Process torsion strain energy alerts paramaters. """
  295 
  296         # Process AltertsMode paramater...
  297         self._ProcessAlertsModeParameter(AlertsMode)
  298 
  299         # Process TotalEnergyCutoff parameter...
  300         if TotalEnergyCutoff <= 0.0:
  301             raise ValueError("The value, %s, specified for TotalEnergyCutoff parameter is not valid. Supported value: > 0.0" % TotalEnergyCutoff)
  302         self.TotalEnergyCutoff = TotalEnergyCutoff
  303 
  304         #Process  MaxSingleEnergyCutoff parameter...
  305         if MaxSingleEnergyCutoff <= 0.0:
  306             raise ValueError("The value, %s, specified for MaxSingleEnergyCutoff parameter is not valid. Supported value: > 0.0" % MaxSingleEnergyCutoff)
  307         self.MaxSingleEnergyCutoff = MaxSingleEnergyCutoff
  308 
  309         # Process RotBondsSMARTSMode and RotBondsSMARTSPattern parameters...
  310         self._ProcessRotBondsParameters(RotBondsSMARTSMode, RotBondsSMARTSPattern)
  311 
  312         # Process TorsionLibraryFilePath parameter...
  313         self._ProcessTorsionLibraryFilePathParameter(TorsionLibraryFilePath)
  314 
  315         # AlertTorsionsNotObserved parameter...
  316         self.AlertTorsionsNotObserved = AlertTorsionsNotObserved
  317 
  318 
  319     def _InitializeTorsionStrainEnergyAlerts(self):
  320         """Initialize tosion strain energy alerts."""
  321 
  322         # Retrieve and setup torsion library info for matching rotatable bonds...
  323         TorsionLibraryInfo = {}
  324 
  325         TorsionLibElementTree = TorsionAlertsUtil.RetrieveTorsionLibraryInfo(self.TorsionLibraryFilePath)
  326         TorsionLibraryInfo["TorsionLibElementTree"] = TorsionLibElementTree
  327 
  328         TorsionAlertsUtil.SetupTorsionLibraryInfoForMatchingRotatableBonds(TorsionLibraryInfo)
  329 
  330         self.TorsionLibraryInfo = TorsionLibraryInfo
  331 
  332 
  333     def _ProcessAlertsModeParameter(self, AlertsMode):
  334         """Process AlertsMode parameter. """
  335 
  336         TotalEnergyMode, MaxSingleEnergyMode, TotalOrMaxSingleEnergyMode = [False] * 3
  337         if re.match("^TotalEnergy$", AlertsMode, re.I):
  338             TotalEnergyMode = True
  339         elif re.match("^MaxSingleEnergy$", AlertsMode, re.I):
  340             MaxSingleEnergyMode = True
  341         elif re.match("^TotalOrMaxSingleEnergy$", AlertsMode, re.I):
  342             TotalOrMaxSingleEnergyMode = True
  343         else:
  344             raise ValueError("Invalid value, %s, specified for AlertsMode parameter. Valid values: TotalEnergy, MaxSingleEnergy, or TotalOrMaxSingleEnergy" % (AlertsMode))
  345 
  346         self.AltersMode = AlertsMode
  347         self.TotalEnergyMode = TotalEnergyMode
  348         self.MaxSingleEnergyMode = MaxSingleEnergyMode
  349         self.TotalOrMaxSingleEnergyMode = TotalOrMaxSingleEnergyMode
  350 
  351 
  352     def _ProcessRotBondsParameters(self, RotBondsSMARTSMode, RotBondsSMARTSPattern):
  353         """Process  RotBondsSMARTSMode and RotBondsSMARTSPattern parameters. """
  354 
  355         if re.match("^NonStrict$", RotBondsSMARTSMode, re.I):
  356             RotBondsSMARTSPattern = "[!$(*#*)&!D1]-&!@[!$(*#*)&!D1]"
  357         elif re.match("^SemiStrict$", RotBondsSMARTSMode, re.I):
  358             RotBondsSMARTSPattern = "[!$(*#*)&!D1&!$(C(F)(F)F)&!$(C(Cl)(Cl)Cl)&!$(C(Br)(Br)Br)&!$(C([CH3])([CH3])[CH3])]-!@[!$(*#*)&!D1&!$(C(F)(F)F)&!$(C(Cl)(Cl)Cl)&!$(C(Br)(Br)Br)&!$(C([CH3])([CH3])[CH3])]"
  359         elif re.match("^Strict$", RotBondsSMARTSMode, re.I):
  360             RotBondsSMARTSPattern = "[!$(*#*)&!D1&!$(C(F)(F)F)&!$(C(Cl)(Cl)Cl)&!$(C(Br)(Br)Br)&!$(C([CH3])([CH3])[CH3])&!$([CD3](=[N,O,S])-!@[#7,O,S!D1])&!$([#7,O,S!D1]-!@[CD3]=[N,O,S])&!$([CD3](=[N+])-!@[#7!D1])&!$([#7!D1]-!@[CD3]=[N+])]-!@[!$(*#*)&!D1&!$(C(F)(F)F)&!$(C(Cl)(Cl)Cl)&!$(C(Br)(Br)Br)&!$(C([CH3])([CH3])[CH3])]"
  361         elif re.match("^Specify$", RotBondsSMARTSMode, re.I):
  362             if RotBondsSMARTSPattern is None:
  363                 raise ValueError("The RotBondsSMARTSPattern parameter value must be specified during Specify value for RotBondsSMARTSMode parameter.")
  364             RotBondsSMARTSPattern = RotBondsSMARTSPattern.strip()
  365             if not len(RotBondsSMARTSPattern):
  366                 raise ValueError("Empty value specified for  RotBondsSMARTSPattern parameter.")
  367         else:
  368             raise ValueError("Invalid value, %s, specified for RotBondsSMARTSMode parameter. Valid values: NonStrict, SemiStrict, Strict or Specify" % (RotBondsSMARTSMode))
  369 
  370         RotBondsPatternMol = Chem.MolFromSmarts(RotBondsSMARTSPattern)
  371         if RotBondsPatternMol is None:
  372             if re.match("Specify", RotBondsSMARTSMode, re.I):
  373                 raise ValueError("Failed to create rotatable bonds pattern molecule. The rotatable bonds SMARTS pattern, \"%s\", specified using RotBondsSMARTSPattern parameter is not valid." % (RotBondsSMARTSPattern))
  374             else:
  375                 raise ValueError("Failed to create rotatable bonds pattern molecule. The default rotatable bonds SMARTS pattern, \"%s\", used for %s value of RotBondsSMARTSMode parameter is not valid." % (RotBondsSMARTSPattern, RotBondsSMARTSMode))
  376 
  377         self.RotBondsSMARTSMode = RotBondsSMARTSMode
  378         self.RotBondsSMARTSPattern = RotBondsSMARTSPattern
  379         self.RotBondsPatternMol = RotBondsPatternMol
  380 
  381 
  382     def _ProcessTorsionLibraryFilePathParameter(self, TorsionLibraryFilePath):
  383         """ Process torsion library path."""
  384 
  385         # TorsionLibraryFilePath parameter...
  386         if re.match("^auto$", TorsionLibraryFilePath):
  387             TorsionLibraryFile = "TorsionStrainEnergyLibrary.xml"
  388             TorsionLibraryFilePath = os.path.join(os.path.dirname(os.path.abspath(__file__)), TorsionLibraryFile)
  389             if not os.path.isfile(TorsionLibraryFilePath):
  390                 raise ValueError("The default torsion alerts library file %s doesn't exist." % TorsionLibraryFilePath)
  391         else:
  392             if not os.path.isfile(TorsionLibraryFilePath):
  393                 raise ValueError("The file specified, %s, for parameter TorsionLibraryFilePath doesn't exist." % TorsionLibraryFilePath)
  394             TorsionLibraryFilePath = os.path.abspath(TorsionLibraryFilePath)
  395 
  396         self.TorsionLibraryFilePath = TorsionLibraryFilePath
  397 
  398 
  399     def GetTorsionLibraryFilePath(self):
  400         """Get torsion strain library file path.
  401 
  402         Arguments:
  403             None
  404 
  405         Returns:
  406             FilePath (str): Torsion strain library path.
  407 
  408         """
  409 
  410         return self.TorsionLibraryFilePath
  411 
  412 
  413     def ListTorsionLibraryInfo(self):
  414         """List torsion strain library information.
  415 
  416         Arguments:
  417             None
  418 
  419         Returns:
  420             None
  421 
  422         """
  423 
  424         return TorsionAlertsUtil.ListTorsionLibraryInfo(self.TorsionLibraryInfo["TorsionLibElementTree"])
  425 
  426 
  427     def IdentifyTorsionLibraryAlertsForRotatableBonds(self, Mol):
  428         """Identify torsion strain library alerts for a molecule by matching rotatable
  429         bonds against SMARTS patterns specified for torsion rules in torsion energy
  430         library file.
  431  
  432         Arguments:
  433             Mol (object): RDKit molecule object.
  434 
  435         Returns:
  436             bool: True - Molecule contains strained torsions; False - Molecule
  437                 contains no strained torsions or rotatable bonds. 
  438             dict or None: Torsion alerts information regarding matching of
  439                 rotatable bonds to torsion strain library.
  440 
  441         Examples:
  442 
  443             from TorsionAlerts.TorsionStrainEnergyAlerts import
  444                 TorsionStrainEnergyAlerts
  445             
  446             StrainEnergyAlerts = TorsionStrainEnergyAlerts()
  447             AlertsStatus, AlertsInfo = StrainEnergyAlerts.
  448                 IdentifyTorsionLibraryAlertsForRotatableBonds(RDKitMol)
  449             
  450             RotBondsAlertsStatus = AlertsInfo["RotBondsAlertsStatus"]
  451             TotalEnergy = AlertsInfo["TotalEnergy"]
  452             TotalEnergyLowerBound = AlertsInfo["TotalEnergyLowerBound"]
  453             TotalEnergyUpperBound = AlertsInfo["TotalEnergyUpperBound"]
  454             AnglesNotObservedCount = AlertsInfo["AnglesNotObservedCount"]
  455             MaxSingleEnergy = AlertsInfo["MaxSingleEnergy"])
  456             MaxSingleEnergyAlertsCount = AlertsInfo[
  457                 "MaxSingleEnergyAlertsCount"]
  458             
  459             # List of rotatable bond IDs...
  460             RotatableBondIDs = AlertsInfo["IDs"]
  461             
  462             # Dictionaries containing information for rotatable bonds by using
  463             # bond ID as key...
  464             for ID in AlertsInfo["IDs"]:
  465                 MatchStatus = AlertsInfo["MatchStatus"][ID]
  466                 MaxSingleEnergyAlertStatus = AlertsInfo[
  467                     "MaxSingleEnergyAlertStatus"][ID]
  468                 AtomIndices = AlertsInfo["AtomIndices"][ID]
  469                 TorsionAtomIndices = AlertsInfo["TorsionAtomIndices"][ID]
  470                 TorsionAngle = AlertsInfo["TorsionAngle"][ID]
  471                 HierarchyClassName = AlertsInfo["HierarchyClassName"][ID]
  472                 HierarchySubClassName = AlertsInfo["HierarchySubClassName"][ID]
  473                 TorsionRuleNodeID = AlertsInfo["TorsionRuleNodeID"][ID]
  474                 TorsionRuleSMARTS = AlertsInfo["TorsionRuleSMARTS"][ID]
  475                 EnergyMethod = AlertsInfo["EnergyMethod"][ID]
  476                 AngleNotObserved = AlertsInfo["AngleNotObserved"][ID]
  477                 Energy = AlertsInfo["Energy"][ID]
  478                 EnergyLowerBound = AlertsInfo["EnergyLowerBound"][ID]
  479                 EnergyUpperBound = AlertsInfo["EnergyUpperBound"][ID]
  480 
  481         """
  482 
  483         # Identify rotatable bonds...
  484         RotBondsStatus, RotBondsInfo = TorsionAlertsUtil.IdentifyRotatableBondsForTorsionLibraryMatch(self.TorsionLibraryInfo, Mol, self.RotBondsPatternMol)
  485 
  486         if not RotBondsStatus:
  487             return (False, None)
  488 
  489         # Identify alerts for rotatable bonds...
  490         RotBondsAlertsStatus, RotBondsAlertsInfo = self._MatchRotatableBondsToTorsionLibrary(Mol, RotBondsInfo)
  491 
  492         return (RotBondsAlertsStatus, RotBondsAlertsInfo)
  493 
  494 
  495     def _MatchRotatableBondsToTorsionLibrary(self, Mol, RotBondsInfo):
  496         """Match rotatable bond to torsion library."""
  497 
  498         # Initialize...
  499         RotBondsAlertsInfo = self._InitializeRotatableBondsAlertsInfo()
  500 
  501         # Match rotatable bonds to torsion library...
  502         for ID in RotBondsInfo["IDs"]:
  503             AtomIndices = RotBondsInfo["AtomIndices"][ID]
  504             HierarchyClass = RotBondsInfo["HierarchyClass"][ID]
  505 
  506             MatchStatus, MatchInfo = self._MatchRotatableBondToTorsionLibrary(Mol, AtomIndices, HierarchyClass)
  507             self._TrackRotatableBondsAlertsInfo(RotBondsAlertsInfo, ID, AtomIndices, MatchStatus, MatchInfo)
  508 
  509         RotBondsAlertsStatus = self._SetupRotatableBondsAlertStatusTotalStrainEnergiesInfo(RotBondsAlertsInfo)
  510 
  511         return (RotBondsAlertsStatus, RotBondsAlertsInfo)
  512 
  513 
  514     def _InitializeRotatableBondsAlertsInfo(self):
  515         """Initialize alerts information for rotatable bonds."""
  516 
  517         RotBondsAlertsInfo = {}
  518         RotBondsAlertsInfo["IDs"] = []
  519 
  520         for DataLabel in ["RotBondsAlertsStatus", "TotalEnergy", "TotalEnergyLowerBound", "TotalEnergyUpperBound", "AnglesNotObservedCount", "MaxSingleEnergy", "MaxSingleEnergyAlertsCount"]:
  521             RotBondsAlertsInfo[DataLabel] = None
  522 
  523         for DataLabel in ["MatchStatus", "MaxSingleEnergyAlertStatus", "AtomIndices", "TorsionAtomIndices", "TorsionAngle", "HierarchyClassName", "HierarchySubClassName", "TorsionRuleNodeID", "TorsionRuleSMARTS", "EnergyMethod", "AngleNotObserved", "Energy", "EnergyLowerBound", "EnergyUpperBound"]:
  524             RotBondsAlertsInfo[DataLabel] = {}
  525 
  526         return RotBondsAlertsInfo
  527 
  528 
  529     def _TrackRotatableBondsAlertsInfo(self, RotBondsAlertsInfo, ID, AtomIndices, MatchStatus, MatchInfo):
  530         """Track alerts information for rotatable bonds."""
  531 
  532         if MatchInfo is None or len(MatchInfo) == 0:
  533             TorsionAtomIndices, TorsionAngle, HierarchyClassName, HierarchySubClassName, TorsionRuleNodeID, TorsionRuleSMARTS, EnergyMethod, AngleNotObserved, Energy, EnergyLowerBound, EnergyUpperBound = [None] * 11
  534         else:
  535             TorsionAtomIndices, TorsionAngle, HierarchyClassName, HierarchySubClassName, TorsionRuleNodeID, TorsionRuleSMARTS, EnergyMethod, AngleNotObserved, Energy, EnergyLowerBound, EnergyUpperBound = MatchInfo
  536 
  537         # Track torsion match information...
  538         RotBondsAlertsInfo["IDs"].append(ID)
  539         RotBondsAlertsInfo["MatchStatus"][ID] = MatchStatus
  540         RotBondsAlertsInfo["AtomIndices"][ID] = AtomIndices
  541         RotBondsAlertsInfo["TorsionAtomIndices"][ID] = TorsionAtomIndices
  542         RotBondsAlertsInfo["TorsionAngle"][ID] = TorsionAngle
  543         RotBondsAlertsInfo["HierarchyClassName"][ID] = HierarchyClassName
  544         RotBondsAlertsInfo["HierarchySubClassName"][ID] = HierarchySubClassName
  545         RotBondsAlertsInfo["TorsionRuleNodeID"][ID] = TorsionRuleNodeID
  546         RotBondsAlertsInfo["TorsionRuleSMARTS"][ID] = TorsionRuleSMARTS
  547         RotBondsAlertsInfo["EnergyMethod"][ID] = EnergyMethod
  548         RotBondsAlertsInfo["AngleNotObserved"][ID] = AngleNotObserved
  549         RotBondsAlertsInfo["Energy"][ID] = Energy
  550         RotBondsAlertsInfo["EnergyLowerBound"][ID] = EnergyLowerBound
  551         RotBondsAlertsInfo["EnergyUpperBound"][ID] = EnergyUpperBound
  552 
  553 
  554     def _SetupRotatableBondsAlertStatusTotalStrainEnergiesInfo(self, RotBondsAlertsInfo):
  555         """Setup rotatable bonds alert status along with total strain energies."""
  556 
  557         # Initialize...
  558         RotBondsAlertsStatus = False
  559         TotalEnergy, TotalEnergyLowerBound, TotalEnergyUpperBound, AnglesNotObservedCount  = [None, None, None, None]
  560         MaxSingleEnergy, MaxSingleEnergyAlertsCount  = [None, None]
  561 
  562         # Initialize max single energy alert status...
  563         for ID in RotBondsAlertsInfo["IDs"]:
  564             RotBondsAlertsInfo["MaxSingleEnergyAlertStatus"][ID] = None
  565 
  566         # Check for torsion angles not obervered in the strain library...
  567         AnglesNotObservedCount = 0
  568         for ID in RotBondsAlertsInfo["IDs"]:
  569             AngleNotObserved = RotBondsAlertsInfo["AngleNotObserved"][ID]
  570             if AngleNotObserved is not None and AngleNotObserved:
  571                 AnglesNotObservedCount += 1
  572 
  573         # Setup alert status for rotable bonds...
  574         if AnglesNotObservedCount > 0:
  575             if OptionsInfo["FilterTorsionsNotObserved"]:
  576                 RotBondsAlertsStatus = True
  577         else:
  578             TotalEnergy = 0.0
  579             for ID in RotBondsAlertsInfo["IDs"]:
  580                 Energy = RotBondsAlertsInfo["Energy"][ID]
  581                 TotalEnergy += Energy
  582                 if self.TotalEnergyMode:
  583                     if TotalEnergy > self.TotalEnergyCutoff:
  584                         RotBondsAlertsStatus = True
  585                         break
  586                 elif self.MaxSingleEnergyMode:
  587                     if Energy > self.MaxSingleEnergyCutoff:
  588                         RotBondsAlertsStatus = True
  589                         break
  590                 elif self.TotalOrMaxSingleEnergyMode:
  591                     if TotalEnergy > self.TotalEnergyCutoff or Energy > self.MaxSingleEnergyCutoff:
  592                         RotBondsAlertsStatus = True
  593                         break
  594 
  595             # Setup energy infomation...
  596             TotalEnergy, TotalEnergyLowerBound, TotalEnergyUpperBound = [0.0, 0.0, 0.0]
  597             if self.MaxSingleEnergyMode or self.TotalOrMaxSingleEnergyMode:
  598                 MaxSingleEnergy, MaxSingleEnergyAlertsCount = [0.0, 0]
  599 
  600             for ID in RotBondsAlertsInfo["IDs"]:
  601                 Energy = RotBondsAlertsInfo["Energy"][ID]
  602 
  603                 # Setup total energy along with the lower and upper bounds...
  604                 TotalEnergy += Energy
  605                 TotalEnergyLowerBound += RotBondsAlertsInfo["EnergyLowerBound"][ID]
  606                 TotalEnergyUpperBound += RotBondsAlertsInfo["EnergyUpperBound"][ID]
  607 
  608                 # Setup max single energy and max single energy alerts count...
  609                 if self.MaxSingleEnergyMode or self.TotalOrMaxSingleEnergyMode:
  610                     MaxSingleEnergyAlertStatus = False
  611 
  612                     if Energy > MaxSingleEnergy:
  613                         MaxSingleEnergy = Energy
  614                         if Energy > self.MaxSingleEnergyCutoff:
  615                             MaxSingleEnergyAlertStatus = True
  616                             MaxSingleEnergyAlertsCount += 1
  617 
  618                     RotBondsAlertsInfo["MaxSingleEnergyAlertStatus"][ID] = MaxSingleEnergyAlertStatus
  619 
  620         RotBondsAlertsInfo["RotBondsAlertsStatus"] = RotBondsAlertsStatus
  621 
  622         RotBondsAlertsInfo["TotalEnergy"] = TotalEnergy
  623         RotBondsAlertsInfo["TotalEnergyLowerBound"] = TotalEnergyLowerBound
  624         RotBondsAlertsInfo["TotalEnergyUpperBound"] = TotalEnergyUpperBound
  625 
  626         RotBondsAlertsInfo["AnglesNotObservedCount"] = AnglesNotObservedCount
  627 
  628         RotBondsAlertsInfo["MaxSingleEnergy"] = MaxSingleEnergy
  629         RotBondsAlertsInfo["MaxSingleEnergyAlertsCount"] = MaxSingleEnergyAlertsCount
  630 
  631         return RotBondsAlertsStatus
  632 
  633 
  634     def _MatchRotatableBondToTorsionLibrary(self, Mol, RotBondAtomIndices, RotBondHierarchyClass):
  635         """Match rotatable bond to torsion library."""
  636 
  637         if TorsionAlertsUtil.IsSpecificHierarchyClass(self.TorsionLibraryInfo, RotBondHierarchyClass):
  638             MatchStatus, MatchInfo = self._MatchRotatableBondAgainstSpecificHierarchyClass(Mol, RotBondAtomIndices, RotBondHierarchyClass)
  639             if not MatchStatus:
  640                 MatchStatus, MatchInfo = self._MatchRotatableBondAgainstGenericHierarchyClass(Mol, RotBondAtomIndices, RotBondHierarchyClass)
  641         else:
  642             MatchStatus, MatchInfo = self._MatchRotatableBondAgainstGenericHierarchyClass(Mol, RotBondAtomIndices, RotBondHierarchyClass)
  643 
  644         return (MatchStatus, MatchInfo)
  645 
  646 
  647     def _MatchRotatableBondAgainstSpecificHierarchyClass(self, Mol, RotBondAtomIndices, RotBondHierarchyClass):
  648         """Match rotatable bond against a specific hierarchy class."""
  649 
  650         TorsionLibraryInfo = self.TorsionLibraryInfo
  651 
  652         HierarchyClassElementNode = None
  653         if RotBondHierarchyClass in TorsionLibraryInfo["SpecificClasses"]["ElementNode"]:
  654             HierarchyClassElementNode = TorsionLibraryInfo["SpecificClasses"]["ElementNode"][RotBondHierarchyClass]
  655 
  656         if HierarchyClassElementNode is None:
  657             return (False, None, None, None)
  658 
  659         TorsionAlertsUtil.TrackHierarchyClassElementNode(TorsionLibraryInfo, HierarchyClassElementNode)
  660         MatchStatus, MatchInfo = self._ProcessElementForRotatableBondMatch(Mol, RotBondAtomIndices, HierarchyClassElementNode)
  661         TorsionAlertsUtil.RemoveLastHierarchyClassElementNodeFromTracking(TorsionLibraryInfo)
  662 
  663         return (MatchStatus, MatchInfo)
  664 
  665 
  666     def _MatchRotatableBondAgainstGenericHierarchyClass(self, Mol, RotBondAtomIndices, RotBondHierarchyClass):
  667         """Match rotatable bond against a generic hierarchy class."""
  668 
  669         TorsionLibraryInfo = self.TorsionLibraryInfo
  670 
  671         HierarchyClassElementNode = TorsionAlertsUtil.GetGenericHierarchyClassElementNode(TorsionLibraryInfo)
  672         if HierarchyClassElementNode is None:
  673             return (False, None)
  674 
  675         TorsionAlertsUtil.TrackHierarchyClassElementNode(TorsionLibraryInfo, HierarchyClassElementNode)
  676 
  677         #  Match hierarchy subclasses before matching torsion rules...
  678         MatchStatus, MatchInfo = self._MatchRotatableBondAgainstGenericHierarchySubClasses(Mol, RotBondAtomIndices, HierarchyClassElementNode)
  679 
  680         if not MatchStatus:
  681             MatchStatus, MatchInfo = self._MatchRotatableBondAgainstGenericHierarchyTorsionRules(Mol, RotBondAtomIndices, HierarchyClassElementNode)
  682 
  683         TorsionAlertsUtil.RemoveLastHierarchyClassElementNodeFromTracking(TorsionLibraryInfo)
  684 
  685         return (MatchStatus, MatchInfo)
  686 
  687 
  688     def _MatchRotatableBondAgainstGenericHierarchySubClasses(self, Mol, RotBondAtomIndices, HierarchyClassElementNode):
  689         """Match rotatable bond againat generic hierarchy subclasses."""
  690 
  691         for ElementChildNode in HierarchyClassElementNode:
  692             if ElementChildNode.tag != "hierarchySubClass":
  693                 continue
  694 
  695             SubClassMatchStatus = self._ProcessHierarchySubClassElementForRotatableBondMatch(Mol, RotBondAtomIndices, ElementChildNode)
  696 
  697             if SubClassMatchStatus:
  698                 MatchStatus, MatchInfo = self._ProcessElementForRotatableBondMatch(Mol, RotBondAtomIndices, ElementChildNode)
  699 
  700                 if MatchStatus:
  701                     return (MatchStatus, MatchInfo)
  702 
  703         return(False, None)
  704 
  705 
  706     def _MatchRotatableBondAgainstGenericHierarchyTorsionRules(self, Mol, RotBondAtomIndices, HierarchyClassElementNode):
  707         """Match rotatable bond againat torsion rules generic hierarchy class."""
  708 
  709         for ElementChildNode in HierarchyClassElementNode:
  710             if ElementChildNode.tag != "torsionRule":
  711                 continue
  712 
  713             MatchStatus, MatchInfo = self._ProcessTorsionRuleElementForRotatableBondMatch(Mol, RotBondAtomIndices, ElementChildNode)
  714 
  715             if MatchStatus:
  716                 return (MatchStatus, MatchInfo)
  717 
  718         return(False, None)
  719 
  720 
  721     def _ProcessElementForRotatableBondMatch(self, Mol, RotBondAtomIndices, ElementNode):
  722         """Process element node to recursively match rotatable bond against hierarchy
  723         subclasses and torsion rules."""
  724 
  725         TorsionLibraryInfo = self.TorsionLibraryInfo
  726 
  727         for ElementChildNode in ElementNode:
  728             if ElementChildNode.tag == "hierarchySubClass":
  729                 SubClassMatchStatus = self._ProcessHierarchySubClassElementForRotatableBondMatch(Mol, RotBondAtomIndices, ElementChildNode)
  730 
  731                 if SubClassMatchStatus:
  732                     TorsionAlertsUtil.TrackHierarchySubClassElementNode(TorsionLibraryInfo, ElementChildNode)
  733 
  734                     MatchStatus, MatchInfo = self._ProcessElementForRotatableBondMatch(Mol, RotBondAtomIndices, ElementChildNode)
  735                     if MatchStatus:
  736                         TorsionAlertsUtil.RemoveLastHierarchySubClassElementNodeFromTracking(TorsionLibraryInfo)
  737                         return (MatchStatus, MatchInfo)
  738 
  739                     TorsionAlertsUtil.RemoveLastHierarchySubClassElementNodeFromTracking(TorsionLibraryInfo)
  740 
  741             elif ElementChildNode.tag == "torsionRule":
  742                 MatchStatus, MatchInfo = self._ProcessTorsionRuleElementForRotatableBondMatch(Mol, RotBondAtomIndices, ElementChildNode)
  743 
  744                 if MatchStatus:
  745                     return (MatchStatus, MatchInfo)
  746 
  747         return (False, None)
  748 
  749 
  750     def _ProcessHierarchySubClassElementForRotatableBondMatch(self, Mol, RotBondAtomIndices, ElementNode):
  751         """Process hierarchy subclass element to match rotatable bond."""
  752 
  753         # Setup subclass SMARTS pattern mol...
  754         SubClassPatternMol = TorsionAlertsUtil.SetupHierarchySubClassElementPatternMol(self.TorsionLibraryInfo, ElementNode)
  755         if SubClassPatternMol is None:
  756             return False
  757 
  758         # Match SMARTS pattern...
  759         SubClassPatternMatches = TorsionAlertsUtil.FilterSubstructureMatchesByAtomMapNumbers(Mol, SubClassPatternMol, Mol.GetSubstructMatches(SubClassPatternMol, useChirality = False))
  760         if len(SubClassPatternMatches) == 0:
  761             return False
  762 
  763         # Match rotatable bond indices...
  764         RotBondAtomIndex1, RotBondAtomIndex2 = RotBondAtomIndices
  765         MatchStatus = False
  766         for SubClassPatternMatch in SubClassPatternMatches:
  767             if len(SubClassPatternMatch) == 2:
  768                 # Matched to pattern containing map atom numbers ":2" and ":3"...
  769                 CentralAtomsIndex1, CentralAtomsIndex2 = SubClassPatternMatch
  770             elif len(SubClassPatternMatch) == 4:
  771                 # Matched to pattern containing map atom numbers ":1", ":2", ":3" and ":4"...
  772                 CentralAtomsIndex1 = SubClassPatternMatch[1]
  773                 CentralAtomsIndex2 = SubClassPatternMatch[2]
  774             elif len(SubClassPatternMatch) == 3:
  775                 SubClassSMARTSPattern = ElementNode.get("smarts")
  776                 if TorsionAlertsUtil.DoesSMARTSContainsMappedAtoms(SubClassSMARTSPattern, [":2", ":3", ":4"]):
  777                     # Matched to pattern containing map atom numbers ":2", ":3" and ":4"...
  778                     CentralAtomsIndex1 = SubClassPatternMatch[0]
  779                     CentralAtomsIndex2 = SubClassPatternMatch[1]
  780                 else:
  781                     # Matched to pattern containing map atom numbers ":1", ":2" and ":3"...
  782                     CentralAtomsIndex1 = SubClassPatternMatch[1]
  783                     CentralAtomsIndex2 = SubClassPatternMatch[2]
  784             else:
  785                 continue
  786 
  787             if CentralAtomsIndex1 != CentralAtomsIndex2:
  788                 if ((CentralAtomsIndex1 == RotBondAtomIndex1 and CentralAtomsIndex2 == RotBondAtomIndex2) or (CentralAtomsIndex1 == RotBondAtomIndex2 and CentralAtomsIndex2 == RotBondAtomIndex1)):
  789                     MatchStatus = True
  790                     break
  791 
  792         return (MatchStatus)
  793 
  794 
  795     def _ProcessTorsionRuleElementForRotatableBondMatch(self, Mol, RotBondAtomIndices, ElementNode):
  796         """Process torsion rule element to match rotatable bond."""
  797 
  798         TorsionLibraryInfo = self.TorsionLibraryInfo
  799 
  800         #  Retrieve torsions matched to rotatable bond...
  801         TorsionAtomIndicesList, TorsionAnglesList = self._MatchTorsionRuleToRotatableBond(Mol, RotBondAtomIndices, ElementNode)
  802         if TorsionAtomIndicesList is None:
  803             return (False, None)
  804 
  805         # Setup torsion angles and enery bin information for matched torsion rule...
  806         TorsionRuleAnglesInfo = TorsionAlertsUtil.SetupTorsionRuleAnglesInfo(TorsionLibraryInfo, ElementNode)
  807         if TorsionRuleAnglesInfo is None:
  808             return (False, None)
  809 
  810         # Setup highest strain energy for matched torsions...
  811         TorsionAtomIndices, TorsionAngle, EnergyMethod, AngleNotObserved, Energy, EnergyLowerBound, EnergyUpperBound = self._SelectHighestStrainEnergyTorsionForRotatableBond(TorsionRuleAnglesInfo, TorsionAtomIndicesList, TorsionAnglesList)
  812 
  813         # Setup hierarchy class and subclass names...
  814         HierarchyClassName, HierarchySubClassName = TorsionAlertsUtil.SetupHierarchyClassAndSubClassNamesForRotatableBond(TorsionLibraryInfo)
  815 
  816         # Setup rule node ID...
  817         TorsionRuleNodeID = ElementNode.get("NodeID")
  818 
  819         # Setup SMARTS...
  820         TorsionRuleSMARTS = ElementNode.get("smarts")
  821         if " " in TorsionRuleSMARTS:
  822             TorsionRuleSMARTS = TorsionRuleSMARTS.replace(" ", "")
  823 
  824         # Setup match info...
  825         MatchInfo = [TorsionAtomIndices, TorsionAngle, HierarchyClassName, HierarchySubClassName, TorsionRuleNodeID, TorsionRuleSMARTS, EnergyMethod, AngleNotObserved, Energy, EnergyLowerBound, EnergyUpperBound]
  826 
  827         # Setup match status...
  828         MatchStatus = True
  829 
  830         return (MatchStatus, MatchInfo)
  831 
  832 
  833     def _SelectHighestStrainEnergyTorsionForRotatableBond(self, TorsionRuleAnglesInfo, TorsionAtomIndicesList, TorsionAnglesList):
  834         """Select highest strain energy torsion matched to a rotatable bond."""
  835 
  836         TorsionAtomIndices, TorsionAngle, EnergyMethod, AngleNotObserved, Energy, EnergyLowerBound, EnergyUpperBound = [None] * 7
  837         ValidEnergyValue, ValidCurrentEnergyValue = [False] * 2
  838 
  839         FirstTorsion = True
  840         for Index in range(0, len(TorsionAtomIndicesList)):
  841             CurrentTorsionAtomIndices = TorsionAtomIndicesList[Index]
  842             CurrentTorsionAngle = TorsionAnglesList[Index]
  843 
  844             if FirstTorsion:
  845                 FirstTorsion = False
  846                 EnergyMethod, AngleNotObserved, Energy, EnergyLowerBound, EnergyUpperBound = self._SetupStrainEnergyForRotatableBond(TorsionRuleAnglesInfo, CurrentTorsionAngle)
  847                 TorsionAtomIndices = CurrentTorsionAtomIndices
  848                 TorsionAngle = CurrentTorsionAngle
  849                 ValidEnergyValue = self._IsEnergyValueValid(Energy)
  850                 continue
  851 
  852             # Select highest strain energy...
  853             CurrentEnergyMethod, CurrentAngleNotObserved, CurrentEnergy, CurrentEnergyLowerBound, CurrentEnergyUpperBound = self._SetupStrainEnergyForRotatableBond(TorsionRuleAnglesInfo, CurrentTorsionAngle)
  854             ValidCurrentEnergyValue = self._IsEnergyValueValid(CurrentEnergy)
  855 
  856             UpdateValues = False
  857             if ValidEnergyValue and ValidCurrentEnergyValue:
  858                 if CurrentEnergy > Energy:
  859                     UpdateValues = True
  860             elif ValidCurrentEnergyValue:
  861                 if not ValidEnergyValue:
  862                     UpdateValues = True
  863 
  864             if UpdateValues:
  865                 EnergyMethod, AngleNotObserved, Energy, EnergyLowerBound, EnergyUpperBound = [CurrentEnergyMethod, CurrentAngleNotObserved, CurrentEnergy, CurrentEnergyLowerBound, CurrentEnergyUpperBound]
  866                 TorsionAtomIndices = CurrentTorsionAtomIndices
  867                 TorsionAngle = CurrentTorsionAngle
  868 
  869         return (TorsionAtomIndices, TorsionAngle, EnergyMethod, AngleNotObserved, Energy, EnergyLowerBound, EnergyUpperBound)
  870 
  871 
  872     def _IsEnergyValueValid(self, Value):
  873         """Check for valid energy value."""
  874 
  875         return False if (Value is None or math.isnan(Value) or math.isinf(Value)) else True
  876 
  877 
  878     def _SetupStrainEnergyForRotatableBond(self, TorsionRuleAnglesInfo, TorsionAngle):
  879         """Setup strain energy for rotatable bond."""
  880 
  881         if TorsionRuleAnglesInfo["EnergyMethodExact"]:
  882             return (self._SetupStrainEnergyForRotatableBondByExactMethod(TorsionRuleAnglesInfo, TorsionAngle))
  883         elif TorsionRuleAnglesInfo["EnergyMethodApproximate"]:
  884             return (self._SetupStrainEnergyForRotatableBondByApproximateMethod(TorsionRuleAnglesInfo, TorsionAngle))
  885         else:
  886             EnergyMethod, AngleNotObserved, Energy, EnergyLowerBound, EnergyUpperBound = [None, None, None, None, None]
  887             return (EnergyMethod, AngleNotObserved, Energy, EnergyLowerBound, EnergyUpperBound)
  888 
  889 
  890     def _SetupStrainEnergyForRotatableBondByExactMethod(self, TorsionRuleAnglesInfo, TorsionAngle):
  891         """Setup strain energy for rotatable bond by exact method."""
  892 
  893         EnergyMethod, AngleNotObserved, Energy, EnergyLowerBound, EnergyUpperBound = ["Exact", None, None, None, None]
  894 
  895         # Map angle to energy bin numbers...
  896         BinNum = math.ceil(TorsionAngle / 10) + 17
  897         PreviousBinNum = (BinNum + 35) % 36
  898 
  899         # Bin angle from -170 to 180 by the right end points...
  900         BinAngleRightSide = (BinNum - 17) * 10
  901 
  902         # Angle offset towards the left of the bin from the right end point...
  903         AngleOffset = TorsionAngle - BinAngleRightSide
  904 
  905         BinEnergy = TorsionRuleAnglesInfo["HistogramEnergy"][BinNum]
  906         PreviousBinEnergy = TorsionRuleAnglesInfo["HistogramEnergy"][PreviousBinNum]
  907         Energy =  BinEnergy + (BinEnergy - PreviousBinEnergy)/10.0 * AngleOffset
  908 
  909         BinEnergyLowerBound = TorsionRuleAnglesInfo["HistogramEnergyLowerBound"][BinNum]
  910         PreviousBinEnergyLowerBound = TorsionRuleAnglesInfo["HistogramEnergyLowerBound"][PreviousBinNum]
  911         EnergyLowerBound =  BinEnergyLowerBound + (BinEnergyLowerBound - PreviousBinEnergyLowerBound)/10.0 * AngleOffset
  912 
  913         BinEnergyUpperBound = TorsionRuleAnglesInfo["HistogramEnergyUpperBound"][BinNum]
  914         PreviousBinEnergyUpperBound = TorsionRuleAnglesInfo["HistogramEnergyUpperBound"][PreviousBinNum]
  915         EnergyUpperBound =  BinEnergyUpperBound + (BinEnergyUpperBound - PreviousBinEnergyUpperBound)/10.0 * AngleOffset
  916 
  917         return (EnergyMethod, AngleNotObserved, Energy, EnergyLowerBound, EnergyUpperBound)
  918 
  919 
  920     def _SetupStrainEnergyForRotatableBondByApproximateMethod(self, TorsionRuleAnglesInfo, TorsionAngle):
  921         """Setup strain energy for rotatable bond by approximate method."""
  922 
  923         EnergyMethod, AngleNotObserved, Energy, EnergyLowerBound, EnergyUpperBound = ["Approximate", True, None, None, None]
  924 
  925         for AngleID in TorsionRuleAnglesInfo["IDs"]:
  926             Tolerance2 = TorsionRuleAnglesInfo["Tolerance2"][AngleID]
  927             Theta0 = TorsionRuleAnglesInfo["Theta0"][AngleID]
  928 
  929             AngleDiff = TorsionAlertsUtil.CalculateTorsionAngleDifference(TorsionAngle, Theta0)
  930             if abs(AngleDiff) <= Tolerance2:
  931                 Beta1 = TorsionRuleAnglesInfo["Beta1"][AngleID]
  932                 Beta2 = TorsionRuleAnglesInfo["Beta2"][AngleID]
  933 
  934                 Energy = Beta1*(AngleDiff ** 2) + Beta2*(AngleDiff** 4)
  935 
  936                 # Estimates of lower and upper bound are not available for
  937                 # approximate method...
  938                 EnergyLowerBound = Energy
  939                 EnergyUpperBound = Energy
  940 
  941                 AngleNotObserved = False
  942 
  943                 break
  944 
  945         return (EnergyMethod, AngleNotObserved, Energy, EnergyLowerBound, EnergyUpperBound)
  946 
  947 
  948     def _MatchTorsionRuleToRotatableBond(self, Mol, RotBondAtomIndices, ElementNode):
  949         """Retrieve matched torsions for torsion rule matched to rotatable bond."""
  950 
  951         # Get torsion matches...
  952         TorsionMatches = self._GetMatchesForTorsionRule(Mol, ElementNode)
  953         if TorsionMatches is None or len(TorsionMatches) == 0:
  954             return (None, None)
  955 
  956         # Identify all torsion matches corresponding to central atoms in RotBondAtomIndices...
  957         RotBondAtomIndex1, RotBondAtomIndex2 = RotBondAtomIndices
  958         RotBondTorsionMatches, RotBondTorsionAngles = [None] * 2
  959 
  960         for TorsionMatch in TorsionMatches:
  961             CentralAtomIndex1 = TorsionMatch[1]
  962             CentralAtomIndex2 = TorsionMatch[2]
  963 
  964             if ((CentralAtomIndex1 == RotBondAtomIndex1 and CentralAtomIndex2 == RotBondAtomIndex2) or (CentralAtomIndex1 == RotBondAtomIndex2 and CentralAtomIndex2 == RotBondAtomIndex1)):
  965                 TorsionAngle = self._CalculateTorsionAngle(Mol, TorsionMatch)
  966                 if RotBondTorsionMatches is None:
  967                     RotBondTorsionMatches = []
  968                     RotBondTorsionAngles = []
  969                 RotBondTorsionMatches.append(TorsionMatch)
  970                 RotBondTorsionAngles.append(TorsionAngle)
  971 
  972         return (RotBondTorsionMatches, RotBondTorsionAngles)
  973 
  974 
  975     def _CalculateTorsionAngle(self, Mol, TorsionMatch):
  976         """Calculate torsion angle."""
  977 
  978         # Calculate torsion angle using torsion atom indices..
  979         MolConf = Mol.GetConformer(0)
  980         TorsionAngle = rdMolTransforms.GetDihedralDeg(MolConf, TorsionMatch[0], TorsionMatch[1], TorsionMatch[2], TorsionMatch[3])
  981         TorsionAngle = round(TorsionAngle, 2)
  982 
  983         return TorsionAngle
  984 
  985 
  986     def _GetMatchesForTorsionRule(self, Mol, ElementNode):
  987         """Get matches for torsion rule."""
  988 
  989         # Match torsions...
  990         TorsionMatches = self._GetSubstructureMatchesForTorsionRule(Mol, ElementNode)
  991 
  992         if TorsionMatches is None or len(TorsionMatches) == 0:
  993             return TorsionMatches
  994 
  995         # Filter torsion matches...
  996         FiltertedTorsionMatches = []
  997         for TorsionMatch in TorsionMatches:
  998             if len(TorsionMatch) != 4:
  999                 continue
 1000 
 1001             # Ignore matches containing hydrogen atoms as first or last atom...
 1002             if Mol.GetAtomWithIdx(TorsionMatch[0]).GetAtomicNum() == 1:
 1003                 continue
 1004             if Mol.GetAtomWithIdx(TorsionMatch[3]).GetAtomicNum() == 1:
 1005                 continue
 1006 
 1007             FiltertedTorsionMatches.append(TorsionMatch)
 1008 
 1009         return FiltertedTorsionMatches
 1010 
 1011 
 1012     def _GetSubstructureMatchesForTorsionRule(self, Mol, ElementNode):
 1013         """Get substructure matches for a torsion rule."""
 1014 
 1015         # Setup torsion rule SMARTS pattern mol....
 1016         TorsionRuleNodeID = ElementNode.get("NodeID")
 1017         TorsionSMARTSPattern = ElementNode.get("smarts")
 1018         TorsionPatternMol = TorsionAlertsUtil.SetupTorsionRuleElementPatternMol(self.TorsionLibraryInfo, ElementNode, TorsionRuleNodeID, TorsionSMARTSPattern)
 1019         if TorsionPatternMol is None:
 1020             return None
 1021 
 1022         # Match torsions...
 1023         TorsionMatches = TorsionAlertsUtil.FilterSubstructureMatchesByAtomMapNumbers(Mol, TorsionPatternMol, Mol.GetSubstructMatches(TorsionPatternMol, useChirality = False))
 1024 
 1025         return TorsionMatches