MayaChemTools

    1 #!/bin/env python
    2 #
    3 # File: Psi4VisualizeElectrostaticPotential.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 Psi4, an
    9 # open source quantum chemistry software package, and RDKit, an open
   10 # source toolkit for cheminformatics developed by Greg Landrum.
   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 import glob
   38 import shutil
   39 import multiprocessing as mp
   40 
   41 # Psi4 imports...
   42 if (hasattr(shutil, 'which') and shutil.which("psi4") is None):
   43     sys.stderr.write("\nWarning: Failed to find 'psi4' in your PATH indicating potential issues with your\n")
   44     sys.stderr.write("Psi4 environment. The 'import psi4' directive in the global scope of the script\n")
   45     sys.stderr.write("interferes with the multiprocessing functionality. It is imported later in the\n")
   46     sys.stderr.write("local scope during the execution of the script and may fail. Check/update your\n")
   47     sys.stderr.write("Psi4 environment and try again.\n\n")
   48 
   49 # RDKit imports...
   50 try:
   51     from rdkit import rdBase
   52     from rdkit import Chem
   53     from rdkit.Chem import AllChem
   54 except ImportError as ErrMsg:
   55     sys.stderr.write("\nFailed to import RDKit module/package: %s\n" % ErrMsg)
   56     sys.stderr.write("Check/update your RDKit environment and try again.\n\n")
   57     sys.exit(1)
   58 
   59 # MayaChemTools imports...
   60 try:
   61     from docopt import docopt
   62     import MiscUtil
   63     import Psi4Util
   64     import RDKitUtil
   65 except ImportError as ErrMsg:
   66     sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg)
   67     sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n")
   68     sys.exit(1)
   69 
   70 ScriptName = os.path.basename(sys.argv[0])
   71 Options = {}
   72 OptionsInfo = {}
   73 
   74 def main():
   75     """Start execution of the script."""
   76     
   77     MiscUtil.PrintInfo("\n%s (Psi4: Imported later; RDKit v%s; MayaChemTools v%s; %s): Starting...\n" % (ScriptName, rdBase.rdkitVersion, MiscUtil.GetMayaChemToolsVersion(), time.asctime()))
   78     
   79     (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
   80     
   81     # Retrieve command line arguments and options...
   82     RetrieveOptions()
   83     
   84     # Process and validate command line arguments and options...
   85     ProcessOptions()
   86     
   87     # Perform actions required by the script...
   88     GenerateAndVisualizeElectrostatisPotential()
   89     
   90     MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
   91     MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
   92 
   93 def GenerateAndVisualizeElectrostatisPotential():
   94     """Generate and visualize electrostatic potential."""
   95     
   96     if OptionsInfo["GenerateCubeFiles"]:
   97         GenerateElectrostaticPotential()
   98         
   99     if OptionsInfo["VisualizeCubeFiles"]:
  100         VisualizeElectrostaticPotential()
  101 
  102 def GenerateElectrostaticPotential():
  103     """Generate cube files for electrostatic potential."""
  104     
  105     # Setup a molecule reader...
  106     MiscUtil.PrintInfo("\nProcessing file %s..." % OptionsInfo["Infile"])
  107     Mols  = RDKitUtil.ReadMolecules(OptionsInfo["InfilePath"], **OptionsInfo["InfileParams"])
  108     
  109     MolCount, ValidMolCount, CubeFilesFailedCount = ProcessMolecules(Mols)
  110     
  111     MiscUtil.PrintInfo("\nTotal number of molecules: %d" % MolCount)
  112     MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
  113     MiscUtil.PrintInfo("Number of molecules failed during generation of cube files: %d" % CubeFilesFailedCount)
  114     MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount + CubeFilesFailedCount))
  115 
  116 def ProcessMolecules(Mols):
  117     """Process and generate ESP cube files for molecules."""
  118 
  119     if OptionsInfo["MPMode"]:
  120         return ProcessMoleculesUsingMultipleProcesses(Mols)
  121     else:
  122         return ProcessMoleculesUsingSingleProcess(Mols)
  123 
  124 def ProcessMoleculesUsingSingleProcess(Mols):
  125     """Process and generate ESP cube files for molecules using a single process."""
  126     
  127     # Intialize Psi4...
  128     MiscUtil.PrintInfo("\nInitializing Psi4...")
  129     Psi4Handle = Psi4Util.InitializePsi4(Psi4RunParams = OptionsInfo["Psi4RunParams"], Psi4OptionsParams = OptionsInfo["Psi4OptionsParams"], PrintVersion = True, PrintHeader = True)
  130     OptionsInfo["psi4"] = Psi4Handle
  131 
  132     MiscUtil.PrintInfo("\nGenerating cube files for electrostatic potential...")
  133     
  134     (MolCount, ValidMolCount, CubeFilesFailedCount) = [0] * 3
  135     for Mol in Mols:
  136         MolCount += 1
  137         
  138         if not CheckAndValidateMolecule(Mol, MolCount):
  139             continue
  140         
  141         # Setup a Psi4 molecule...
  142         Psi4Mol = SetupPsi4Mol(Psi4Handle, Mol, MolCount)
  143         if Psi4Mol is None:
  144             continue
  145         
  146         ValidMolCount += 1
  147         
  148         CalcStatus = GenerateMolCubeFiles(Psi4Handle, Psi4Mol, Mol, MolCount)
  149 
  150         if not CalcStatus:
  151             if not OptionsInfo["QuietMode"]:
  152                 MiscUtil.PrintWarning("Failed to generate cube files for molecule %s" % RDKitUtil.GetMolName(Mol, MolCount))
  153             
  154             CubeFilesFailedCount += 1
  155             continue
  156         
  157     return (MolCount, ValidMolCount, CubeFilesFailedCount)
  158     
  159 def ProcessMoleculesUsingMultipleProcesses(Mols):
  160     """Process and generate ESP cube files for molecules using  multiple processes."""
  161 
  162     MiscUtil.PrintInfo("\nGenerating electrostatic potential using multiprocessing...")
  163     
  164     MPParams = OptionsInfo["MPParams"]
  165     
  166     # Setup data for initializing a worker process...
  167     InitializeWorkerProcessArgs = (MiscUtil.ObjectToBase64EncodedString(Options), MiscUtil.ObjectToBase64EncodedString(OptionsInfo))
  168     
  169     # Setup a encoded mols data iterable for a worker process...
  170     WorkerProcessDataIterable = RDKitUtil.GenerateBase64EncodedMolStrings(Mols)
  171     
  172     # Setup process pool along with data initialization for each process...
  173     if not OptionsInfo["QuietMode"]:
  174         MiscUtil.PrintInfo("\nConfiguring multiprocessing using %s method..." % ("mp.Pool.imap()" if re.match("^Lazy$", MPParams["InputDataMode"], re.I) else "mp.Pool.map()"))
  175         MiscUtil.PrintInfo("NumProcesses: %s; InputDataMode: %s; ChunkSize: %s\n" % (MPParams["NumProcesses"], MPParams["InputDataMode"], ("automatic" if MPParams["ChunkSize"] is None else MPParams["ChunkSize"])))
  176     
  177     ProcessPool = mp.Pool(MPParams["NumProcesses"], InitializeWorkerProcess, InitializeWorkerProcessArgs)
  178     
  179     # Start processing...
  180     if re.match("^Lazy$", MPParams["InputDataMode"], re.I):
  181         Results = ProcessPool.imap(WorkerProcess, WorkerProcessDataIterable, MPParams["ChunkSize"])
  182     elif re.match("^InMemory$", MPParams["InputDataMode"], re.I):
  183         Results = ProcessPool.map(WorkerProcess, WorkerProcessDataIterable, MPParams["ChunkSize"])
  184     else:
  185         MiscUtil.PrintError("The value, %s, specified for \"--inputDataMode\" is not supported." % (MPParams["InputDataMode"]))
  186 
  187     # Print out Psi4 version in the main process...
  188     MiscUtil.PrintInfo("\nInitializing Psi4...\n")
  189     Psi4Handle  = Psi4Util.InitializePsi4(PrintVersion = True, PrintHeader = False)
  190     OptionsInfo["psi4"] = Psi4Handle
  191     
  192     (MolCount, ValidMolCount, CubeFilesFailedCount) = [0] * 3
  193     for Result in Results:
  194         MolCount += 1
  195         MolIndex, EncodedMol, CalcStatus = Result
  196         
  197         if EncodedMol is None:
  198             continue
  199         
  200         ValidMolCount += 1
  201 
  202         Mol = RDKitUtil.MolFromBase64EncodedMolString(EncodedMol)
  203         
  204         if not CalcStatus:
  205             if not OptionsInfo["QuietMode"]:
  206                 MiscUtil.PrintWarning("Failed to generate cube files for molecule %s" % RDKitUtil.GetMolName(Mol, MolCount))
  207             
  208             CubeFilesFailedCount += 1
  209             continue
  210         
  211     return (MolCount, ValidMolCount, CubeFilesFailedCount)
  212 
  213 def InitializeWorkerProcess(*EncodedArgs):
  214     """Initialize data for a worker process."""
  215     
  216     global Options, OptionsInfo
  217     
  218     if not OptionsInfo["QuietMode"]:
  219         MiscUtil.PrintInfo("Starting process (PID: %s)..." % os.getpid())
  220     
  221     # Decode Options and OptionInfo...
  222     Options = MiscUtil.ObjectFromBase64EncodedString(EncodedArgs[0])
  223     OptionsInfo = MiscUtil.ObjectFromBase64EncodedString(EncodedArgs[1])
  224 
  225     # Psi4 is initialized in the worker process to avoid creation of redundant Psi4
  226     # output files for each process...
  227     OptionsInfo["Psi4Initialized"]  = False
  228 
  229 def InitializePsi4ForWorkerProcess():
  230     """Initialize Psi4 for a worker process."""
  231     
  232     if OptionsInfo["Psi4Initialized"]:
  233         return
  234 
  235     OptionsInfo["Psi4Initialized"] = True
  236     
  237     # Update output file...
  238     OptionsInfo["Psi4RunParams"]["OutputFile"] = Psi4Util.UpdatePsi4OutputFileUsingPID(OptionsInfo["Psi4RunParams"]["OutputFile"], os.getpid())
  239     
  240     # Intialize Psi4...
  241     OptionsInfo["psi4"] = Psi4Util.InitializePsi4(Psi4RunParams = OptionsInfo["Psi4RunParams"], Psi4OptionsParams = OptionsInfo["Psi4OptionsParams"], PrintVersion = False, PrintHeader = True)
  242     
  243 def WorkerProcess(EncodedMolInfo):
  244     """Process data for a worker process."""
  245     
  246     if not OptionsInfo["Psi4Initialized"]:
  247         InitializePsi4ForWorkerProcess()
  248     
  249     MolIndex, EncodedMol = EncodedMolInfo
  250     
  251     CalcStatus = False
  252     
  253     if EncodedMol is None:
  254         return [MolIndex, None, CalcStatus]
  255     
  256     Mol = RDKitUtil.MolFromBase64EncodedMolString(EncodedMol)
  257     MolCount = MolIndex + 1
  258     
  259     if not CheckAndValidateMolecule(Mol, MolCount):
  260         return [MolIndex, None, CalcStatus]
  261     
  262     # Setup a Psi4 molecule...
  263     Psi4Mol = SetupPsi4Mol(OptionsInfo["psi4"], Mol, MolCount)
  264     if Psi4Mol is None:
  265         return [MolIndex, None, CalcStatus]
  266     
  267     CalcStatus = GenerateMolCubeFiles(OptionsInfo["psi4"], Psi4Mol, Mol, MolCount)
  268     
  269     return [MolIndex, RDKitUtil.MolToBase64EncodedMolString(Mol, PropertyPickleFlags = Chem.PropertyPickleOptions.MolProps | Chem.PropertyPickleOptions.PrivateProps), CalcStatus]
  270 
  271 def GenerateMolCubeFiles(Psi4Handle, Psi4Mol, Mol, MolNum = None):
  272     """Generate cube files for electrostatic potential."""
  273     
  274     #  Setup reference wave function...
  275     Reference = SetupReferenceWavefunction(Mol)
  276     Psi4Handle.set_options({'Reference': Reference})
  277     
  278     # Setup method name and basis set...
  279     MethodName, BasisSet = SetupMethodNameAndBasisSet(Mol)
  280     
  281     # Calculate single point energy to setup a wavefunction...
  282     Status, Energy, WaveFunction = Psi4Util.CalculateSinglePointEnergy(Psi4Handle, Psi4Mol, MethodName, BasisSet, ReturnWaveFunction = True, Quiet = OptionsInfo["QuietMode"])
  283 
  284     if not Status:
  285         PerformPsi4Cleanup(Psi4Handle)
  286         return (False)
  287     
  288     # Generate cube files...
  289     Status = GenerateCubeFilesForElectrostaticPotential(Psi4Handle, WaveFunction, Mol, MolNum)
  290     
  291     # Clean up
  292     PerformPsi4Cleanup(Psi4Handle)
  293 
  294     return (True)
  295 
  296 def GenerateCubeFilesForElectrostaticPotential(Psi4Handle, WaveFunction, Mol, MolNum):
  297     """Generate cube files for electrostatic potential."""
  298     
  299     # Setup a temporary local directory to generate cube files...
  300     Status, CubeFilesDir, CubeFilesDirPath = SetupMolCubeFilesDir(Mol, MolNum)
  301     if not Status:
  302         return False
  303     
  304     # Generate cube files using psi4.cubeprop...
  305     Status, Psi4CubeFiles = GenerateCubeFilesUsingCubeprop(Psi4Handle, WaveFunction, Mol, MolNum, CubeFilesDir, CubeFilesDirPath)
  306     if not Status:
  307         return False
  308 
  309     # Copy and rename cube files...
  310     if not MoveAndRenameCubeFiles(Mol, MolNum, Psi4CubeFiles):
  311         return False
  312 
  313     # Delete temporary local directory...
  314     if os.path.isdir(CubeFilesDir):
  315         shutil.rmtree(CubeFilesDir)
  316     
  317     if not GenerateMolFile(Mol, MolNum):
  318         return False
  319     
  320     return True
  321 
  322 def SetupMolCubeFilesDir(Mol, MolNum):
  323     """Setup a directory for generating cube files for a molecule."""
  324     
  325     MolPrefix = SetupMolPrefix(Mol, MolNum)
  326 
  327     CubeFilesDir = "%s_CubeFiles" % (MolPrefix)
  328     CubeFilesDirPath = os.path.join(os.getcwd(), CubeFilesDir)
  329     try:
  330         if os.path.isdir(CubeFilesDir):
  331             shutil.rmtree(CubeFilesDir)
  332         os.mkdir(CubeFilesDir)
  333     except Exception as ErrMsg:
  334         if not OptionsInfo["QuietMode"]:
  335             MiscUtil.PrintWarning("Failed to create cube files: %s\n" % ErrMsg)
  336             MiscUtil.PrintWarning("Ignoring molecule: %s" % RDKitUtil.GetMolName(Mol, MolNum))
  337         return (False, None, None)
  338         
  339     return (True, CubeFilesDir, CubeFilesDirPath)
  340 
  341 def GenerateCubeFilesUsingCubeprop(Psi4Handle, WaveFunction, Mol, MolNum, CubeFilesDir, CubeFilesDirPath):
  342     """Generate cube files using cubeprop."""
  343     
  344     Psi4CubeFilesParams = OptionsInfo["Psi4CubeFilesParams"]
  345 
  346     # Generate cube files...
  347     GridSpacing = Psi4CubeFilesParams["GridSpacing"]
  348     GridOverage = Psi4CubeFilesParams["GridOverage"]
  349     IsoContourThreshold = Psi4CubeFilesParams["IsoContourThreshold"]
  350     try:
  351         Psi4Handle.set_options({"CUBEPROP_TASKS": ['ESP'],
  352                                 "CUBIC_GRID_SPACING": [GridSpacing, GridSpacing, GridSpacing],
  353                                 "CUBIC_GRID_OVERAGE": [GridOverage, GridOverage, GridOverage],
  354                                 "CUBEPROP_FILEPATH": CubeFilesDirPath,
  355                                 "CUBEPROP_ISOCONTOUR_THRESHOLD": IsoContourThreshold})
  356         Psi4Handle.cubeprop(WaveFunction)
  357     except Exception as ErrMsg:
  358         shutil.rmtree(CubeFilesDir)
  359         if not OptionsInfo["QuietMode"]:
  360             MiscUtil.PrintWarning("Failed to create cube files: %s\n" % ErrMsg)
  361             MiscUtil.PrintWarning("Ignoring molecule: %s" % RDKitUtil.GetMolName(Mol, MolNum))
  362         return (False, None)
  363 
  364     # Collect cube files...
  365     Psi4CubeFiles = glob.glob(os.path.join(CubeFilesDir, "*.cube"))
  366     if len(Psi4CubeFiles) == 0:
  367         shutil.rmtree(CubeFilesDir)
  368         if not OptionsInfo["QuietMode"]:
  369             MiscUtil.PrintWarning("Failed to create cube files for electrostatic potential...")
  370             MiscUtil.PrintWarning("Ignoring molecule: %s" % RDKitUtil.GetMolName(Mol, MolNum))
  371         return (False, None)
  372     
  373     return (True, Psi4CubeFiles)
  374 
  375 def MoveAndRenameCubeFiles(Mol, MolNum, Psi4CubeFiles):
  376     """Move and rename cube files."""
  377 
  378     for Psi4CubeFileName in Psi4CubeFiles:
  379         try:
  380             NewCubeFileName = SetupMolCubeFileName(Mol, MolNum, Psi4CubeFileName)
  381             shutil.move(Psi4CubeFileName, NewCubeFileName)
  382         except Exception as ErrMsg:
  383             if not OptionsInfo["QuietMode"]:
  384                 MiscUtil.PrintWarning("Failed to move cube file: %s\n" % ErrMsg)
  385                 MiscUtil.PrintWarning("Ignoring molecule: %s" % RDKitUtil.GetMolName(Mol, MolNum))
  386             return False
  387     
  388     return True
  389 
  390 def GenerateMolFile(Mol, MolNum):
  391     """Generate a SD file for molecules corresponding to cube files."""
  392 
  393     Outfile = SetupMolFileName(Mol, MolNum)
  394     
  395     OutfileParams = {}
  396     Writer = RDKitUtil.MoleculesWriter(Outfile, **OutfileParams)
  397     if Writer is None:
  398         if not OptionsInfo["QuietMode"]:
  399             MiscUtil.PrintError("Failed to setup a writer for output fie %s " % Outfile)
  400         return False
  401     
  402     Writer.write(Mol)
  403 
  404     if Writer is not None:
  405         Writer.close()
  406     
  407     return True
  408 
  409 def VisualizeElectrostaticPotential():
  410     """Visualize cube files for electrostatic potential."""
  411 
  412     MiscUtil.PrintInfo("\nVisualizing cube files for electrostatic potential...")
  413 
  414     if not OptionsInfo["GenerateCubeFiles"]:
  415         MiscUtil.PrintInfo("\nInitializing Psi4...\n")
  416         Psi4Handle  = Psi4Util.InitializePsi4(PrintVersion = True, PrintHeader = False)
  417         OptionsInfo["psi4"] = Psi4Handle
  418     
  419     # Setup a molecule reader...
  420     MiscUtil.PrintInfo("Processing file %s..." % OptionsInfo["Infile"])
  421     Mols  = RDKitUtil.ReadMolecules(OptionsInfo["InfilePath"], **OptionsInfo["InfileParams"])
  422     
  423     # Setup for writing a PyMOL PML file...
  424     Outfile = OptionsInfo["Outfile"]
  425     OutFH = open(Outfile, "w")
  426     if OutFH is None:
  427         MiscUtil.PrintError("Failed to open output fie %s " % Outfile)
  428     MiscUtil.PrintInfo("\nGenerating file %s..." % Outfile)
  429 
  430     # Setup header...
  431     WritePMLHeader(OutFH, ScriptName)
  432     WritePyMOLParameters(OutFH)
  433 
  434     # Process cube files for molecules...
  435     FirstMol = True
  436     (MolCount, ValidMolCount, CubeFilesMissingCount) = [0] * 3
  437     for Mol in Mols:
  438         MolCount += 1
  439 
  440         if Mol is None:
  441             if not OptionsInfo["QuietMode"]:
  442                 MiscUtil.PrintInfo("\nProcessing molecule number %s..." % MolCount)
  443             continue
  444         ValidMolCount += 1
  445 
  446         MolName = RDKitUtil.GetMolName(Mol, MolCount)
  447         if not OptionsInfo["QuietMode"]:
  448             MiscUtil.PrintInfo("\nProcessing molecule %s..." % MolName)
  449             
  450         # Retrieve cube files...
  451         CubeFilesStatus, CubeFilesInfo = RetrieveMolCubeFilesInfo(Mol, MolCount)
  452         if not CubeFilesStatus:
  453             CubeFilesMissingCount += 1
  454             if not OptionsInfo["QuietMode"]:
  455                 MiscUtil.PrintWarning("Ignoring molecule with missing cube file...\n")
  456             continue
  457         
  458         # Setup PyMOL object names..
  459         PyMOLObjectNames = SetupPyMOLObjectNames(Mol, MolCount, CubeFilesInfo)
  460         
  461         # Setup molecule view...
  462         WriteMolView(OutFH, CubeFilesInfo, PyMOLObjectNames)
  463         
  464         # Setup ESP views...
  465         WriteElectrostaticPotentialView(OutFH, CubeFilesInfo, PyMOLObjectNames)
  466 
  467         # Setup ESP level group...
  468         Enable, Action = [True, "open"]
  469         GenerateAndWritePMLForGroup(OutFH, PyMOLObjectNames["ESPGroup"], PyMOLObjectNames["ESPGroupMembers"], Enable, Action)
  470         
  471         # Setup mol level group...
  472         Enable, Action = [False, "close"]
  473         if FirstMol:
  474             FirstMol = False
  475             Enable, Action = [True, "open"]
  476         GenerateAndWritePMLForGroup(OutFH, PyMOLObjectNames["MolGroup"], PyMOLObjectNames["MolGroupMembers"], Enable, Action)
  477     
  478     OutFH.close()
  479     
  480     MiscUtil.PrintInfo("\nTotal number of molecules: %d" % MolCount)
  481     MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
  482     MiscUtil.PrintInfo("Number of molecules with missing cube files: %d" % CubeFilesMissingCount)
  483     MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount + CubeFilesMissingCount))
  484 
  485 def WritePMLHeader(OutFH, ScriptName):
  486     """Write out PML header."""
  487 
  488     HeaderInfo = """\
  489 #
  490 # This file is automatically generated by the following Psi4 script available in
  491 # MayaChemTools: %s
  492 #
  493 cmd.reinitialize() """ % (ScriptName)
  494     
  495     OutFH.write("%s\n" % HeaderInfo)
  496 
  497 def WritePyMOLParameters(OutFH):
  498     """Write out PyMOL global parameters."""
  499 
  500     OutFH.write("""\n""\n"Setting up PyMOL gobal parameters..."\n""\n""")
  501     PMLCmds = []
  502     PMLCmds.append("""cmd.set("mesh_quality", %s)""" % (OptionsInfo["PyMOLViewParams"]["MeshQuality"]))
  503     PMLCmds.append("""cmd.set("mesh_width", %.2f)""" % (OptionsInfo["PyMOLViewParams"]["MeshWidth"]))
  504     
  505     PMLCmds.append("""cmd.set("surface_quality", %s)""" % (OptionsInfo["PyMOLViewParams"]["SurfaceQuality"]))
  506     PMLCmds.append("""cmd.set("transparency", %.2f)""" % (OptionsInfo["PyMOLViewParams"]["SurfaceTransparency"]))
  507     PML = "\n".join(PMLCmds)
  508     OutFH.write("%s\n" % PML)
  509 
  510 def WriteMolView(OutFH, CubeFilesInfo, PyMOLObjectNames):
  511     """Write out PML for viewing molecules."""
  512     
  513     MolName = CubeFilesInfo["MolName"]
  514     MolFile = CubeFilesInfo["MolFile"]
  515     
  516     OutFH.write("""\n""\n"Loading %s and setting up view for molecule..."\n""\n""" % MolFile)
  517     
  518     PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
  519     PMLCmds = []
  520     
  521     # Molecule...
  522     Name = PyMOLObjectNames["Mol"]
  523     PMLCmds.append("""cmd.load("%s", "%s")""" % (MolFile, Name))
  524     PMLCmds.append("""cmd.hide("everything", "%s")""" % (Name))
  525     PMLCmds.append("""util.cbag("%s", _self = cmd)""" % (Name))
  526     PMLCmds.append("""cmd.show("sticks", "%s")""" % (Name))
  527     PMLCmds.append("""cmd.set("stick_radius", %s, "%s")""" % (PyMOLViewParams["DisplayStickRadius"], Name))
  528     if PyMOLViewParams["HideHydrogens"]:
  529         PMLCmds.append("""cmd.hide("(%s and hydro)")""" % (Name))
  530     if re.match("^Sticks$", PyMOLViewParams["DisplayMolecule"]):
  531         PMLCmds.append("""cmd.enable("%s")""" % (Name))
  532     else:
  533         PMLCmds.append("""cmd.disable("%s")""" % (Name))
  534 
  535     # Molecule ball and stick...
  536     Name = PyMOLObjectNames["MolBallAndStick"]
  537     PMLCmds.append("""cmd.load("%s", "%s")""" % (MolFile, Name))
  538     PMLCmds.append("""cmd.hide("everything", "%s")""" % (Name))
  539     PMLCmds.append("""util.cbag("%s", _self = cmd)""" % (Name))
  540     PMLCmds.append("""cmd.show("sphere", "%s")""" % (Name))
  541     PMLCmds.append("""cmd.show("sticks", "%s")""" % (Name))
  542     PMLCmds.append("""cmd.set("sphere_scale", %s, "%s")""" % (PyMOLViewParams["DisplaySphereScale"], Name))
  543     PMLCmds.append("""cmd.set("stick_radius", %s, "%s")""" % (PyMOLViewParams["DisplayStickRadius"], Name))
  544     if PyMOLViewParams["HideHydrogens"]:
  545         PMLCmds.append("""cmd.hide("(%s and hydro)")""" % (Name))
  546     if re.match("^BallAndStick$", PyMOLViewParams["DisplayMolecule"]):
  547         PMLCmds.append("""cmd.enable("%s")""" % (Name))
  548     else:
  549         PMLCmds.append("""cmd.disable("%s")""" % (Name))
  550     
  551     PML = "\n".join(PMLCmds)
  552     OutFH.write("%s\n" % PML)
  553     
  554     # MolAlone group...
  555     GenerateAndWritePMLForGroup(OutFH, PyMOLObjectNames["MolAloneGroup"], PyMOLObjectNames["MolAloneGroupMembers"], True, "open")
  556 
  557 def WriteElectrostaticPotentialView(OutFH, CubeFilesInfo, PyMOLObjectNames):
  558     """Write out PML for electrostatic potential a molecule."""
  559 
  560     OutFH.write("""\n""\n"Setting up views for electrostatic potential..."\n""\n""")
  561 
  562     # ESP cube...
  563     ESPCubeFile = CubeFilesInfo["ESPFileName"]
  564     ESPCubeName = PyMOLObjectNames["ESPCube"]
  565     PMLCmds = []
  566     PMLCmds.append("""cmd.load("%s", "%s")""" % (ESPCubeFile, ESPCubeName))
  567     PMLCmds.append("""cmd.disable("%s")""" % (ESPCubeName))
  568     PMLCmds.append("")
  569     PML = "\n".join(PMLCmds)
  570     OutFH.write("%s\n" % PML)
  571 
  572     # ESP legend...
  573     ESPLegendName = PyMOLObjectNames["ESPLegend"]
  574     PMLCmds = []
  575     PMLCmds.append("""cmd.ramp_new("%s", "%s", [%s], [%s])""" % (ESPLegendName, ESPCubeName, ",".join(CubeFilesInfo["ESPRampValuesList"]), MiscUtil.JoinWords(CubeFilesInfo["ESPRampColorsList"], ",", Quote = True)))
  576     PMLCmds.append("""cmd.disable("%s")""" % (ESPLegendName))
  577     PMLCmds.append("")
  578     PML = "\n".join(PMLCmds)
  579     OutFH.write("%s\n" % PML)
  580     
  581     # Density cube...
  582     DensityCubeFile = CubeFilesInfo["DensityFileName"]
  583     DensityCubeName = PyMOLObjectNames["DensityCube"]
  584     PMLCmds = []
  585     PMLCmds.append("""cmd.load("%s", "%s")""" % (DensityCubeFile, DensityCubeName))
  586     PMLCmds.append("""cmd.disable("%s")""" % (DensityCubeName))
  587     PMLCmds.append("")
  588     PML = "\n".join(PMLCmds)
  589     OutFH.write("%s\n" % PML)
  590 
  591     # Density mesh...
  592     DensityContourLevel = CubeFilesInfo["DensityContourLevel"]
  593     DensityMeshName = PyMOLObjectNames["DensityMesh"]
  594     PMLCmds = []
  595     PMLCmds.append("""cmd.isomesh("%s", "%s", %s)""" % (DensityMeshName, DensityCubeName, DensityContourLevel))
  596     PMLCmds.append("""cmd.set("mesh_color", "%s", "%s")""" % (ESPLegendName, DensityMeshName))
  597     PMLCmds.append("""cmd.disable("%s")""" % (DensityMeshName))
  598     PMLCmds.append("")
  599     PML = "\n".join(PMLCmds)
  600     OutFH.write("%s\n" % PML)
  601     
  602     # Density surface...
  603     DensitySurfaceName = PyMOLObjectNames["DensitySurface"]
  604     PMLCmds = []
  605     PMLCmds.append("""cmd.isosurface("%s", "%s", %s)""" % (DensitySurfaceName, DensityCubeName, DensityContourLevel))
  606     PMLCmds.append("""cmd.set("surface_color", "%s", "%s")""" % (ESPLegendName, DensitySurfaceName))
  607     PMLCmds.append("""cmd.enable("%s")""" % (DensitySurfaceName))
  608     PMLCmds.append("")
  609     PML = "\n".join(PMLCmds)
  610     OutFH.write("%s\n" % PML)
  611     
  612     # Density group...
  613     Enable = True if re.match("^OnTotalDensity$", OptionsInfo["PyMOLViewParams"]["DisplayESP"]) else False
  614     GenerateAndWritePMLForGroup(OutFH, PyMOLObjectNames["DensityGroup"], PyMOLObjectNames["DensityGroupMembers"], Enable, "open")
  615     
  616     # Mol mesh...
  617     MolName = PyMOLObjectNames["Mol"]
  618     MolMeshName = PyMOLObjectNames["MolMesh"]
  619     PMLCmds = []
  620     PMLCmds.append("""cmd.create("%s", "(%s)")""" % (MolMeshName, MolName))
  621     PMLCmds.append("""cmd.hide("everything", "%s")""" % (MolMeshName))
  622     PMLCmds.append("""cmd.show("mesh", "%s")""" % (MolMeshName))
  623     PMLCmds.append("""cmd.set("mesh_color", "%s", "%s")""" % (ESPLegendName, MolMeshName))
  624     PMLCmds.append("""cmd.disable("%s")""" % (MolMeshName))
  625     PMLCmds.append("")
  626     PML = "\n".join(PMLCmds)
  627     OutFH.write("%s\n" % PML)
  628     
  629     # Mol surface...
  630     MolSurfaceName = PyMOLObjectNames["MolSurface"]
  631     PMLCmds = []
  632     PMLCmds.append("""cmd.create("%s", "(%s)")""" % (MolSurfaceName, MolName))
  633     PMLCmds.append("""cmd.hide("everything", "%s")""" % (MolSurfaceName))
  634     PMLCmds.append("""cmd.show("surface", "%s")""" % (MolSurfaceName))
  635     PMLCmds.append("""cmd.set("surface_color", "%s", "%s")""" % (ESPLegendName, MolSurfaceName))
  636     PMLCmds.append("""cmd.enable("%s")""" % (MolSurfaceName))
  637     PMLCmds.append("")
  638     PML = "\n".join(PMLCmds)
  639     OutFH.write("%s\n" % PML)
  640     
  641     # Mol group...
  642     Enable = True if re.match("^OnSurface$", OptionsInfo["PyMOLViewParams"]["DisplayESP"]) else False
  643     GenerateAndWritePMLForGroup(OutFH, PyMOLObjectNames["MolSurfaceGroup"], PyMOLObjectNames["MolSurfaceGroupMembers"], Enable, "open")
  644 
  645 def GenerateAndWritePMLForGroup(OutFH, GroupName, GroupMembersList, Enable, Action):
  646     """Generate and write PML for group."""
  647     
  648     OutFH.write("""\n""\n"Setting up group %s..."\n""\n""" % GroupName)
  649     
  650     PMLCmds = []
  651     
  652     GroupMembers = " ".join(GroupMembersList)
  653     PMLCmds.append("""cmd.group("%s", "%s")""" % (GroupName, GroupMembers))
  654     
  655     if Enable is not None:
  656         if Enable:
  657             PMLCmds.append("""cmd.enable("%s")""" % GroupName)
  658         else:
  659             PMLCmds.append("""cmd.disable("%s")""" % GroupName)
  660     
  661     if Action is not None:
  662         PMLCmds.append("""cmd.group("%s", action="%s")""" % (GroupName, Action))
  663 
  664     PML = "\n".join(PMLCmds)
  665     
  666     OutFH.write("%s\n\n" % PML)
  667 
  668 def RetrieveMolCubeFilesInfo(Mol, MolNum):
  669     """Retrieve available cube files info for a molecule."""
  670 
  671     # Initialize cube files info...
  672     CubeFilesInfo = {}
  673     CubeFilesInfo["ESPFileName"] = None
  674     CubeFilesInfo["DensityFileName"] = None
  675 
  676     # Setup cube mol info...
  677     CubeFilesInfo["MolPrefix"] = SetupMolPrefix(Mol, MolNum)
  678     CubeFilesInfo["MolName"] = SetupMolName(Mol, MolNum)
  679     CubeFilesInfo["MolFile"] = SetupMolFileName(Mol, MolNum)
  680 
  681     # ESP and Density cube file names...
  682     ESPCubeFiles = glob.glob("%s*ESP*.cube" % (CubeFilesInfo["MolPrefix"]))
  683     DensityCubeFiles = glob.glob("%s*Dt*.cube" % (CubeFilesInfo["MolPrefix"]))
  684     
  685     if len(ESPCubeFiles) == 0 or len(DensityCubeFiles) == 0:
  686         return (False, CubeFilesInfo)
  687     
  688     ESPCubeFileName = ESPCubeFiles[0]
  689     DensityCubeFileName = DensityCubeFiles[0]
  690     if  len(ESPCubeFiles) > 1 or len(DensityCubeFiles) > 1:
  691         if not OptionsInfo["QuietMode"]:
  692             MiscUtil.PrintWarning("Multiple ESP and/or density  cube files available for molecule %s; Using first set of cube files, %s and %s, to visualize electrostatic potential..." % (RDKitUtil.GetMolName(Mol, MolNum), ESPCubeFileName, DensityCubeFileName))
  693     
  694     CubeFilesInfo["ESPFileName"] = ESPCubeFileName
  695     CubeFilesInfo["DensityFileName"] = DensityCubeFileName
  696 
  697     DensityIsocontourRangeMin, DensityIsocontourRangeMax, DensityContourLevel = SetupDensityIsocontourRangeAndContourLevel(DensityCubeFileName)
  698     CubeFilesInfo["DensityIsocontourRangeMin"] = DensityIsocontourRangeMin
  699     CubeFilesInfo["DensityIsocontourRangeMax"] = DensityIsocontourRangeMax
  700     CubeFilesInfo["DensityContourLevel"] = DensityContourLevel
  701 
  702     ESPMinValue, ESPMaxValue, ESPRampValuesList, ESPRampColorsList = SetupESPRampValuesAndColors(ESPCubeFileName)
  703     CubeFilesInfo["ESPMinValue"] = ESPMinValue
  704     CubeFilesInfo["ESPMaxValue"] = ESPMaxValue
  705     CubeFilesInfo["ESPRampValuesList"] = ESPRampValuesList
  706     CubeFilesInfo["ESPRampColorsList"] = ESPRampColorsList
  707     
  708     return (True, CubeFilesInfo)
  709 
  710 def SetupDensityIsocontourRangeAndContourLevel(CubeFileName):
  711     """Setup density isocontour range and contour level."""
  712 
  713     PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
  714     
  715     # Setup isocontour range and contour levels...
  716     IsocontourRangeMin, IsocontourRangeMax = Psi4Util.RetrieveIsocontourRangeFromCubeFile(CubeFileName)
  717 
  718     DefaultIsocontourRange = 0.08
  719     if IsocontourRangeMin is None:
  720         IsocontourRangeMin = -DefaultIsocontourRange
  721         if not OptionsInfo["QuietMode"]:
  722             MiscUtil.PrintInfo("Failed to retrieve isocontour range from the cube file. Setting min isocontour value to %s..." % (IsocontourRangeMin))
  723     
  724     if IsocontourRangeMax is None:
  725         IsocontourRangeMax = DefaultIsocontourRange
  726         if not OptionsInfo["QuietMode"]:
  727             MiscUtil.PrintInfo("Failed to retrieve isocontour range from the cube file. Setting max isocontour value to %s..." % (IsocontourRangeMax))
  728 
  729     # Setup contour levels...
  730     ContourLevel = max(abs(IsocontourRangeMin), abs(IsocontourRangeMax)) * PyMOLViewParams["ContourLevelAutoAt"]
  731     
  732     if ContourLevel >= 0.01:
  733         ContourLevel = float("%.2f" % ContourLevel)
  734     elif ContourLevel >= 0.001:
  735         ContourLevel = float("%.3f" % ContourLevel)
  736     elif ContourLevel >= 0.0001:
  737         ContourLevel = float("%.4f" % ContourLevel)
  738     
  739     ContourLevel = ContourLevel if PyMOLViewParams["ContourLevelAuto"] else PyMOLViewParams["ContourLevel"]
  740     
  741     if not OptionsInfo["QuietMode"]:
  742         if IsocontourRangeMin is not None and IsocontourRangeMax is not None:
  743             MiscUtil.PrintInfo("DensityCubeFileName: %s; Isocontour range for %s percent of the density: %.4f to %.4f; ContourLevel: %s" % (CubeFileName, (100 * OptionsInfo["Psi4CubeFilesParams"]["IsoContourThreshold"]), IsocontourRangeMin, IsocontourRangeMax, ContourLevel))
  744 
  745     return (IsocontourRangeMin, IsocontourRangeMax, ContourLevel)
  746     
  747 def SetupESPRampValuesAndColors(CubeFileName):
  748     """Setup ESP ramp and color values."""
  749 
  750     PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
  751     
  752     ESPMinValue, ESPMaxValue = Psi4Util.RetrieveMinAndMaxValueFromCubeFile(CubeFileName)
  753     
  754     ESPRampValuesList = PyMOLViewParams["ESPRampValuesList"]
  755     ESPRampColorsList = PyMOLViewParams["ESPRampColorsList"]
  756     
  757     if PyMOLViewParams["ESPRampAuto"]:
  758         ESPRampLimit = min(abs(ESPMinValue), abs(ESPMaxValue))
  759         if ESPRampLimit >= 0.01:
  760             ESPRampLimit = float("%.2f" % ESPRampLimit)
  761         elif ESPRampLimit >= 0.001:
  762             ESPRampLimit = float("%.3f" % ESPRampLimit)
  763         elif ESPRampLimit >= 0.0001:
  764             ESPRampLimit = float("%.4f" % ESPRampLimit)
  765         ESPRampMin = -ESPRampLimit
  766         ESPRampMax = ESPRampLimit
  767         ESPRampValuesList = ["%s" % ESPRampMin, "0.0", "%s" % ESPRampMax]
  768         ESPRampColorsList = ["red", "white", "blue"]
  769         
  770     if not OptionsInfo["QuietMode"]:
  771         MiscUtil.PrintInfo("ESPCubeFileName: %s; MinValue: %.4f; MaxValue: %.4f; ESPRampValues: %s; ESPRampColors: %s" % (CubeFileName, ESPMinValue, ESPMaxValue, ESPRampValuesList, ESPRampColorsList))
  772 
  773     return (ESPMinValue, ESPMaxValue, ESPRampValuesList, ESPRampColorsList)
  774 
  775 def SetupPyMOLObjectNames(Mol, MolNum, CubeFilesInfo):
  776     """Setup PyMOL object names."""
  777     
  778     PyMOLObjectNames = {}
  779 
  780     SetupPyMOLObjectNamesForMol(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames)
  781     SetupPyMOLObjectNamesForESP(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames)
  782 
  783     return PyMOLObjectNames
  784 
  785 def SetupPyMOLObjectNamesForMol(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames):
  786     """Setup groups and objects for molecule."""
  787     
  788     MolFileRoot = CubeFilesInfo["MolPrefix"]
  789     
  790     MolGroupName = "%s" % MolFileRoot
  791     PyMOLObjectNames["MolGroup"] = MolGroupName
  792     PyMOLObjectNames["MolGroupMembers"] = []
  793 
  794     # Molecule alone group...
  795     MolAloneGroupName = "%s.Molecule" % (MolGroupName)
  796     PyMOLObjectNames["MolAloneGroup"] = MolAloneGroupName
  797     PyMOLObjectNames["MolGroupMembers"].append(MolAloneGroupName)
  798     
  799     PyMOLObjectNames["MolAloneGroupMembers"] = []
  800 
  801     # Molecule...
  802     MolName = "%s.Molecule" % (MolAloneGroupName)
  803     PyMOLObjectNames["Mol"] = MolName
  804     PyMOLObjectNames["MolAloneGroupMembers"].append(MolName)
  805     
  806     # BallAndStick...
  807     MolBallAndStickName = "%s.BallAndStick" % (MolAloneGroupName)
  808     PyMOLObjectNames["MolBallAndStick"] = MolBallAndStickName
  809     PyMOLObjectNames["MolAloneGroupMembers"].append(MolBallAndStickName)
  810     
  811 def SetupPyMOLObjectNamesForESP(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames):
  812     """Setup groups and objects for electrostatic potential."""
  813     
  814     MolGroupName = PyMOLObjectNames["MolGroup"]
  815 
  816     # ESP group...
  817     ESPGroupName = "%s.ESP" % (MolGroupName)
  818     PyMOLObjectNames["ESPGroup"] = ESPGroupName
  819     PyMOLObjectNames["MolGroupMembers"].append(ESPGroupName)
  820     PyMOLObjectNames["ESPGroupMembers"] = []
  821 
  822     # ESP cube...
  823     ESPCubeName = "%s.Cube" % (ESPGroupName)
  824     PyMOLObjectNames["ESPCube"] = ESPCubeName
  825     PyMOLObjectNames["ESPGroupMembers"].append(ESPCubeName)
  826     
  827     # ESP legend...
  828     ESPLegendName = "%s.Legend" % (ESPGroupName)
  829     PyMOLObjectNames["ESPLegend"] = ESPLegendName
  830     PyMOLObjectNames["ESPGroupMembers"].append(ESPLegendName)
  831     
  832     # Density group...
  833     DensityGroupName = "%s.On_Total_Density" % (ESPGroupName)
  834     PyMOLObjectNames["DensityGroup"] = DensityGroupName
  835     PyMOLObjectNames["ESPGroupMembers"].append(DensityGroupName)
  836     PyMOLObjectNames["DensityGroupMembers"] = []
  837     
  838     # Density cube...
  839     DensityCubeName = "%s.Cube" % (DensityGroupName)
  840     PyMOLObjectNames["DensityCube"] = DensityCubeName
  841     PyMOLObjectNames["DensityGroupMembers"].append(DensityCubeName)
  842     
  843     # Density mesh...
  844     DensityMeshName = "%s.Mesh" % (DensityGroupName)
  845     PyMOLObjectNames["DensityMesh"] = DensityMeshName
  846     PyMOLObjectNames["DensityGroupMembers"].append(DensityMeshName)
  847     
  848     # Density surface...
  849     DensitySurfaceName = "%s.Surface" % (DensityGroupName)
  850     PyMOLObjectNames["DensitySurface"] = DensitySurfaceName
  851     PyMOLObjectNames["DensityGroupMembers"].append(DensitySurfaceName)
  852     
  853     # MolSurface group...
  854     MolSurfaceGroupName = "%s.On_Surface" % (ESPGroupName)
  855     PyMOLObjectNames["MolSurfaceGroup"] = MolSurfaceGroupName
  856     PyMOLObjectNames["ESPGroupMembers"].append(MolSurfaceGroupName)
  857     PyMOLObjectNames["MolSurfaceGroupMembers"] = []
  858     
  859     # Mol mesh...
  860     MolMeshName = "%s.Mesh" % (MolSurfaceGroupName)
  861     PyMOLObjectNames["MolMesh"] = MolMeshName
  862     PyMOLObjectNames["MolSurfaceGroupMembers"].append(MolMeshName)
  863     
  864     # Mol surface...
  865     MolSurfaceName = "%s.Surface" % (MolSurfaceGroupName)
  866     PyMOLObjectNames["MolSurface"] = MolSurfaceName
  867     PyMOLObjectNames["MolSurfaceGroupMembers"].append(MolSurfaceName)
  868     
  869 def SetupMolFileName(Mol, MolNum):
  870     """Setup SD file name for a molecule."""
  871 
  872     MolPrefix = SetupMolPrefix(Mol, MolNum)
  873     MolFileName = "%s.sdf" % MolPrefix
  874 
  875     return MolFileName
  876     
  877 def SetupMolCubeFileName(Mol, MolNum, Psi4CubeFileName):
  878     """Setup cube file name for a molecule."""
  879 
  880     FileDir, FileName, FileExt = MiscUtil.ParseFileName(Psi4CubeFileName)
  881     CubeFileName = "%s.%s" % (FileName, FileExt)
  882 
  883     # Replace Psi by MolPrefix...
  884     MolPrefix = SetupMolPrefix(Mol, MolNum)
  885     CubeFileName = "%s_%s" % (MolPrefix, CubeFileName)
  886     
  887     return CubeFileName
  888     
  889 def SetupMolPrefix(Mol, MolNum):
  890     """Get molecule prefix for generating files and directories."""
  891 
  892     MolNamePrefix = ''
  893     if Mol.HasProp("_Name"):
  894         MolNamePrefix = re.sub("[^a-zA-Z0-9]", "_", Mol.GetProp("_Name"))
  895     
  896     MolNumPrefix = "Mol%s" % MolNum
  897 
  898     if OptionsInfo["UseMolNumPrefix"] and OptionsInfo["UseMolNamePrefix"]:
  899         MolPrefix = MolNumPrefix
  900         if len(MolNamePrefix):
  901             MolPrefix = "%s_%s" % (MolNumPrefix, MolNamePrefix)
  902     elif OptionsInfo["UseMolNamePrefix"]:
  903         MolPrefix = MolNamePrefix if len(MolNamePrefix) else MolNumPrefix
  904     elif OptionsInfo["UseMolNumPrefix"]:
  905         MolPrefix = MolNumPrefix
  906     
  907     return MolPrefix
  908 
  909 def SetupMolName(Mol, MolNum):
  910     """Get molecule name."""
  911 
  912     # Test for creating PyMOL object name for molecule...
  913     MolNamePrefix = ''
  914     if Mol.HasProp("_Name"):
  915         MolName = re.sub("[^a-zA-Z0-9]", "_", Mol.GetProp("_Name"))
  916     else:
  917         MolName = "Mol%s" % MolNum
  918     
  919     return MolName
  920 
  921 def SetupPsi4Mol(Psi4Handle, Mol, MolCount = None):
  922     """Setup a Psi4 molecule object."""
  923     
  924     MolGeometry = RDKitUtil.GetPsi4XYZFormatString(Mol, NoCom = True, NoReorient = True)
  925     
  926     try:
  927         Psi4Mol = Psi4Handle.geometry(MolGeometry)
  928     except Exception as ErrMsg:
  929         Psi4Mol = None
  930         if not OptionsInfo["QuietMode"]:
  931             MiscUtil.PrintWarning("Failed to create Psi4 molecule from geometry string: %s\n" % ErrMsg)
  932             MolName = RDKitUtil.GetMolName(Mol, MolCount)
  933             MiscUtil.PrintWarning("Ignoring molecule: %s" % MolName)
  934     
  935     return Psi4Mol
  936 
  937 def PerformPsi4Cleanup(Psi4Handle):
  938     """Perform clean up."""
  939     
  940     # Clean up after Psi4 run...
  941     Psi4Handle.core.clean()
  942     
  943     # Clean up any leftover scratch files...
  944     if OptionsInfo["MPMode"]:
  945         Psi4Util.RemoveScratchFiles(Psi4Handle, OptionsInfo["Psi4RunParams"]["OutputFile"])
  946 
  947 def CheckAndValidateMolecule(Mol, MolCount = None):
  948     """Validate molecule for Psi4 calculations."""
  949     
  950     if Mol is None:
  951         if not OptionsInfo["QuietMode"]:
  952             MiscUtil.PrintInfo("\nProcessing molecule number %s..." % MolCount)
  953         return False
  954     
  955     MolName = RDKitUtil.GetMolName(Mol, MolCount)
  956     if not OptionsInfo["QuietMode"]:
  957         MiscUtil.PrintInfo("\nProcessing molecule %s..." % MolName)
  958     
  959     if RDKitUtil.IsMolEmpty(Mol):
  960         if not OptionsInfo["QuietMode"]:
  961             MiscUtil.PrintWarning("Ignoring empty molecule: %s\n" % MolName)
  962         return False
  963 
  964     if not RDKitUtil.ValidateElementSymbols(RDKitUtil.GetAtomSymbols(Mol)):
  965         if not OptionsInfo["QuietMode"]:
  966             MiscUtil.PrintWarning("Ignoring molecule containing invalid element symbols: %s\n" % MolName)
  967         return False
  968     
  969     # Check for 3D flag...
  970     if not Mol.GetConformer().Is3D():
  971         if not OptionsInfo["QuietMode"]:
  972             MiscUtil.PrintWarning("3D tag is not set for molecule: %s\n" % MolName)
  973     
  974     # Check for missing hydrogens...
  975     if RDKitUtil.AreHydrogensMissingInMolecule(Mol):
  976         if not OptionsInfo["QuietMode"]:
  977             MiscUtil.PrintWarning("Missing hydrogens in molecule: %s\n" % MolName)
  978 
  979     return True
  980 
  981 def SetupMethodNameAndBasisSet(Mol):
  982     """Setup method name and basis set."""
  983     
  984     MethodName = OptionsInfo["MethodName"]
  985     if OptionsInfo["MethodNameAuto"]:
  986         MethodName = "B3LYP"
  987     
  988     BasisSet = OptionsInfo["BasisSet"]
  989     if OptionsInfo["BasisSetAuto"]:
  990         BasisSet = "6-31+G**" if RDKitUtil.IsAtomSymbolPresentInMol(Mol, "S") else "6-31G**"
  991     
  992     return (MethodName, BasisSet)
  993 
  994 def SetupReferenceWavefunction(Mol):
  995     """Setup reference wavefunction."""
  996 
  997     Reference = OptionsInfo["Reference"]
  998     if OptionsInfo["ReferenceAuto"]:
  999         Reference = 'UHF' if (RDKitUtil.GetSpinMultiplicity(Mol) > 1) else 'RHF'
 1000     
 1001     return Reference
 1002 
 1003 def CheckAndSetupOutfilesDir():
 1004     """Check and setup outfiles directory."""
 1005     
 1006     if OptionsInfo["GenerateCubeFiles"]:
 1007         if os.path.isdir(OptionsInfo["OutfilesDir"]):
 1008             if not Options["--overwrite"]:
 1009                 MiscUtil.PrintError("The outfiles directory, %s, corresponding to output file specified, %s, for option \"-o, --outfile\" already exist. Use option \"--overwrite\" or \"--ov\"  and try again to generate and visualize cube files...\n" % (OptionsInfo["OutfilesDir"], OptionsInfo["Outfile"]))
 1010     
 1011     if OptionsInfo["VisualizeCubeFiles"]:
 1012         if not Options["--overwrite"]:
 1013             if  os.path.exists(os.path.join(OptionsInfo["OutfilesDir"], OptionsInfo["Outfile"])):
 1014                 MiscUtil.PrintError("The outfile file specified, %s, for option \"-o, --outfile\" already exist in outfiles dir, %s. Use option \"--overwrite\" or \"--ov\" to overwrite and try again to generate and visualize cube files.\n" % (OptionsInfo["Outfile"], OptionsInfo["OutfilesDir"]))
 1015                     
 1016         if not OptionsInfo["GenerateCubeFiles"]:
 1017             if not os.path.isdir(OptionsInfo["OutfilesDir"]):
 1018                 MiscUtil.PrintError("The outfiles directory, %s, corresponding to output file specified, %s, for option \"-o, --outfile\" doesn't exist. Use value, GenerateCubeFiles or Both, for option \"--mode\" and try again to generate and visualize cube files...\n" % (OptionsInfo["OutfilesDir"], OptionsInfo["Outfile"]))
 1019             
 1020             CubeFiles = glob.glob(os.path.join(OptionsInfo["OutfilesDir"], "*.cube"))
 1021             if not len(CubeFiles):
 1022                 MiscUtil.PrintError("The outfiles directory, %s, contains no cube files, \"*.cube\". Use value, GenerateCubeFiles or Both, for option \"--mode\" and try again to generate and visualize cube...\n" % (OptionsInfo["OutfilesDir"]))
 1023     
 1024     OutfilesDir = OptionsInfo["OutfilesDir"]
 1025     if OptionsInfo["GenerateCubeFiles"]:
 1026         if not os.path.isdir(OutfilesDir):
 1027             MiscUtil.PrintInfo("\nCreating directory %s..." % OutfilesDir)
 1028             os.mkdir(OutfilesDir)
 1029 
 1030     MiscUtil.PrintInfo("\nChanging directory to %s..." % OutfilesDir)
 1031     os.chdir(OutfilesDir)
 1032 
 1033 def ProcessESPRampOptions():
 1034     """Process ESP ramp options for PyMOL views."""
 1035     
 1036     PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
 1037     
 1038     ESPRampColorsAuto = PyMOLViewParams["ESPRampColorsAuto"]
 1039     ESPRampValuesAuto = PyMOLViewParams["ESPRampValuesAuto"]
 1040     ESPRampAuto = True if (ESPRampValuesAuto or ESPRampColorsAuto) else False
 1041     
 1042     if (ESPRampColorsAuto and not ESPRampValuesAuto) or (ESPRampValuesAuto and not ESPRampColorsAuto):
 1043         MiscUtil.PrintError("The parameter values, \"%s\" and \"%s\", specified for paramater names, \"espRampValues\" and \"espRampColors\", using \"--pymolViewParams\" are not valid. \"auto\" value must be specified for both parameters. " % (PyMOLViewParams["ESPRampValues"], PyMOLViewParams["ESPRampColors"]))
 1044     
 1045     ESPRampValuesList = []
 1046     ESPRampColorsList = []
 1047     if not ESPRampValuesAuto:
 1048         for Value in  PyMOLViewParams["ESPRampValues"].split():
 1049             if not MiscUtil.IsFloat(Value):
 1050                 MiscUtil.PrintError("The value, %s, specified for paramater name, \"espRampValues\", using \"--pymolViewParams\" must be a float." % (Value))
 1051             ESPRampValuesList.append(Value)
 1052     
 1053     if not ESPRampColorsAuto:
 1054         ESPRampColorsList = PyMOLViewParams["ESPRampColors"].split()
 1055     
 1056     if len(ESPRampValuesList) != len(ESPRampColorsList):
 1057         MiscUtil.PrintError("The number of values, \"%s\" and \"%s\", specified for paramater names, \"espRampValues\" and \"espRampColors\", using \"--pymolViewParams\" must match." % (len(ESPRampValuesList), len(ESPRampColorsList)))
 1058         
 1059     PyMOLViewParams["ESPRampValuesList"] = ESPRampValuesList
 1060     PyMOLViewParams["ESPRampColorsList"] = ESPRampColorsList
 1061     PyMOLViewParams["ESPRampAuto"] = ESPRampAuto
 1062     
 1063 def ProcessOptions():
 1064     """Process and validate command line arguments and options."""
 1065     
 1066     MiscUtil.PrintInfo("Processing options...")
 1067     
 1068     # Validate options...
 1069     ValidateOptions()
 1070 
 1071     OptionsInfo["Infile"] = Options["--infile"]
 1072     OptionsInfo["InfilePath"] = os.path.abspath(Options["--infile"])
 1073     
 1074     ParamsDefaultInfoOverride = {"RemoveHydrogens": False}
 1075     OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters("--infileParams", Options["--infileParams"], InfileName = Options["--infile"], ParamsDefaultInfo = ParamsDefaultInfoOverride)
 1076 
 1077     Outfile = Options["--outfile"]
 1078     FileDir, FileName, FileExt = MiscUtil.ParseFileName(Outfile)
 1079     
 1080     OptionsInfo["Outfile"] = Outfile
 1081     OptionsInfo["OutfileRoot"] = FileName
 1082     OptionsInfo["OutfileExt"] = FileExt
 1083     
 1084     OutfilesDir = Options["--outfilesDir"]
 1085     if re.match("^auto$", OutfilesDir, re.I):
 1086         OutfilesDir = "%s_ESP" % OptionsInfo["OutfileRoot"]
 1087     OptionsInfo["OutfilesDir"] = OutfilesDir
 1088     
 1089     OptionsInfo["Overwrite"] = Options["--overwrite"]
 1090     
 1091     # Method, basis set, and reference wavefunction...
 1092     OptionsInfo["BasisSet"] = Options["--basisSet"]
 1093     OptionsInfo["BasisSetAuto"] = True if re.match("^auto$", Options["--basisSet"], re.I) else False
 1094     
 1095     OptionsInfo["MethodName"] = Options["--methodName"]
 1096     OptionsInfo["MethodNameAuto"] = True if re.match("^auto$", Options["--methodName"], re.I) else False
 1097     
 1098     OptionsInfo["Reference"] = Options["--reference"]
 1099     OptionsInfo["ReferenceAuto"] = True if re.match("^auto$", Options["--reference"], re.I) else False
 1100     
 1101     # Run, options, and cube files parameters...
 1102     OptionsInfo["Psi4OptionsParams"] = Psi4Util.ProcessPsi4OptionsParameters("--psi4OptionsParams", Options["--psi4OptionsParams"])
 1103     OptionsInfo["Psi4RunParams"] = Psi4Util.ProcessPsi4RunParameters("--psi4RunParams", Options["--psi4RunParams"], InfileName = OptionsInfo["Infile"])
 1104 
 1105     ParamsDefaultInfoOverride = {}
 1106     OptionsInfo["Psi4CubeFilesParams"] = Psi4Util.ProcessPsi4CubeFilesParameters("--psi4CubeFilesParams", Options["--psi4CubeFilesParams"], ParamsDefaultInfo = ParamsDefaultInfoOverride)
 1107 
 1108     ParamsDefaultInfoOverride = {"ContourLevel": "auto", "ContourLevelAutoAt": 0.75, "DisplayESP": "OnSurface", "DisplayMolecule": "BallAndStick", "DisplaySphereScale": 0.2, "DisplayStickRadius": 0.1, "ESPRampColors": "auto", "ESPRampValues": "auto", "HideHydrogens": True, "MeshWidth": 0.5, "MeshQuality": 2, "SurfaceQuality": 2, "SurfaceTransparency": 0.25}
 1109     OptionsInfo["PyMOLViewParams"] = MiscUtil.ProcessOptionPyMOLCubeFileViewParameters("--pymolViewParams", Options["--pymolViewParams"], ParamsDefaultInfo = ParamsDefaultInfoOverride)
 1110 
 1111     ProcessESPRampOptions()
 1112 
 1113     Mode = Options["--mode"]
 1114     if re.match("^GenerateCubeFiles$", Mode, re.I):
 1115         GenerateCubeFiles = True
 1116         VisualizeCubeFiles = False
 1117     elif re.match("^VisualizeCubeFiles$", Mode, re.I):
 1118         GenerateCubeFiles = False
 1119         VisualizeCubeFiles = True
 1120     else:
 1121         GenerateCubeFiles = True
 1122         VisualizeCubeFiles = True
 1123     OptionsInfo["Mode"] = Mode
 1124     OptionsInfo["GenerateCubeFiles"] = GenerateCubeFiles
 1125     OptionsInfo["VisualizeCubeFiles"] = VisualizeCubeFiles
 1126     
 1127     OptionsInfo["MPMode"] = True if re.match("^yes$", Options["--mp"], re.I) else False
 1128     OptionsInfo["MPParams"] = MiscUtil.ProcessOptionMultiprocessingParameters("--mpParams", Options["--mpParams"])
 1129     
 1130     OutfilesMolPrefix = Options["--outfilesMolPrefix"]
 1131     if re.match("^MolName$", OutfilesMolPrefix, re.I):
 1132         UseMolNamePrefix = True
 1133         UseMolNumPrefix = False
 1134     elif re.match("^MolNum$", OutfilesMolPrefix, re.I):
 1135         UseMolNamePrefix = False
 1136         UseMolNumPrefix = True
 1137     else:
 1138         UseMolNamePrefix = True
 1139         UseMolNumPrefix = True
 1140     OptionsInfo["OutfilesMolPrefix"] = OutfilesMolPrefix
 1141     OptionsInfo["UseMolNamePrefix"] = UseMolNamePrefix
 1142     OptionsInfo["UseMolNumPrefix"] = UseMolNumPrefix
 1143     
 1144     OptionsInfo["QuietMode"] = True if re.match("^yes$", Options["--quiet"], re.I) else False
 1145     
 1146     CheckAndSetupOutfilesDir()
 1147     
 1148 def RetrieveOptions():
 1149     """Retrieve command line arguments and options."""
 1150     
 1151     # Get options...
 1152     global Options
 1153     Options = docopt(_docoptUsage_)
 1154     
 1155     # Set current working directory to the specified directory...
 1156     WorkingDir = Options["--workingdir"]
 1157     if WorkingDir:
 1158         os.chdir(WorkingDir)
 1159     
 1160     # Handle examples option...
 1161     if "--examples" in Options and Options["--examples"]:
 1162         MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
 1163         sys.exit(0)
 1164 
 1165 def ValidateOptions():
 1166     """Validate option values."""
 1167     
 1168     MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
 1169     MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "sdf sd mol")
 1170 
 1171     MiscUtil.ValidateOptionFileExt("-o, --outfile", Options["--outfile"], "pml")
 1172     
 1173     MiscUtil.ValidateOptionTextValue("--mode", Options["--mode"], "GenerateCubeFiles VisualizeCubeFiles Both")
 1174     MiscUtil.ValidateOptionTextValue("--mp", Options["--mp"], "yes no")
 1175     
 1176     MiscUtil.ValidateOptionTextValue("--outfilesMolPrefix", Options["--outfilesMolPrefix"], "MolNum MolName Both")
 1177     MiscUtil.ValidateOptionTextValue("-q, --quiet", Options["--quiet"], "yes no")
 1178  
 1179 # Setup a usage string for docopt...
 1180 _docoptUsage_ = """
 1181 Psi4VisualizeElectrostaticPotential.py - Visualize electrostatic potential
 1182 
 1183 Usage:
 1184     Psi4VisualizeElectrostaticPotential.py [--basisSet <text>] [--infileParams <Name,Value,...>] [--methodName <text>]
 1185                                            [--mode <GenerateCubeFiles, VisualizeCubeFiles, Both>] [--mp <yes or no>] [--mpParams <Name, Value,...>]
 1186                                            [--outfilesDir <text>] [--outfilesMolPrefix <MolNum, MolName, Both> ] [--overwrite]
 1187                                            [--psi4CubeFilesParams <Name,Value,...>] [--psi4OptionsParams <Name,Value,...>]
 1188                                            [--psi4RunParams <Name,Value,...>] [--pymolViewParams <Name,Value,...>] [--quiet <yes or no>]
 1189                                            [--reference <text>] [-w <dir>] -i <infile> -o <outfile> 
 1190     Psi4VisualizeElectrostaticPotential.py -h | --help | -e | --examples
 1191 
 1192 Description:
 1193     Generate and visualize total electrostatic potential (ESP) for molecules in
 1194     input file. A set of cube files, corresponding to total ESP and total density,
 1195     is generated for molecules. The cube files are used to create a PyMOL
 1196     visualization file for viewing meshes and surfaces representing ESP. An
 1197     option is available to skip the generation of new cube files and use existing
 1198     cube file to visualize frontier molecular orbitals.
 1199     
 1200     The total ESP corresponds to the sum to nuclear and electronic electrostatic
 1201     potential. The total density represents the sum of alpha and beta electron
 1202     densities. The ESP is mapped on the density and molecule surface for each
 1203     molecule in input file. The ESP value range and density contour level is
 1204     automatically determined from the cube files. An option is available to 
 1205     override these values.
 1206     
 1207     A Psi4 XYZ format geometry string is automatically generated for each molecule
 1208     in input file. It contains atom symbols and 3D coordinates for each atom in a
 1209     molecule. In addition, the formal charge and spin multiplicity are present in the
 1210     the geometry string. These values are either retrieved from molecule properties
 1211     named 'FormalCharge' and 'SpinMultiplicty' or dynamically calculated for a
 1212     molecule.
 1213     
 1214     A set of cube and SD output files is generated for each molecule in input file
 1215     as shown below:
 1216         
 1217         Ouput dir: <OutfileRoot>_ESP or <OutfilesDir>
 1218         
 1219         <MolIDPrefix>.sdf
 1220         <MolIDPrefix>*ESP*.cube
 1221         <MolIDPrefix>*Dt*.cube
 1222         
 1223     In addition, a <OutfileRoot>.pml is generated containing ESP for all molecules
 1224     in input fie.
 1225     
 1226     The supported input file formats are: Mol (.mol), SD (.sdf, .sd)
 1227      
 1228     A variety of PyMOL groups and objects are  created for visualization of ESP
 1229     for molecules as shown below:
 1230         
 1231         <MoleculeID>
 1232             .Molecule
 1233                 .Molecule
 1234                 .BallAndStick
 1235             .ESP
 1236                 .Cube
 1237                 .Legend
 1238                 .On_Total_Density
 1239                     .Cube
 1240                     .Mesh
 1241                     .Surface
 1242                 .On_Surface
 1243                     .Mesh
 1244                     .Surface
 1245         <MoleculeID>
 1246             .Molecule
 1247                 ... ... ...
 1248             .ESP
 1249                 ... ... ...
 1250 
 1251 Options:
 1252     -b, --basisSet <text>  [default: auto]
 1253         Basis set to use for calculating single point energy before generating
 1254         cube files corresponding to total ESP and density. Default: 6-31+G**
 1255         for sulfur containing molecules; Otherwise, 6-31G** [ Ref 150 ]. The
 1256         specified value must be a valid Psi4 basis set. No validation is
 1257         performed.
 1258         
 1259         The following list shows a representative sample of basis sets available
 1260         in Psi4:
 1261             
 1262             STO-3G, 6-31G, 6-31+G, 6-31++G, 6-31G*, 6-31+G*,  6-31++G*, 
 1263             6-31G**, 6-31+G**, 6-31++G**, 6-311G, 6-311+G, 6-311++G,
 1264             6-311G*, 6-311+G*, 6-311++G*, 6-311G**, 6-311+G**, 6-311++G**,
 1265             cc-pVDZ, cc-pCVDZ, aug-cc-pVDZ, cc-pVDZ-DK, cc-pCVDZ-DK, def2-SVP,
 1266             def2-SVPD, def2-TZVP, def2-TZVPD, def2-TZVPP, def2-TZVPPD
 1267             
 1268     -e, --examples
 1269         Print examples.
 1270     -h, --help
 1271         Print this help message.
 1272     -i, --infile <infile>
 1273         Input file name.
 1274     --infileParams <Name,Value,...>  [default: auto]
 1275         A comma delimited list of parameter name and value pairs for reading
 1276         molecules from files. The supported parameter names for different file
 1277         formats, along with their default values, are shown below:
 1278             
 1279             SD, MOL: removeHydrogens,no,sanitize,yes,strictParsing,yes
 1280             
 1281     -m, --methodName <text>  [default: auto]
 1282         Method to use for calculating single point energy before generating
 1283         cube files corresponding to total ESP and density. Default: B3LYP
 1284         [ Ref 150 ]. The specified value must be a valid Psi4 method name.
 1285         No validation is performed.
 1286         
 1287         The following list shows a representative sample of methods available
 1288         in Psi4:
 1289             
 1290             B1LYP, B2PLYP, B2PLYP-D3BJ, B2PLYP-D3MBJ, B3LYP, B3LYP-D3BJ,
 1291             B3LYP-D3MBJ, CAM-B3LYP, CAM-B3LYP-D3BJ, HF, HF-D3BJ,  HF3c, M05,
 1292             M06, M06-2x, M06-HF, M06-L, MN12-L, MN15, MN15-D3BJ,PBE, PBE0,
 1293             PBEH3c, PW6B95, PW6B95-D3BJ, WB97, WB97X, WB97X-D, WB97X-D3BJ
 1294             
 1295     --mode <GenerateCubeFiles, VisualizeCubeFiles, or Both>  [default: Both]
 1296         Generate and visualize cube files corresponding to total ESP. The
 1297         'VisualizeCubes' value skips the generation of new cube files and uses
 1298         existing cube files for visualization of ESP. Multiprocessing is not
 1299         supported during 'VisualizeCubeFiles' value of '--mode' option.
 1300     --mp <yes or no>  [default: no]
 1301         Use multiprocessing.
 1302          
 1303         By default, input data is retrieved in a lazy manner via mp.Pool.imap()
 1304         function employing lazy RDKit data iterable. This allows processing of
 1305         arbitrary large data sets without any additional requirements memory.
 1306         
 1307         All input data may be optionally loaded into memory by mp.Pool.map()
 1308         before starting worker processes in a process pool by setting the value
 1309         of 'inputDataMode' to 'InMemory' in '--mpParams' option.
 1310         
 1311         A word to the wise: The default 'chunkSize' value of 1 during 'Lazy' input
 1312         data mode may adversely impact the performance. The '--mpParams' section
 1313         provides additional information to tune the value of 'chunkSize'.
 1314     --mpParams <Name,Value,...>  [default: auto]
 1315         A comma delimited list of parameter name and value pairs to configure
 1316         multiprocessing.
 1317         
 1318         The supported parameter names along with their default and possible
 1319         values are shown below:
 1320         
 1321             chunkSize, auto
 1322             inputDataMode, Lazy   [ Possible values: InMemory or Lazy ]
 1323             numProcesses, auto   [ Default: mp.cpu_count() ]
 1324         
 1325         These parameters are used by the following functions to configure and
 1326         control the behavior of multiprocessing: mp.Pool(), mp.Pool.map(), and
 1327         mp.Pool.imap().
 1328         
 1329         The chunkSize determines chunks of input data passed to each worker
 1330         process in a process pool by mp.Pool.map() and mp.Pool.imap() functions.
 1331         The default value of chunkSize is dependent on the value of 'inputDataMode'.
 1332         
 1333         The mp.Pool.map() function, invoked during 'InMemory' input data mode,
 1334         automatically converts RDKit data iterable into a list, loads all data into
 1335         memory, and calculates the default chunkSize using the following method
 1336         as shown in its code:
 1337         
 1338             chunkSize, extra = divmod(len(dataIterable), len(numProcesses) * 4)
 1339             if extra: chunkSize += 1
 1340         
 1341         For example, the default chunkSize will be 7 for a pool of 4 worker processes
 1342         and 100 data items.
 1343         
 1344         The mp.Pool.imap() function, invoked during 'Lazy' input data mode, employs
 1345         'lazy' RDKit data iterable to retrieve data as needed, without loading all the
 1346         data into memory. Consequently, the size of input data is not known a priori.
 1347         It's not possible to estimate an optimal value for the chunkSize. The default 
 1348         chunkSize is set to 1.
 1349         
 1350         The default value for the chunkSize during 'Lazy' data mode may adversely
 1351         impact the performance due to the overhead associated with exchanging
 1352         small chunks of data. It is generally a good idea to explicitly set chunkSize to
 1353         a larger value during 'Lazy' input data mode, based on the size of your input
 1354         data and number of processes in the process pool.
 1355         
 1356         The mp.Pool.map() function waits for all worker processes to process all
 1357         the data and return the results. The mp.Pool.imap() function, however,
 1358         returns the the results obtained from worker processes as soon as the
 1359         results become available for specified chunks of data.
 1360         
 1361         The order of data in the results returned by both mp.Pool.map() and 
 1362         mp.Pool.imap() functions always corresponds to the input data.
 1363     -o, --outfile <outfile>
 1364         Output file name for PyMOL PML file. The PML output file, along with cube
 1365         files, is generated in a local directory corresponding to '--outfilesDir'
 1366         option.
 1367     --outfilesDir <text>  [default: auto]
 1368         Directory name containing PML and cube files. Default:
 1369        <OutfileRoot>_ESP. This directory must be present during
 1370         'VisualizeCubeFiles' value of '--mode' option.
 1371     --outfilesMolPrefix <MolNum, MolName, Both>  [default: Both]
 1372         Molecule prefix to use for the names of cube files. Possible values:
 1373         MolNum, MolName, or Both. By default, both molecule number and name
 1374         are used. The format of molecule prefix is as follows: MolNum - Mol<Num>;
 1375         MolName - <MolName>, Both: Mol<Num>_<MolName>. Empty molecule names
 1376         are ignored. Molecule numbers are used for empty molecule names.
 1377     --overwrite
 1378         Overwrite existing files.
 1379     --psi4CubeFilesParams <Name,Value,...>  [default: auto]
 1380         A comma delimited list of parameter name and value pairs for generating
 1381         Psi4 cube files.
 1382         
 1383         The supported parameter names along with their default and possible
 1384         values are shown below:
 1385              
 1386             gridSpacing, 0.2, gridOverage, 4.0, isoContourThreshold, 0.85
 1387              
 1388         gridSpacing: Grid spacing for generating cube files. Units: Bohr. A higher
 1389         value reduces the size of the cube files on the disk. This option corresponds
 1390         to Psi4 option CUBIC_GRID_SPACING.
 1391         
 1392         gridOverage: Grid overage for generating cube files. Units: Bohr.This option
 1393         corresponds to Psi4 option CUBIC_GRID_OVERAGE.
 1394         
 1395         isoContourThreshold: IsoContour values for generating cube files that capture
 1396         specified percent of the probability density using the least amount of grid
 1397         points. Default: 0.85 (85%). This option corresponds to Psi4 option
 1398         CUBEPROP_ISOCONTOUR_THRESHOLD.
 1399     --psi4OptionsParams <Name,Value,...>  [default: none]
 1400         A comma delimited list of Psi4 option name and value pairs for setting
 1401         global and module options. The names are 'option_name' for global options
 1402         and 'module_name__option_name' for options local to a module. The
 1403         specified option names must be valid Psi4 names. No validation is
 1404         performed.
 1405         
 1406         The specified option name and  value pairs are processed and passed to
 1407         psi4.set_options() as a dictionary. The supported value types are float,
 1408         integer, boolean, or string. The float value string is converted into a float.
 1409         The valid values for a boolean string are yes, no, true, false, on, or off. 
 1410     --psi4RunParams <Name,Value,...>  [default: auto]
 1411         A comma delimited list of parameter name and value pairs for configuring
 1412         Psi4 jobs.
 1413         
 1414         The supported parameter names along with their default and possible
 1415         values are shown below:
 1416              
 1417             MemoryInGB, 1
 1418             NumThreads, 1
 1419             OutputFile, auto   [ Possible  values: stdout, quiet, or FileName ]
 1420             ScratchDir, auto   [ Possivle values: DirName]
 1421             RemoveOutputFile, yes   [ Possible values: yes, no, true, or false]
 1422             
 1423         These parameters control the runtime behavior of Psi4.
 1424         
 1425         The default file name for 'OutputFile' is <InFileRoot>_Psi4.out. The PID
 1426         is appended to output file name during multiprocessing as shown:
 1427         <InFileRoot>_Psi4_<PIDNum>.out. The 'stdout' value for 'OutputType'
 1428         sends Psi4 output to stdout. The 'quiet' or 'devnull' value suppresses
 1429         all Psi4 output.
 1430         
 1431         The default 'Yes' value of 'RemoveOutputFile' option forces the removal
 1432         of any existing Psi4 before creating new files to append output from
 1433         multiple Psi4 runs.
 1434         
 1435         The option 'ScratchDir' is a directory path to the location of scratch
 1436         files. The default value corresponds to Psi4 default. It may be used to
 1437         override the deafult path.
 1438     --pymolViewParams <Name,Value,...>  [default: auto]
 1439         A comma delimited list of parameter name and value pairs for visualizing
 1440         cube files in PyMOL.
 1441             
 1442             contourLevel, auto, contourLevelAutoAt, 0.75
 1443             displayESP, OnSurface, displayMolecule, BallAndStick,
 1444             displaySphereScale, 0.2, displayStickRadius, 0.1,
 1445             espRampValues, auto, espRampColors, auto,
 1446             hideHydrogens, yes,
 1447             meshWidth, 0.5, meshQuality, 2, 
 1448             surfaceQuality, 2, surfaceTransparency, 0.25,
 1449             
 1450         contourLevel: Contour level to use for visualizing meshes and surfaces
 1451         for the total density retrieved from the cube files. The contour level is set
 1452         at 'contourLevelAutoAt' of the absolute maximum value of the isocontour
 1453         range. For example: Contour level is set to plus 0.05 at 'contourLevelAutoAt'
 1454         of 0.75 for isocontour range of 0 to 0.0622 covering specified percent of
 1455         the total density.
 1456         
 1457         contourLevelAutoAt: Set contour level at specified fraction of the absolute
 1458         maximum value of the isocontour range retrieved from  the cube files. This
 1459         option is only used during the automatic calculations of the contour levels.
 1460         
 1461         displayESP: Display mode for electrostatic potential. Possible values:
 1462         OnTotalDensity or OnSurface. Both displays objects are created
 1463         for molecules.
 1464         
 1465         displayMolecule: Display mode for molecules. Possible values: Sticks or
 1466         BallAndStick. Both displays objects are created for molecules.
 1467         
 1468         displaySphereScale: Sphere scale for displaying molecule during
 1469         BallAndStick display.
 1470         
 1471         displayStickRadius: Stick radius  for displaying molecule during Sticks
 1472         and BallAndStick display.
 1473         
 1474         espRampValues and espRampColors: Electrostatic potential values and
 1475         colors to create ESP ramp for visualizing ESP on total density and surface.
 1476         The ESP values range is automatically retrieved from the ESP cube files.
 1477         The ESP value limit is set to the absolute minimum value of the ESP value
 1478         range. The ESP ramp and color values are set to "-ESPValueLimit 0.0
 1479         ESPValueLimit" and "red, white, blue" by default. For example, ESP ramp
 1480         values and colors are set to "-0.09 0.0 0.09" and "red white blue" for a
 1481         cube file containing minimum and maximum ESP values of -0.09 and
 1482         157.93.
 1483         
 1484         hideHydrogens: Hide hydrogens in molecules. Default: yes. Possible
 1485         values: yes or no.
 1486         
 1487         meshQuality: Mesh quality for meshes to visualize cube files. The
 1488         higher values represents better quality.
 1489         
 1490         meshWidth: Line width for mesh lines to visualize cube files.
 1491         
 1492         surfaceQuality: Surface quality for surfaces to visualize cube files.
 1493         The higher values represents better quality.
 1494         
 1495         surfaceTransparency: Surface transparency for surfaces to visualize cube
 1496         files.
 1497     -q, --quiet <yes or no>  [default: no]
 1498         Use quiet mode. The warning and information messages will not be printed.
 1499     -r, --reference <text>  [default: auto]
 1500         Reference wave function to use for calculating single point energy before
 1501         generating cube files for total ESP and density. Default: RHF or UHF. The
 1502         default values are Restricted Hartree-Fock (RHF) for closed-shell molecules
 1503         with all electrons paired and Unrestricted artree-Fock (UHF) for open-shell
 1504         molecules with unpaired electrons.
 1505         
 1506         The specified value must be a valid Psi4 reference wave function. No validation
 1507         is performed. For example: ROHF, CUHF, RKS, etc.
 1508         
 1509         The spin multiplicity determines the default value of reference wave function
 1510         for input molecules. It is calculated from number of free radical electrons using
 1511         Hund's rule of maximum multiplicity defined as 2S + 1 where S is the total
 1512         electron spin. The total spin is 1/2 the number of free radical electrons in a 
 1513         molecule. The value of 'SpinMultiplicity' molecule property takes precedence
 1514         over the calculated value of spin multiplicity.
 1515     -w, --workingdir <dir>
 1516         Location of working directory which defaults to the current directory.
 1517 
 1518 Examples:
 1519     To generate and visualize ESP based on a single point  energy calculation
 1520     using  B3LYP/6-31G** and B3LYP/6-31+G** for non-sulfur and sulfur
 1521     containing closed-shell molecules in a SD file with 3D structures, and
 1522     write a new PML file, type:
 1523 
 1524         % Psi4VisualizeElectrostaticPotential.py -i Psi4Sample3D.sdf
 1525           -o Psi4Sample3DOut.pml
 1526 
 1527     To run the first example to only generate cube files and skip generation of 
 1528     a PML file to visualize ESP, type:
 1529 
 1530         % Psi4VisualizeElectrostaticPotential.py --mode GenerateCubeFiles
 1531           -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
 1532 
 1533     To run the first example to skip generation of cube files and use existing cube
 1534     files to visualize ESP and write out a PML file, type:
 1535 
 1536         % Psi4VisualizeElectrostaticPotential.py --mode VisualizeCubeFiles
 1537           -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
 1538 
 1539     To run the first example in multiprocessing mode on all available CPUs
 1540     without loading all data into memory and write out a PML file, type:
 1541 
 1542         % Psi4VisualizeElectrostaticPotential.py --mp yes -i Psi4Sample3D.sdf
 1543             -o Psi4Sample3DOut.pml
 1544 
 1545     To run the first example in multiprocessing mode on all available CPUs
 1546     by loading all data into memory and write out a PML file, type:
 1547 
 1548         % Psi4VisualizeElectrostaticPotential.py  --mp yes --mpParams "inputDataMode,
 1549             InMemory" -i Psi4Sample3D.sdf  -o Psi4Sample3DOut.pml
 1550 
 1551     To run the first example in multiprocessing mode on all available CPUs
 1552     without loading all data into memory along with multiple threads for each
 1553     Psi4 run and write out a SD file, type:
 1554 
 1555         % Psi4VisualizeElectrostaticPotential.py --mp yes --psi4RunParams
 1556           "NumThreads,2" -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
 1557 
 1558     To run the first example in using a specific set of parameters to generate and
 1559     visualize ESP and write out a PML file,
 1560     type:
 1561 
 1562         % Psi4VisualizeElectrostaticPotential.py --mode both -m SCF -b aug-cc-pVDZ 
 1563           --psi4CubeFilesParams "gridSpacing, 0.2, gridOverage, 4.0"
 1564           --psi4RunParams "MemoryInGB, 2" --pymolViewParams "contourLevel,0.03,
 1565           contourLevelAutoAt, 0.75,espRampValues, -0.05 0.0 0.05,
 1566           espRampColors, red white blue, hideHydrogens, no"
 1567           -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
 1568 
 1569 Author:
 1570     Manish Sud(msud@san.rr.com)
 1571 
 1572 See also:
 1573     Psi4PerformMinimization.py, Psi4GenerateConformers.py,
 1574     Psi4VisualizeDualDescriptors.py, Psi4VisualizeFrontierOrbitals.py
 1575 
 1576 Copyright:
 1577     Copyright (C) 2024 Manish Sud. All rights reserved.
 1578 
 1579     The functionality available in this script is implemented using Psi4, an
 1580     open source quantum chemistry software package, and RDKit, an open
 1581     source toolkit for cheminformatics developed by Greg Landrum.
 1582 
 1583     This file is part of MayaChemTools.
 1584 
 1585     MayaChemTools is free software; you can redistribute it and/or modify it under
 1586     the terms of the GNU Lesser General Public License as published by the Free
 1587     Software Foundation; either version 3 of the License, or (at your option) any
 1588     later version.
 1589 
 1590 """
 1591 
 1592 if __name__ == "__main__":
 1593     main()