MayaChemTools

    1 #!/bin/env python
    2 #
    3 # File: PyMOLInfoMacromolecules.py
    4 # Author: Manish Sud <msud@san.rr.com>
    5 #
    6 # Copyright (C) 2024 Manish Sud. All rights reserved.
    7 #
    8 # The functionality available in this script is implemented using PyMOL, a
    9 # molecular visualization system on an open source foundation originally
   10 # developed by Warren DeLano.
   11 #
   12 # This file is part of MayaChemTools.
   13 #
   14 # MayaChemTools is free software; you can redistribute it and/or modify it under
   15 # the terms of the GNU Lesser General Public License as published by the Free
   16 # Software Foundation; either version 3 of the License, or (at your option) any
   17 # later version.
   18 #
   19 # MayaChemTools is distributed in the hope that it will be useful, but without
   20 # any warranty; without even the implied warranty of merchantability of fitness
   21 # for a particular purpose.  See the GNU Lesser General Public License for more
   22 # details.
   23 #
   24 # You should have received a copy of the GNU Lesser General Public License
   25 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
   26 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
   27 # Boston, MA, 02111-1307, USA.
   28 #
   29 
   30 from __future__ import print_function
   31 
   32 # Add local python path to the global path and import standard library modules...
   33 import os
   34 import sys;  sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python"))
   35 import time
   36 import re
   37 
   38 # PyMOL imports...
   39 try:
   40     import pymol
   41     # Finish launching PyMOL in  a command line mode for batch processing (-c)
   42     # along with the following options:  disable loading of pymolrc and plugins (-k);
   43     # suppress start up messages (-q)
   44     pymol.finish_launching(['pymol', '-ckq'])
   45 except ImportError as ErrMsg:
   46     sys.stderr.write("\nFailed to import PyMOL module/package: %s\n" % ErrMsg)
   47     sys.stderr.write("Check/update your PyMOL environment and try again.\n\n")
   48     sys.exit(1)
   49 
   50 # MayaChemTools imports...
   51 try:
   52     from docopt import docopt
   53     import MiscUtil
   54     import PyMOLUtil
   55 except ImportError as ErrMsg:
   56     sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg)
   57     sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n")
   58     sys.exit(1)
   59 
   60 ScriptName = os.path.basename(sys.argv[0])
   61 Options = {}
   62 OptionsInfo = {}
   63 
   64 def main():
   65     """Start execution of the script."""
   66     
   67     MiscUtil.PrintInfo("\n%s (PyMOL v%s; MayaChemTools v%s; %s): Starting...\n" % (ScriptName, pymol.cmd.get_version()[0], MiscUtil.GetMayaChemToolsVersion(), time.asctime()))
   68     
   69     (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
   70     
   71     # Retrieve command line arguments and options...
   72     RetrieveOptions()
   73     
   74     # Process and validate command line arguments and options...
   75     ProcessOptions()
   76 
   77     # Perform actions required by the script...
   78     ListInfo()
   79     
   80     MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
   81     MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
   82 
   83 def ListInfo():
   84     """List information for macromolecules."""
   85 
   86     for Infile in OptionsInfo["InfilesNames"]:
   87         MiscUtil.PrintInfo("\nProcessing file %s..." % Infile)
   88         ListFileInfo(Infile)
   89 
   90 def ListFileInfo(Infile):
   91     """List information for macromolecules in a file."""
   92     
   93     FileDir, FileName, FileExt = MiscUtil.ParseFileName(Infile)
   94     MolName = FileName
   95 
   96     # Load infile...
   97     pymol.cmd.load(Infile, MolName)
   98     
   99     ChainIDs = PyMOLUtil.GetChains(MolName)
  100     ListHeaderInfo(Infile)
  101     
  102     ListChainsInfo(MolName, ChainIDs)
  103     ListChainsResiduesInfo(MolName, ChainIDs)
  104     
  105     ListLigandsInfo(MolName, ChainIDs)
  106     ListSolventsInfo(MolName, ChainIDs)
  107     ListInorganicsInfo(MolName, ChainIDs)
  108 
  109     ListPocketsInfo(MolName, ChainIDs)
  110     
  111     ListInterfaceResiduesInfo(MolName, ChainIDs)
  112     ListSurfaceResiduesInfo(MolName, ChainIDs)
  113     ListPhiPsiAnglesInfo(MolName, ChainIDs)
  114     
  115     ListBoundingBoxInfo(MolName)
  116     ListCentroid(MolName)
  117     
  118     # Delete infile object...
  119     pymol.cmd.delete(MolName)
  120     
  121     ListFileSizeAndModificationInfo(Infile)
  122 
  123 def ListHeaderInfo(Infile):
  124     """List header information."""
  125 
  126     if not (OptionsInfo["All"] or OptionsInfo["Header"]):
  127         return
  128 
  129     FileDir, FileName, FileExt = MiscUtil.ParseFileName(Infile)
  130 
  131     Classification, DepositionDate, IDCode, ExperimentalTechnique, Resolution = ["Not Available"] * 5
  132     if re.match("^pdb$", FileExt, re.I):
  133          Classification, DepositionDate, IDCode, ExperimentalTechnique, Resolution = RetriveHeadAndExperimentalInfoFromPDBFile(Infile)
  134     elif re.match("^cif$", FileExt, re.I):
  135          Classification, DepositionDate, IDCode, ExperimentalTechnique, Resolution = RetriveHeadAndExperimentalInfoFromCIFFile(Infile)
  136 
  137     MiscUtil.PrintInfo("\nID: %s\nClassification: %s\nDeposition date: %s" % (IDCode, Classification, DepositionDate))
  138     MiscUtil.PrintInfo("\nExperimental technique: %s\nResolution: %s" % (ExperimentalTechnique, Resolution))
  139 
  140 def RetriveHeadAndExperimentalInfoFromPDBFile(Infile):
  141     """Retrieve header and experimental information from PDB file."""
  142 
  143     Classification, DepositionDate, IDCode, ExperimentalTechnique, Resolution = ["Not Available"] * 5
  144 
  145     Lines = MiscUtil.GetTextLines(Infile)
  146 
  147     # Retrieve header info...
  148     for Line in Lines:
  149         if re.match("^HEADER", Line, re.I):
  150             # Format: 10x40s9s3x4s
  151             FormatSize = 66
  152             Line = PrepareLineForFormatSize(Line, FormatSize)
  153             
  154             Classification = Line[10:50]
  155             DepositionDate = Line[50:59]
  156             IDCode = Line[62:66]
  157             
  158             Classification = Classification.strip() if len(Classification.strip()) else "Not Available"
  159             DepositionDate = DepositionDate.strip() if len(DepositionDate.strip()) else "Not Available"
  160             IDCode = IDCode.strip() if len(IDCode.strip()) else "Not Available"
  161             
  162             break
  163 
  164     # Retrieve experimental info...
  165     for Line in Lines:
  166         if re.match("^EXPDTA", Line, re.I):
  167             ExperimentalTechnique = re.sub("^EXPDTA", "", Line, re.I)
  168             ExperimentalTechnique = ExperimentalTechnique.strip()
  169         elif re.match("^REMARK   2 RESOLUTION.", Line, re.I):
  170             if re.search("NOT APPLICABLE", Line, re.I):
  171                 Resolution = "NOT APPLICABLE"
  172             else:
  173                 FormatSize = 70
  174                 Line = PrepareLineForFormatSize(Line, FormatSize)
  175                 Resolution = Line[22:70]
  176                 Resolution = Resolution.strip() if len(Resolution.strip()) else "Not Available"
  177         elif re.match("^(ATOM|HETATM)", Line, re.I):
  178             break
  179 
  180     return Classification, DepositionDate, IDCode, ExperimentalTechnique, Resolution
  181 
  182 def RetriveHeadAndExperimentalInfoFromCIFFile(Infile):
  183     """Retrieve header information from CIF file."""
  184 
  185     Classification, DepositionDate, IDCode, ExperimentalTechnique, Resolution = ["Not Available"] * 5
  186     
  187     Lines = MiscUtil.GetTextLines(Infile)
  188     
  189     # IDCode...
  190     for Line in Lines:
  191         if re.match("^_struct_keywords.entry_id", Line, re.I):
  192             IDCode = re.sub("^_struct_keywords.entry_id", "", Line, re.I)
  193             IDCode = IDCode.strip() if len(IDCode.strip()) else "Not Available"
  194             break
  195 
  196     # Classification...
  197     for Line in Lines:
  198         if re.match("^_struct_keywords.pdbx_keywords", Line, re.I):
  199             Classification = re.sub("^_struct_keywords.pdbx_keywords", "", Line, re.I)
  200             Classification = Classification.strip() if len(Classification.strip()) else "Not Available"
  201             break
  202     
  203     # Deposition date...
  204     for Line in Lines:
  205         if re.match("^_pdbx_database_status.recvd_initial_deposition_date", Line, re.I):
  206             DepositionDate = re.sub("^_pdbx_database_status.recvd_initial_deposition_date", "", Line, re.I)
  207             DepositionDate = DepositionDate.strip() if len(DepositionDate.strip()) else "Not Available"
  208             break
  209     
  210     # Experimental technique...
  211     for Line in Lines:
  212         if re.match("^_exptl.method", Line, re.I):
  213             ExperimentalTechnique = re.sub("(_exptl.method|')", "", Line, flags = re.I)
  214             ExperimentalTechnique = ExperimentalTechnique.strip() if len(ExperimentalTechnique.strip()) else "Not Available"
  215             break
  216         
  217     # Resolution...
  218     for Line in Lines:
  219         if re.match("^_reflns.d_resolution_high", Line, re.I):
  220             Resolution = re.sub("^_reflns.d_resolution_high", "", Line, re.I)
  221             Resolution = Resolution.strip() if len(Resolution.strip()) else "Not Available"
  222             break
  223         
  224     return Classification, DepositionDate, IDCode, ExperimentalTechnique, Resolution
  225 
  226 def PrepareLineForFormatSize(Text, FormatSize):
  227     """Prepare text string for format size padding or truncation to
  228     alter its size.
  229     """
  230 
  231     TextLen = len(Text)
  232     if TextLen < FormatSize:
  233         PaddingLen = FormatSize - TextLen
  234         TextPadding = " " * PaddingLen
  235         Text = Text + TextPadding
  236     elif TextLen > FormatSize:
  237         Text = Text[:FormatSize]
  238     
  239     return Text
  240 
  241 def ListChainsInfo(MolName, ChainIDs):
  242     """List chains information across all chains."""
  243 
  244     if not (OptionsInfo["All"] or OptionsInfo["Chains"]):
  245         return
  246 
  247     ChainsInfo = ", ".join(ChainIDs) if len(ChainIDs) else "None"
  248 
  249     MiscUtil.PrintInfo("\nNumber of chains: %s" % len(ChainIDs))
  250     MiscUtil.PrintInfo("ChainIDs: %s" % ChainsInfo)
  251 
  252 def ListChainsResiduesInfo(MolName, ChainIDs):
  253     """List polymer chain residue information across all chains."""
  254     
  255     if not (OptionsInfo["All"] or OptionsInfo["CountResidues"]):
  256         return
  257     
  258     ListSelectionResiduesInfo(MolName, ChainIDs, "Chains")
  259 
  260     # List information for non-standard amino acids...
  261     ListSelectionResiduesInfo(MolName, ChainIDs, "NonStandardAminoAcids")
  262 
  263 def ListLigandsInfo(MolName, ChainIDs):
  264     """List ligand information across all chains."""
  265     
  266     if not (OptionsInfo["All"] or OptionsInfo["Ligands"]):
  267         return
  268     
  269     ListSelectionResiduesInfo(MolName, ChainIDs, "Ligands")
  270 
  271 def ListSolventsInfo(MolName, ChainIDs):
  272     """List solvents information across all chains."""
  273     
  274     if not (OptionsInfo["All"] or OptionsInfo["Solvents"]):
  275         return
  276 
  277     ListSelectionResiduesInfo(MolName, ChainIDs, "Solvents")
  278     
  279 def ListInorganicsInfo(MolName, ChainIDs):
  280     """List inorganics information across all chains."""
  281     
  282     if not (OptionsInfo["All"] or OptionsInfo["Inorganics"]):
  283         return
  284     
  285     ListSelectionResiduesInfo(MolName, ChainIDs, "Inorganics")
  286 
  287 def ListSelectionResiduesInfo(MolName, ChainIDs, SelectionType):
  288     """List residues information for a specified selection type."""
  289 
  290     Lines = []
  291     TotalResCount = 0
  292     SelectionLabel = None
  293     
  294     if not len(ChainIDs):
  295         SelectionInfo, SelectionLabel = GetSelectionResiduesInfo(MolName, None, SelectionType)
  296         MiscUtil.PrintInfo("\nNumber of %s residues: %s" % (SelectionLabel, TotalResCount))
  297         return
  298         
  299     for ChainID in ChainIDs:
  300         SelectionInfo, SelectionLabel = GetSelectionResiduesInfo(MolName, ChainID, SelectionType)
  301 
  302         ChainResCount = 0
  303         LineWords = []
  304         
  305         SortedResNames = sorted(SelectionInfo["ResNames"], key = lambda ResName: SelectionInfo["ResCount"][ResName], reverse = True)
  306         for ResName in SortedResNames:
  307             ResCount = SelectionInfo["ResCount"][ResName]
  308             LineWords.append("%s - %s" % (ResName, ResCount))
  309 
  310             ChainResCount += ResCount
  311             TotalResCount += ResCount
  312         
  313         Line = "; ".join(LineWords) if len(LineWords) else None
  314         Lines.append("Chain ID: %s; Count: %s; Names:  %s" % (ChainID, ChainResCount, Line))
  315     
  316     MiscUtil.PrintInfo("\nNumber of %s residues: %s" % (SelectionLabel, TotalResCount))
  317     for Line in Lines:
  318         MiscUtil.PrintInfo("%s" % Line)
  319 
  320 def GetSelectionResiduesInfo(MolName, ChainID, SelectionType):
  321     """Get residues info for a specified selection type."""
  322 
  323     SelectionInfo = None
  324     SelectionLabel = None
  325     
  326     if re.match("^Ligands$", SelectionType, re.I):
  327         SelectionLabel = "ligand"
  328         SelectionInfo = PyMOLUtil.GetLigandResiduesInfo(MolName, ChainID) if ChainID is not None else None
  329     elif re.match("^Solvents$", SelectionType, re.I):
  330         SelectionLabel = "solvent"
  331         SelectionInfo = PyMOLUtil.GetSolventResiduesInfo(MolName, ChainID) if ChainID is not None else None
  332     elif re.match("^Inorganics$", SelectionType, re.I):
  333         SelectionLabel = "inorganic"
  334         SelectionInfo = PyMOLUtil.GetInorganicResiduesInfo(MolName, ChainID) if ChainID is not None else None
  335     elif re.match("^Chains$", SelectionType, re.I):
  336         SelectionLabel = "polymer chain"
  337         SelectionInfo = PyMOLUtil.GetPolymerResiduesInfo(MolName, ChainID) if ChainID is not None else None
  338     elif re.match("^NonStandardAminoAcids$", SelectionType, re.I):
  339         SelectionLabel = "non-standard amino acids"
  340         SelectionInfo = PyMOLUtil.GetAminoAcidResiduesInfo(MolName, ChainID, "NonStandard") if ChainID is not None else None
  341     else:
  342         MiscUtil.PrintError("Failed to retrieve residues information: Selection type %s is not valid..." % SelectionType)
  343 
  344     return SelectionInfo, SelectionLabel
  345 
  346 def ListPocketsInfo(MolName, ChainIDs):
  347     """List pockect residues information across all chains."""
  348     
  349     if not (OptionsInfo["All"] or OptionsInfo["PocketLigands"] or OptionsInfo["PocketSolvents"] or OptionsInfo["PocketInorganics"]) :
  350         return
  351     
  352     for ChainID in ChainIDs:
  353         MiscUtil.PrintInfo("\nListing ligand pockets information for chain %s..." % (ChainID))
  354         
  355         LigandsInfo = PyMOLUtil.GetLigandResiduesInfo(MolName, ChainID)
  356         if not len(LigandsInfo["ResNames"]):
  357             MiscUtil.PrintInfo("\nNumber of residues in ligand pocket: None")
  358             MiscUtil.PrintInfo("Chain ID: %s; Ligands: None" % (ChainID))
  359             continue
  360         
  361         for LigandResName in sorted(LigandsInfo["ResNames"]):
  362             for LigandResNum in LigandsInfo["ResNum"][LigandResName]:
  363                 ListPocketsPolymerInfo(MolName, ChainID, LigandResName, LigandResNum)
  364                 ListPocketsSolventsInfo(MolName, ChainID, LigandResName, LigandResNum)
  365                 ListPocketsInorganicsInfo(MolName, ChainID, LigandResName, LigandResNum)
  366 
  367 def ListPocketsPolymerInfo(MolName, ChainID, LigandResName, LigandResNum):
  368     """List pockect residues information across all chains."""
  369 
  370     if not (OptionsInfo["All"] or OptionsInfo["PocketLigands"]):
  371         return
  372     
  373     ListPocketSelectionResiduesInfo(MolName, ChainID, LigandResName, LigandResNum, "Pockets")
  374 
  375 def ListPocketsSolventsInfo(MolName, ChainID, LigandResName, LigandResNum):
  376     """List pockect solvent residues information across all chains."""
  377     
  378     if not (OptionsInfo["All"] or OptionsInfo["PocketSolvents"]):
  379         return
  380     
  381     ListPocketSelectionResiduesInfo(MolName, ChainID, LigandResName, LigandResNum, "PocketSolvents")
  382 
  383 def ListPocketsInorganicsInfo(MolName, ChainID, LigandResName, LigandResNum):
  384     """List pockect inorganic residues information across all chains."""
  385     
  386     if not (OptionsInfo["All"] or OptionsInfo["PocketInorganics"]):
  387         return
  388     
  389     ListPocketSelectionResiduesInfo(MolName, ChainID, LigandResName, LigandResNum, "PocketInorganics")
  390 
  391 def ListPocketSelectionResiduesInfo(MolName, ChainID, LigandResName, LigandResNum, SelectionType):
  392     """List residues information for a specified pocket selection type."""
  393 
  394     PocketDistanceCutoff  = OptionsInfo["PocketDistanceCutoff"]
  395     SelectionLabel = GetPocketSelectionResiduesInfoLabel(SelectionType)
  396     
  397     SelectionInfo = GetPocketSelectionResiduesInfo(MolName, ChainID, LigandResName, LigandResNum, PocketDistanceCutoff, SelectionType)
  398 
  399     ResiduesCount, ResiduesDistribution, ResiduesIDs = SetupSelectionResiduesInfo(SelectionInfo)
  400     MiscUtil.PrintInfo("\nNumber of %s residues in ligand pocket: %s" % (SelectionLabel, ResiduesCount))
  401     MiscUtil.PrintInfo("Chain ID: %s; Ligand ID: %s_%s\nResidue distribution: %s\nResidue IDs: %s" % (ChainID, LigandResName, LigandResNum, ResiduesDistribution, ResiduesIDs))
  402 
  403 def GetPocketSelectionResiduesInfo(MolName, ChainID, LigandResName, LigandResNum, PocketDistanceCutoff, SelectionType):
  404     """Get pocket residues info for a specified selection type."""
  405 
  406     SelectionInfo = None
  407     
  408     if re.match("^Pockets$", SelectionType, re.I):
  409         SelectionInfo = PyMOLUtil.GetPocketPolymerResiduesInfo(MolName, ChainID, LigandResName, LigandResNum, PocketDistanceCutoff)
  410     elif re.match("^PocketSolvents$", SelectionType, re.I):
  411         SelectionInfo = PyMOLUtil.GetPocketSolventResiduesInfo(MolName, ChainID, LigandResName, LigandResNum, PocketDistanceCutoff)
  412     elif re.match("^PocketInorganics$", SelectionType, re.I):
  413         SelectionInfo = PyMOLUtil.GetPocketInorganicResiduesInfo(MolName, ChainID, LigandResName, LigandResNum, PocketDistanceCutoff)
  414     else:
  415         MiscUtil.PrintError("Failed to retrieve pocket residues information: Selection type %s is not valid..." % SelectionType)
  416 
  417     return SelectionInfo
  418 
  419 def ListSurfaceResiduesInfo(MolName, ChainIDs):
  420     """List surface and buried residues for polymer chains with in a molecule."""
  421     
  422     if not (OptionsInfo["All"] or OptionsInfo["SurfaceResidues"]):
  423         return
  424 
  425     MiscUtil.PrintInfo("\nListing surface and buried residues information...")
  426     if not len(ChainIDs):
  427         MiscUtil.PrintInfo("\nNumber of surface residues: None\nNumber of buried residues: None")
  428         return
  429 
  430     TotalSurfaceResidues, TotalBuriedResidues = [0] * 2
  431     
  432     for ChainID in ChainIDs:
  433         SurfaceResiduesSelectionInfo, BuriedResiduesSelectionInfo = PyMOLUtil.GetSurfaceAndBuriedResiduesInfo(MolName, ChainID, OptionsInfo["SurfaceResiduesCutoff"])
  434         
  435         SurfaceResiduesCount, SurfaceResiduesDistribution, SurfaceResiduesIDs = SetupSelectionResiduesInfo(SurfaceResiduesSelectionInfo)
  436         BuriedResiduesCount, BuriedResiduesDistribution, BuriedResiduesIDs = SetupSelectionResiduesInfo(BuriedResiduesSelectionInfo)
  437 
  438         TotalSurfaceResidues += SurfaceResiduesCount
  439         MiscUtil.PrintInfo("\nChainID: %s; Number of surface residues: %d" % (ChainID, SurfaceResiduesCount))
  440         MiscUtil.PrintInfo("Residue distribution: %s" % (SurfaceResiduesDistribution))
  441         if OptionsInfo["SurfaceResiduesIDs"]:
  442             MiscUtil.PrintInfo("Residue IDs: %s" % (SurfaceResiduesIDs))
  443         
  444         TotalBuriedResidues += BuriedResiduesCount
  445         MiscUtil.PrintInfo("\nChainID: %s; Number of buried residues: %d" % (ChainID, BuriedResiduesCount))
  446         MiscUtil.PrintInfo("Residue distribution: %s" % (BuriedResiduesDistribution))
  447         if OptionsInfo["SurfaceResiduesIDs"]:
  448             MiscUtil.PrintInfo("Residue IDs: %s" % (BuriedResiduesIDs))
  449     
  450     MiscUtil.PrintInfo("\nTotal number of surface residues: %d\nTotal number of buried residues: %s" % (TotalSurfaceResidues, TotalBuriedResidues))
  451         
  452 def ListPhiPsiAnglesInfo(MolName, ChainIDs):
  453     """List phi and psi torsion angles for polymer chains with in a molecule."""
  454     
  455     if not (OptionsInfo["All"] or OptionsInfo["PhiPsi"]):
  456         return
  457 
  458     MiscUtil.PrintInfo("\nListing phi and psi angles information...")
  459     
  460     if not len(ChainIDs):
  461         MiscUtil.PrintInfo("\nNumber of phi and psi angles: None\n")
  462         return
  463 
  464     for ChainID in ChainIDs:
  465         if re.match("^Categories$", OptionsInfo["PhiPsiMode"], re.I):
  466             # Retrieve phi and psi angles by categories used for Ramachandran plots
  467             GeneralPhiPsiInfo, GlyPhiPsiInfo, ProPhiPsiInfo, PreProPhiPsiInfo = PyMOLUtil.GetPhiPsiCategoriesResiduesInfo(MolName, ChainID)
  468             
  469             SetupAndListPhiPsiResiduesInfo(GeneralPhiPsiInfo, "General (All residues except glycine, proline, or pre-proline)", ChainID)
  470             SetupAndListPhiPsiResiduesInfo(GlyPhiPsiInfo, "Glycine (Only glycine residues)", ChainID)
  471             SetupAndListPhiPsiResiduesInfo(ProPhiPsiInfo, "Proline (Only proline residues)", ChainID)
  472             SetupAndListPhiPsiResiduesInfo(PreProPhiPsiInfo, "Pre-Proline (Only residues before proline not including glycine or proline)", ChainID)
  473         else:
  474             PhiPsiResiduesInfo = PyMOLUtil.GetPhiPsiResiduesInfo(MolName, ChainID, Categorize = True)
  475             SetupAndListPhiPsiResiduesInfo(PhiPsiResiduesInfo, "All", ChainID)
  476 
  477 def SetupAndListPhiPsiResiduesInfo(PhiPsiResiduesInfo, AnglesType, ChainID):
  478     """Setup and  list information for phi and psi torsion angles."""
  479 
  480     MiscUtil.PrintInfo("\nChainID: %s; Phi and Psi angles: %s" %(ChainID, AnglesType))
  481 
  482     PhiPsiCount = len(PhiPsiResiduesInfo["ResNums"])
  483     if not PhiPsiCount:
  484         MiscUtil.PrintInfo("Number of angles: None")
  485         return
  486 
  487     MiscUtil.PrintInfo("Number of phi and psi angles: %d" % PhiPsiCount)
  488 
  489     CategoriesMode = True if re.match("^Categories$", OptionsInfo["PhiPsiMode"], re.I) else False
  490 
  491     if CategoriesMode:
  492         MiscUtil.PrintInfo("\nResNum, ResName, Phi, Psi")
  493     else:
  494         MiscUtil.PrintInfo("\nResNum, ResName, Phi, Psi, Category")
  495     
  496     Precision = OptionsInfo["PhiPsiPrecision"]
  497     for ResNum in PhiPsiResiduesInfo["ResNums"]:
  498         ResName = PhiPsiResiduesInfo["ResName"][ResNum]
  499         Phi = PhiPsiResiduesInfo["Phi"][ResNum]
  500         Psi = PhiPsiResiduesInfo["Psi"][ResNum]
  501         if CategoriesMode:
  502             MiscUtil.PrintInfo("%s, %s, %.*f, %.*f" % (ResNum, ResName, Precision, Phi, Precision, Psi))
  503         else:
  504             Category = PhiPsiResiduesInfo["Category"][ResNum]
  505             MiscUtil.PrintInfo("%s, %s, %.*f, %.*f, %s " % (ResNum, ResName, Precision, Phi, Precision, Psi, Category))
  506 
  507 def ListInterfaceResiduesInfo(MolName, ChainIDs):
  508     """List interface residues between all unique pairs of polymer chains
  509     with in a molecule."""
  510     
  511     if not (OptionsInfo["All"] or OptionsInfo["InterfaceResidues"]):
  512         return
  513 
  514     MiscUtil.PrintInfo("\nListing interface residues information...")
  515 
  516     # Get chain ID pairs for identifying interface residues...
  517     ChainIDsPairs = GetChainIDsPairsForInterfaceResidues(ChainIDs)
  518     if not len(ChainIDsPairs):
  519         MiscUtil.PrintInfo("Valid interface chain ID pairs: None")
  520         return
  521 
  522     InterfaceResiduesMethod = OptionsInfo["InterfaceResiduesMethod"]
  523     InterfaceResiduesCutoff = OptionsInfo["InterfaceResiduesCutoff"]
  524     MiscUtil.PrintInfo("Methodology: %s; Cutoff: %.2f" % (InterfaceResiduesMethod, InterfaceResiduesCutoff))
  525     
  526     for ChainIDsPair in ChainIDsPairs:
  527         InterfaceChainIDs1, InterfaceChainIDs2 = ChainIDsPair
  528         InterfaceID = "%s-%s" % ("+".join(InterfaceChainIDs1), "+".join(InterfaceChainIDs2))
  529 
  530         InterfaceResiduesInfo1, InterfaceResiduesInfo2 = GetInterfaceChainsAndResiduesInfo(MolName, InterfaceChainIDs1, MolName, InterfaceChainIDs2, InterfaceResiduesMethod, InterfaceResiduesCutoff)
  531 
  532         ChainNames1, ResiduesInfo1 = SetupSelectionChainsResiduesInfo(InterfaceResiduesInfo1)
  533         ChainNames2, ResiduesInfo2 = SetupSelectionChainsResiduesInfo(InterfaceResiduesInfo2)
  534         
  535         if len(ChainNames1) and len(ChainNames2):
  536             MiscUtil.PrintInfo("\nListing interface residues for interface chain IDs: %s" % (InterfaceID))
  537             
  538             ListInterfaceChainsAndResiduesInfo(InterfaceID,  InterfaceChainIDs1, ChainNames1, ResiduesInfo1)
  539             ListInterfaceChainsAndResiduesInfo(InterfaceID,  InterfaceChainIDs2, ChainNames2, ResiduesInfo2)
  540         else:
  541             MiscUtil.PrintInfo("\nListing interface residues for interface chain IDs: %s" % (InterfaceID))
  542             MiscUtil.PrintInfo("Interface chain IDs: None; ChainID: None; Number of interface residues: 0")
  543 
  544 def ListInterfaceChainsAndResiduesInfo(InterfaceID, InterfaceChainIDs, ChainNames, ResiduesInfo):
  545     """List interface chains and residues."""
  546 
  547     for ChainID in InterfaceChainIDs:
  548         if not ChainID in ChainNames:
  549             MiscUtil.PrintInfo("\nInterface chain IDs: %s; ChainID: %s; Number of interface residues: 0" % (InterfaceID, ChainID))
  550             continue
  551         
  552         for Index in range(0, len(ChainNames)):
  553             ChainName = ChainNames[Index]
  554             ChainResiduesInfo = ResiduesInfo[Index]
  555             if re.match(ChainID, ChainName, re.I):
  556                 MiscUtil.PrintInfo("\nInterface chain IDs: %s; ChainID: %s; Number of interface residues: %d" % (InterfaceID, ChainName, ChainResiduesInfo[0]))
  557                 MiscUtil.PrintInfo("Residue distribution: %s\nResidue IDs: %s" % (ChainResiduesInfo[1], ChainResiduesInfo[2]))
  558                 continue
  559 
  560 def GetInterfaceChainsAndResiduesInfo(MolName1, ChainIDs1, MolName2, ChainIDs2, Method, Cutoff):
  561     """Get interface chains and residues info for chains using a specified methodology."""
  562 
  563     InterfaceChainsResiduesInfo1 = None
  564     InterfaceChainsResiduesInfo2 = None
  565 
  566     ChainNames1 = ",".join(ChainIDs1)
  567     ChainNames2 = ",".join(ChainIDs2)
  568     
  569     if re.match("^BySASAChange$", Method, re.I):
  570         InterfaceChainsResiduesInfo1, InterfaceChainsResiduesInfo2 = PyMOLUtil.GetInterfaceChainsResiduesBySASAChange(MolName1, ChainNames1, MolName2, ChainNames2, Cutoff)
  571     elif re.match("^ByHeavyAtomsDistance$", Method, re.I):
  572         InterfaceChainsResiduesInfo1, InterfaceChainsResiduesInfo2 = PyMOLUtil.GetInterfaceChainsResiduesByHeavyAtomsDistance(MolName1, ChainNames1, MolName2, ChainNames2, Cutoff)
  573     elif re.match("^ByCAlphaAtomsDistance$", Method, re.I):
  574         InterfaceChainsResiduesInfo1, InterfaceChainsResiduesInfo2 = PyMOLUtil.GetInterfaceChainsResiduesByCAlphaAtomsDistance(MolName1, ChainNames1, MolName2, ChainNames2, Cutoff)
  575     else:
  576         MiscUtil.PrintError("Failed to retrieve interface residues information: Method %s is not valid..." % Method)
  577 
  578     return InterfaceChainsResiduesInfo1, InterfaceChainsResiduesInfo2
  579 
  580 def GetChainIDsPairsForInterfaceResidues(ChainIDs):
  581     """Get chain IDs pairs for identifying interface residues."""
  582 
  583     ChainIDsPairsList = []
  584     
  585     InterfaceResiduesChainsList = OptionsInfo["InterfaceResiduesChainsList"]
  586     if not len(InterfaceResiduesChainsList):
  587         # Use first two chain IDs...
  588         if len(ChainIDs) >= 2:
  589             ChainIDsPair = [ChainIDs[0], ChainIDs[1]]
  590             ChainIDsPairsList.append(ChainIDsPair)
  591         return ChainIDsPairsList
  592 
  593     # Validate specified pairwise chain IDs...
  594     for Index in range(0, len(InterfaceResiduesChainsList), 2):
  595         ChainIDs1 = InterfaceResiduesChainsList[Index]
  596         ChainIDs2 = InterfaceResiduesChainsList[Index + 1]
  597         
  598         ValidChainIDs = True
  599         SpecifiedChainIDs = []
  600         SpecifiedChainIDs.extend(ChainIDs1)
  601         SpecifiedChainIDs.extend(ChainIDs2)
  602         
  603         for ChainID in (SpecifiedChainIDs):
  604             if not ChainID in ChainIDs:
  605                 ValidChainIDs = False
  606                 MiscUtil.PrintWarning("The chain ID, %s, specified using \"--interfaceResiduesChains\" for a chain IDs pairs is not a valid chain ID." % (ChainID))
  607         
  608         if not ValidChainIDs:
  609             MiscUtil.PrintWarning("Ignoring chain IDs pair: %s, %s" % ("+".join(ChainIDs1), "+".join(ChainIDs2)))
  610             continue
  611         
  612         ChainIDsPair = [ChainIDs1, ChainIDs2]
  613         ChainIDsPairsList.append(ChainIDsPair)
  614 
  615     return ChainIDsPairsList
  616     
  617 def SetupSelectionResiduesInfo(SelectionInfo):
  618     """Setup residues info."""
  619     
  620     # Setup distribution of residues...
  621     LineWords = []
  622     ResiduesCount = 0
  623     SortedResNames = sorted(SelectionInfo["ResNames"], key = lambda ResName: SelectionInfo["ResCount"][ResName], reverse = True)
  624     for ResName in SortedResNames:
  625         ResCount = SelectionInfo["ResCount"][ResName]
  626         LineWords.append("%s - %s" % (ResName, ResCount))
  627         ResiduesCount += ResCount
  628     
  629     ResiduesDistribution = "; ".join(LineWords) if len(LineWords) else None
  630     
  631     # Setup residue IDs sorted by residue numbers...
  632     ResNumMap = {}
  633     for ResName in SelectionInfo["ResNames"]:
  634         for ResNum in SelectionInfo["ResNum"][ResName]:
  635             ResNumMap[ResNum] = ResName
  636     
  637     LineWords = []
  638     for ResNum in sorted(ResNumMap, key = int):
  639         ResName = ResNumMap[ResNum]
  640         ResID = "%s_%s" % (ResName, ResNum)
  641         LineWords.append(ResID)
  642     ResiduesIDs = ", ".join(LineWords) if len(LineWords) else None
  643                 
  644     return ResiduesCount, ResiduesDistribution, ResiduesIDs
  645 
  646 def SetupSelectionChainsResiduesInfo(SelectionInfo):
  647     """Setup chains and residues info."""
  648 
  649     ChainNames = []
  650     ResiduesInfo = []
  651 
  652     for ChainID in SelectionInfo["ChainIDs"]:
  653         ChainNames.append(ChainID)
  654         
  655         # Setup distribution of residues...
  656         LineWords = []
  657         ResiduesCount = 0
  658         SortedResNames = sorted(SelectionInfo["ResNames"][ChainID], key = lambda ResName: SelectionInfo["ResCount"][ChainID][ResName], reverse = True)
  659         for ResName in SortedResNames:
  660             ResCount = SelectionInfo["ResCount"][ChainID][ResName]
  661             LineWords.append("%s - %s" % (ResName, ResCount))
  662             ResiduesCount += ResCount
  663         
  664         ResiduesDistribution = "; ".join(LineWords) if len(LineWords) else None
  665         
  666         # Setup residue IDs sorted by residue numbers...
  667         ResNumMap = {}
  668         for ResName in SelectionInfo["ResNames"][ChainID]:
  669             for ResNum in SelectionInfo["ResNum"][ChainID][ResName]:
  670                 ResNumMap[ResNum] = ResName
  671         
  672         LineWords = []
  673         for ResNum in sorted(ResNumMap, key = int):
  674             ResName = ResNumMap[ResNum]
  675             ResID = "%s_%s" % (ResName, ResNum)
  676             LineWords.append(ResID)
  677         ResiduesIDs = ", ".join(LineWords) if len(LineWords) else None
  678 
  679         ResiduesInfo.append([ResiduesCount, ResiduesDistribution, ResiduesIDs])
  680                 
  681     return ChainNames, ResiduesInfo
  682 
  683 def ListBoundingBoxInfo(MolName):
  684     """List bounding box information."""
  685     
  686     if not (OptionsInfo["All"] or OptionsInfo["BoundingBox"]):
  687         return
  688 
  689     MolSelection = "(%s)" % MolName
  690     MolExtents = pymol.cmd.get_extent(MolSelection)
  691     
  692     XMin, YMin, ZMin = MolExtents[0]
  693     XMax, YMax, ZMax = MolExtents[1]
  694 
  695     XSize = abs(XMax - XMin)
  696     YSize = abs(YMax - YMin)
  697     ZSize = abs(ZMax - ZMin)
  698 
  699     MiscUtil.PrintInfo("\nBounding box coordinates: <XMin, XMax> - <%.3f, %.3f>; <YMin, YMax> - <%.3f, %.3f>; <ZMin, ZMax> - <%.3f, %.3f>" % (XMin, XMax, YMin, YMax, ZMin, ZMax))
  700     MiscUtil.PrintInfo("Bounding box size in Angstroms: XSize - %.3f; YSize - %.3f; ZSize - %.3f" % (XSize, YSize, ZSize))
  701     
  702 def ListCentroid(MolName):
  703     """List centroid information."""
  704     
  705     if not (OptionsInfo["All"] or OptionsInfo["Centroid"]):
  706         return
  707     
  708     MolSelection = "(%s)" % MolName
  709     Centroid = PyMOLUtil.GetCentroid(MolSelection)
  710 
  711     MiscUtil.PrintInfo("\nCentroid: <%.3f, %.3f, %.3f>" % (Centroid[0], Centroid[1], Centroid[2]))
  712 
  713 def ListFileSizeAndModificationInfo(Infile):
  714     """List file size and modification time info."""
  715 
  716     MiscUtil.PrintInfo("\nFile size: %s" % MiscUtil.GetFormattedFileSize(Infile))
  717     MiscUtil.PrintInfo("Last modified: %s" % time.ctime(os.path.getmtime(Infile)))
  718     MiscUtil.PrintInfo("Created: %s" % time.ctime(os.path.getctime(Infile)))
  719     
  720 def GetPocketSelectionResiduesInfoLabel(SelectionType):
  721     """Get pocket residues info label for a specified selection type."""
  722 
  723     SelectionLabel = None
  724     
  725     if re.match("^Pockets$", SelectionType, re.I):
  726         SelectionLabel = "polymer"
  727     elif re.match("^PocketSolvents$", SelectionType, re.I):
  728         SelectionLabel = "solvent"
  729     elif re.match("^PocketInorganics$", SelectionType, re.I):
  730         SelectionLabel = "inorganic"
  731     else:
  732         MiscUtil.PrintError("Failed to retrieve pocket residues label information: Selection type %s is not valid..." % SelectionType)
  733 
  734     return SelectionLabel
  735 
  736 def ProcessOptions():
  737     """Process and validate command line arguments and options."""
  738 
  739     MiscUtil.PrintInfo("Processing options...")
  740     
  741     # Validate options...
  742     ValidateOptions()
  743 
  744     OptionsInfo["All"] = Options["--all"]
  745     OptionsInfo["BoundingBox"] = Options["--boundingBox"]
  746     OptionsInfo["Centroid"] = Options["--centroid"]
  747     
  748     OptionsInfo["Chains"] = Options["--chains"]
  749     
  750     OptionsInfo["CountResidues"] = Options["--countResidues"]
  751     OptionsInfo["Header"] = Options["--header"]
  752     
  753     OptionsInfo["Infiles"] = Options["--infiles"]
  754     OptionsInfo["InfilesNames"] =  Options["--infilesNames"]
  755     
  756     OptionsInfo["Inorganics"] = Options["--inorganics"]
  757 
  758     OptionsInfo["InterfaceResidues"] = Options["--interfaceResidues"]
  759     OptionsInfo["InterfaceResiduesMethod"] = Options["--interfaceResiduesMethod"]
  760     
  761     InterfaceResiduesChains = Options["--interfaceResiduesChains"]
  762     InterfaceResiduesChainsList = []
  763     if not re.match("^auto$", InterfaceResiduesChains, re.I):
  764         InterfaceResiduesChains = re.sub(" ", "", Options["--interfaceResiduesChains"])
  765         InterfaceResiduesChainsWords = InterfaceResiduesChains.split(",")
  766         if len(InterfaceResiduesChainsWords) % 2:
  767             MiscUtil.PrintError("The number of comma delimited chain IDs, %d, specified using \"--interfaceResiduesChains\" option, \"%s\",  must be a multple of 2." % (len(InterfaceResiduesChainsWords), Options["--interfaceResiduesChains"]))
  768         for ChainID in InterfaceResiduesChainsWords:
  769             ChainIDWords = ChainID.split("+")
  770             InterfaceResiduesChainsList.append(ChainIDWords)
  771     OptionsInfo["InterfaceResiduesChains"] = InterfaceResiduesChains
  772     OptionsInfo["InterfaceResiduesChainsList"] = InterfaceResiduesChainsList
  773     
  774     InterfaceResiduesCutoff = Options["--interfaceResiduesCutoff"]
  775     InterfaceResiduesMethod = OptionsInfo["InterfaceResiduesMethod"]
  776     if re.match("^auto$", InterfaceResiduesCutoff, re.I):
  777         if re.match("^BySASAChange$", InterfaceResiduesMethod, re.I):
  778             InterfaceResiduesCutoff = 1.0
  779         elif re.match("^ByHeavyAtomsDistance$", InterfaceResiduesMethod, re.I):
  780             InterfaceResiduesCutoff = 5.0
  781         elif re.match("^ByCAlphaAtomsDistance$", InterfaceResiduesMethod, re.I):
  782             InterfaceResiduesCutoff = 8.0
  783         else:
  784             MiscUtil.PrintError("The specified value, %s, for option \"--interfaceResiduesMethod\" is not supported." % (InterfaceResiduesMethod))
  785     else:
  786         InterfaceResiduesCutoff = float(InterfaceResiduesCutoff)
  787     OptionsInfo["InterfaceResiduesCutoff"] = InterfaceResiduesCutoff
  788     
  789     OptionsInfo["Ligands"] = Options["--ligands"]
  790     
  791     OptionsInfo["PocketLigands"] = Options["--pocketLigands"]
  792     OptionsInfo["PocketDistanceCutoff"] = float(Options["--pocketDistanceCutoff"])
  793     OptionsInfo["PocketSolvents"] = Options["--pocketSolvents"]
  794     OptionsInfo["PocketInorganics"] = Options["--pocketInorganics"]
  795     
  796     OptionsInfo["PhiPsi"] = Options["--phiPsi"]
  797     OptionsInfo["PhiPsiMode"] = Options["--phiPsiMode"]
  798     OptionsInfo["PhiPsiPrecision"] = int(Options["--phiPsiPrecision"])
  799     
  800     OptionsInfo["Solvents"] = Options["--solvents"]
  801     
  802     OptionsInfo["SurfaceResidues"] = Options["--surfaceResidues"]
  803     OptionsInfo["SurfaceResiduesCutoff"] = float(Options["--surfaceResiduesCutoff"])
  804     OptionsInfo["SurfaceResiduesIDs"] = True if re.match("^Yes$", Options["--surfaceResiduesIDs"], re.I) else False
  805 
  806     # Always list header, chains, and ligands...
  807     OptionNames = ["Chains", "Header", "Ligands"]
  808     for Name in OptionNames:
  809         if Name in OptionsInfo:
  810             OptionsInfo[Name] = True
  811         else:
  812             MiscUtil.PrintError("Option name %s is not a valid name..." % s)
  813     
  814 def RetrieveOptions(): 
  815     """Retrieve command line arguments and options."""
  816     
  817     # Get options...
  818     global Options
  819     Options = docopt(_docoptUsage_)
  820 
  821     # Set current working directory to the specified directory...
  822     WorkingDir = Options["--workingdir"]
  823     if WorkingDir:
  824         os.chdir(WorkingDir)
  825     
  826     # Handle examples option...
  827     if "--examples" in Options and Options["--examples"]:
  828         MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
  829         sys.exit(0)
  830     
  831 def ValidateOptions():
  832     """Validate option values."""
  833 
  834     # Expand infile names..
  835     InfilesNames = MiscUtil.ExpandFileNames(Options["--infiles"], ",")
  836 
  837     # Validate file extensions...
  838     for Infile in InfilesNames:
  839         MiscUtil.ValidateOptionFilePath("-i, --infiles", Infile)
  840         MiscUtil.ValidateOptionFileExt("-i, --infiles", Infile, "pdb cif")
  841     Options["--infilesNames"] = InfilesNames
  842     
  843     if not re.match("^auto$", Options["--interfaceResiduesCutoff"], re.I):
  844         MiscUtil.ValidateOptionFloatValue("--interfaceResiduesCutoff", Options["--interfaceResiduesCutoff"], {">": 0.0})
  845     MiscUtil.ValidateOptionTextValue("--interfaceResiduesMethod", Options["--interfaceResiduesMethod"], "BySASAChange ByHeavyAtomsDistance ByCAlphaAtomsDistance")
  846     
  847     MiscUtil.ValidateOptionFloatValue("--pocketDistanceCutoff", Options["--pocketDistanceCutoff"], {">": 0.0})
  848     MiscUtil.ValidateOptionTextValue("--phiPsiMode", Options["--phiPsiMode"], "All  Categories")
  849     MiscUtil.ValidateOptionIntegerValue("--phiPsiPrecision", Options["--phiPsiPrecision"], {">": 0})
  850     MiscUtil.ValidateOptionFloatValue("--surfaceResiduesCutoff", Options["--surfaceResiduesCutoff"], {">": 0.0})
  851     MiscUtil.ValidateOptionTextValue("--surfaceResiduesIDs", Options["--surfaceResiduesIDs"], "Yes No")
  852 
  853 # Setup a usage string for docopt...
  854 _docoptUsage_ = """
  855 PyMOLInfoMacromolecules.py - List information about macromolecules
  856 
  857 Usage:
  858     PyMOLInfoMacromolecules.py [--all] [--boundingBox] [--centroid] [--chains]
  859                                [--countResidues] [--header] [--inorganics] [--interfaceResidues]
  860                                [--interfaceResiduesChains <ChainID1,ChainD2,...>] [--interfaceResiduesMethod <text>]
  861                                [--interfaceResiduesCutoff <number>] [--ligands] [--pocketLigands]
  862                                [--pocketDistanceCutoff  <number>] [--pocketSolvents] [--pocketInorganics]
  863                                [--phiPsi] [--phiPsiMode <All or Categories>] [--phiPsiPrecision <number>]
  864                                [--surfaceResidues] [--surfaceResiduesCutoff <number>] [--surfaceResiduesIDs <yes or no>]
  865                                [--solvents] [-w <dir>] -i <infile1,infile2,infile3...>
  866     PyMOLInfoMacromolecules.py -h | --help | -e | --examples
  867 
  868 Description:
  869     List information regarding  ID, classification, experimental technique, chains,
  870     solvents, inorganics, ligands, and ligand binding pockets in macromolecules
  871     present including proteins and nucleic acids.
  872 
  873     The supported input  file format are: PDB (.pdb), mmCIF (.cif)
  874 
  875 Options:
  876     -a, --all
  877         All available information.
  878     -b, --boundingBox
  879         Min and max coordinates for bounding box along with its size.
  880     -c, --chains
  881         Number of chains and their IDs. This is also default behavior.
  882      --centroid
  883         Centroid of atomic coordinates.  It corresponds to the mean of all 3D
  884         coordinates in input file.
  885      --countResidues
  886         Number of residues across chains. The chain residues are identified
  887         using polymer selection operator available in PyMOL. In addition,
  888         the non-standard amino acid residues are listed.
  889     -e, --examples
  890         Print examples.
  891     -h, --help
  892         Print this help message.
  893      --header
  894         Header information including experimental technique information
  895         along with any available resolution. This is also default behavior.
  896     -i, --infiles <infile1,infile2,infile3...>
  897         A comma delimited list of input files. The wildcards are also allowed
  898         in file names.
  899     --inorganics
  900         Inorganic residues across chains. The inorganic residues are identified
  901         using inorganic selection operator available in PyMOL.
  902     --interfaceResidues
  903         Interface residues between specified pairs of chains.
  904     --interfaceResiduesChains <ChainID1,Chain1D2,...>  [default: Auto]
  905         Pairwise comma delimited list of chain IDs for the identification of
  906         interface residues. Each chain ID may contain mutiple chain IDs
  907         delimited by a plus sign. For example: A+B,C+D chain pair specifies
  908         interface between chain complexes A+B and C+D.
  909         
  910         The interface residues are identified between first two chains in
  911         input files by default.
  912     --interfaceResiduesMethod <text>  [default: BySASAChange]
  913         Methodology for the identification of interface residues between a pair
  914         of chains in an input file. The interface residues may be identified by
  915         change in solvent accessible surface area (SASA) for a residue between
  916         a chain and chains complex, distance between heavy atoms
  917         in two chains, or distance between CAlpha atoms. Possible values:
  918         BySASAChange, ByHeavyAtomsDistance, or ByCAlphaAtomsDistance. 
  919     --interfaceResiduesCutoff <number>  [default: auto]
  920         Cutoff value used by different methodologies during identification of
  921         interface residues between a pair of chains. The default values are
  922         shown below:
  923         
  924             BySASAChange: 1.0; Units: Angstrom**2 [ Ref 141 ]
  925             ByHeavyAtomsDistance: 5.0; Units: Angstrom [ Ref 142 ]
  926             ByCAlphaAtomsDistance: 8.0; Units: Angstrom [ Ref 143 ]
  927         
  928     -l, --ligands
  929         Ligands across chains. This is also default behavior. The ligands
  930         residues are identified using organic selection operator available
  931         in PyMOL.
  932     -p, --pocketLigands
  933         Chain residues in ligand pockets.
  934     --pocketDistanceCutoff <number>  [default: 5.0]
  935         Distance in Angstroms for identifying pocket residues around ligands.
  936     --pocketSolvents
  937         Solvent residues in ligand pockets. The solvent residues are identified
  938         using solvent selection operator available in PyMOL.
  939     --pocketInorganics
  940         Inorganic residues in ligand pockets. The inorganic residues are identified
  941         using Inorganic selection operator available in PyMOL.
  942     --phiPsi
  943         Phi and psi torsion angles across chains in macromolecules containing
  944         amino acids.
  945     --phiPsiMode <All or Categories>  [default: Categories]
  946         List all phi and psi torsion angles for residues as a single group or split
  947         them into the following categories corresponding to four types of
  948         Ramachandran plots:
  949         
  950             General: All residues except glycine, proline, or pre-proline
  951             Glycine: Only glycine residues
  952             Proline: Only proline residues
  953             Pre-Proline: Only residues before proline not including glycine
  954                 or proline
  955         
  956     --phiPsiPrecision <number>  [default: 2]
  957         Precision for listing phi and psi torsion angles.
  958     -s, --solvents
  959         Solvent residues across chains. The solvent residues are identified
  960         using solvent selection operator available in PyMOL.
  961     --surfaceResidues
  962         Surface and buried residues in chains.
  963     --surfaceResiduesCutoff <number>  [default: 2.5]
  964         Solvenet Accessible Surface Area (SASA) cutoff value in Angstroms**2
  965         for surface and buried resiudes in chains. The residues with SASA less than
  966         the cutoff value correspond to burried residues.
  967     --surfaceResiduesIDs <yes or no>  [default: No]
  968         List residue IDs for surface and buried residues during listing of the
  969         distribution of these residues for '--surfaceResidues' option.
  970     -w, --workingdir <dir>
  971         Location of working directory which defaults to the current directory.
  972 
  973 Examples:
  974     To list header, chains, and ligand information for macromolecules in input
  975     file, type:
  976 
  977         % PyMOLInfoMacromolecules.py  -i Sample3.pdb
  978 
  979     To list all available information for macromolecules in input files, type:
  980 
  981         % PyMOLInfoMacromolecules.py  -a  -i "Sample3.pdb,Sample4.pdb"
  982 
  983     To list pockets residues information along with other default information
  984     for marcomolecules in input file, type:
  985 
  986         % PyMOLInfoMacromolecules.py  -p --pocketDistanceCutoff 4.5 
  987         --pocketSolvents  --pocketInorganics -i Sample3.pdb
  988 
  989     To list chain residues information along with other default information
  990     for marcomolecules in input file, type:
  991 
  992         % PyMOLInfoMacromolecules.py  -c --countResidues --solvents
  993         --inorganics -i "Sample3.pdb,Sample4.pdb" 
  994 
  995     To list interface residues between first two chains by SASA change for
  996     marcomolecules in input file, type:
  997 
  998         % PyMOLInfoMacromolecules.py  --interfaceResidues
  999         -i Sample3.pdb
 1000 
 1001     To list interface residues between chains E and I  by heay atoms
 1002     distance for marcomolecules in input file, type:
 1003 
 1004         % PyMOLInfoMacromolecules.py  --interfaceResidues
 1005         --interfaceResiduesChains E,I  --interfaceResiduesMethod
 1006         ByHeavyAtomsDistance --interfaceResiduesCutoff  5 -i Sample3.pdb
 1007 
 1008     To list interface residues between two sets of chains by SASA change for
 1009     marcomolecules in input file, type:
 1010 
 1011         % PyMOLInfoMacromolecules.py  --interfaceResidues
 1012         --interfaceResiduesChains "A+B,C+D" -i Sample8.pdb 
 1013 
 1014 Author:
 1015     Manish Sud(msud@san.rr.com)
 1016 
 1017 See also:
 1018     DownloadPDBFiles.pl, PyMOLSplitChainsAndLigands.py,
 1019     PyMOLVisualizeMacromolecules.py
 1020 
 1021 Copyright:
 1022     Copyright (C) 2024 Manish Sud. All rights reserved.
 1023 
 1024     The functionality available in this script is implemented using PyMOL, a
 1025     molecular visualization system on an open source foundation originally
 1026     developed by Warren DeLano.
 1027 
 1028     This file is part of MayaChemTools.
 1029 
 1030     MayaChemTools is free software; you can redistribute it and/or modify it under
 1031     the terms of the GNU Lesser General Public License as published by the Free
 1032     Software Foundation; either version 3 of the License, or (at your option) any
 1033     later version.
 1034 
 1035 """
 1036 
 1037 if __name__ == "__main__":
 1038     main()