MayaChemTools

    1 #!/bin/env python
    2 #
    3 # File: Psi4VisualizeFrontierOrbitals.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     GenerateAndVisualizeFrontierOrbitals()
   89     
   90     MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
   91     MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
   92 
   93 def GenerateAndVisualizeFrontierOrbitals():
   94     """Generate and visualize frontier molecular orbitals."""
   95     
   96     if OptionsInfo["GenerateCubeFiles"]:
   97         GenerateFrontierOrbitals()
   98         
   99     if OptionsInfo["VisualizeCubeFiles"]:
  100         VisualizeFrontierOrbitals()
  101 
  102 def GenerateFrontierOrbitals():
  103     """Generate cube files for frontier orbitals."""
  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 frontier orbital 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 frontier orbitals 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 frontier orbitals...")
  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 frontier orbitals cube files for molecules using  multiple processes."""
  161 
  162     MiscUtil.PrintInfo("\nGenerating frontier orbitals 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 frontier orbitals."""
  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 = GenerateCubeFilesForFrontierOrbitals(Psi4Handle, WaveFunction, Mol, MolNum)
  290     
  291     # Clean up
  292     PerformPsi4Cleanup(Psi4Handle)
  293 
  294     return (True)
  295 
  296 def GenerateCubeFilesForFrontierOrbitals(Psi4Handle, WaveFunction, Mol, MolNum):
  297     """Generate cube files for frontier orbitals."""
  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": ['FRONTIER_ORBITALS'],
  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, "Psi*.cube"))
  366     if len(Psi4CubeFiles) == 0:
  367         shutil.rmtree(CubeFilesDir)
  368         if not OptionsInfo["QuietMode"]:
  369             MiscUtil.PrintWarning("Failed to create cube files for frontier orbitals...")
  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 VisualizeFrontierOrbitals():
  410     """Visualize cube files for frontier orbitals."""
  411     
  412     MiscUtil.PrintInfo("\nVisualizing cube files for frontier orbitals...")
  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 orbital views...
  465         FirstOrbital = True
  466         for OrbitalID in CubeFilesInfo["OrbitalIDs"]:
  467             FirstSpin = True
  468             for SpinID in CubeFilesInfo["SpinIDs"][OrbitalID]:
  469                 WriteOrbitalSpinView(OutFH, CubeFilesInfo, PyMOLObjectNames, OrbitalID, SpinID, FirstSpin)
  470                 FirstSpin = False
  471         
  472             # Setup orbital level group...
  473             Enable, Action = [False, "open"]
  474             if FirstOrbital:
  475                 FirstOrbital = False
  476                 Enable, Action = [True, "open"]
  477             GenerateAndWritePMLForGroup(OutFH, PyMOLObjectNames["Orbitals"][OrbitalID]["OrbitalGroup"], PyMOLObjectNames["Orbitals"][OrbitalID]["OrbitalGroupMembers"], Enable, Action)
  478 
  479         # Setup mol level group...
  480         Enable, Action = [False, "close"]
  481         if FirstMol:
  482             FirstMol = False
  483             Enable, Action = [True, "open"]
  484         GenerateAndWritePMLForGroup(OutFH, PyMOLObjectNames["MolGroup"], PyMOLObjectNames["MolGroupMembers"], Enable, Action)
  485     
  486     OutFH.close()
  487     
  488     MiscUtil.PrintInfo("\nTotal number of molecules: %d" % MolCount)
  489     MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
  490     MiscUtil.PrintInfo("Number of molecules with missing cube files: %d" % CubeFilesMissingCount)
  491     MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount + CubeFilesMissingCount))
  492 
  493 def WritePMLHeader(OutFH, ScriptName):
  494     """Write out PML header."""
  495 
  496     HeaderInfo = """\
  497 #
  498 # This file is automatically generated by the following Psi4 script available in
  499 # MayaChemTools: %s
  500 #
  501 cmd.reinitialize() """ % (ScriptName)
  502     
  503     OutFH.write("%s\n" % HeaderInfo)
  504 
  505 def WritePyMOLParameters(OutFH):
  506     """Write out PyMOL global parameters."""
  507 
  508     OutFH.write("""\n""\n"Setting up PyMOL gobal parameters..."\n""\n""")
  509     PMLCmds = []
  510     PMLCmds.append("""cmd.set("mesh_quality", %s)""" % (OptionsInfo["PyMOLViewParams"]["MeshQuality"]))
  511     PMLCmds.append("""cmd.set("mesh_width", %.2f)""" % (OptionsInfo["PyMOLViewParams"]["MeshWidth"]))
  512     
  513     PMLCmds.append("""cmd.set("surface_quality", %s)""" % (OptionsInfo["PyMOLViewParams"]["SurfaceQuality"]))
  514     PMLCmds.append("""cmd.set("transparency", %.2f)""" % (OptionsInfo["PyMOLViewParams"]["SurfaceTransparency"]))
  515     PML = "\n".join(PMLCmds)
  516     OutFH.write("%s\n" % PML)
  517 
  518     if OptionsInfo["PyMOLViewParams"]["SetupGlobalVolumeColorRamp"]:
  519         PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
  520         GenerateAndWritePMLForVolumeColorRamp(OutFH, PyMOLViewParams["GlobalVolumeColorRampName"], PyMOLViewParams["ContourLevel1"], PyMOLViewParams["ContourColor1"], PyMOLViewParams["ContourLevel2"], PyMOLViewParams["ContourColor2"], PyMOLViewParams["VolumeContourWindowFactor"], PyMOLViewParams["VolumeColorRampOpacity"])
  521 
  522 def WriteMolView(OutFH, CubeFilesInfo, PyMOLObjectNames):
  523     """Write out PML for viewing molecules."""
  524 
  525     MolName = CubeFilesInfo["MolName"]
  526     MolFile = CubeFilesInfo["MolFile"]
  527     
  528     OutFH.write("""\n""\n"Loading %s and setting up view for molecule..."\n""\n""" % MolFile)
  529     
  530     PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
  531     PMLCmds = []
  532     
  533     # Molecule...
  534     Name = PyMOLObjectNames["Mol"]
  535     PMLCmds.append("""cmd.load("%s", "%s")""" % (MolFile, Name))
  536     PMLCmds.append("""cmd.hide("everything", "%s")""" % (Name))
  537     PMLCmds.append("""util.cbag("%s", _self = cmd)""" % (Name))
  538     PMLCmds.append("""cmd.show("sticks", "%s")""" % (Name))
  539     PMLCmds.append("""cmd.set("stick_radius", %s, "%s")""" % (PyMOLViewParams["DisplayStickRadius"], Name))
  540     if PyMOLViewParams["HideHydrogens"]:
  541         PMLCmds.append("""cmd.hide("(%s and hydro)")""" % (Name))
  542     if re.match("^Sticks$", PyMOLViewParams["DisplayMolecule"]):
  543         PMLCmds.append("""cmd.enable("%s")""" % (Name))
  544     else:
  545         PMLCmds.append("""cmd.disable("%s")""" % (Name))
  546 
  547     # Molecule ball and stick...
  548     Name = PyMOLObjectNames["MolBallAndStick"]
  549     PMLCmds.append("""cmd.load("%s", "%s")""" % (MolFile, Name))
  550     PMLCmds.append("""cmd.hide("everything", "%s")""" % (Name))
  551     PMLCmds.append("""util.cbag("%s", _self = cmd)""" % (Name))
  552     PMLCmds.append("""cmd.show("sphere", "%s")""" % (Name))
  553     PMLCmds.append("""cmd.show("sticks", "%s")""" % (Name))
  554     PMLCmds.append("""cmd.set("sphere_scale", %s, "%s")""" % (PyMOLViewParams["DisplaySphereScale"], Name))
  555     PMLCmds.append("""cmd.set("stick_radius", %s, "%s")""" % (PyMOLViewParams["DisplayStickRadius"], Name))
  556     if PyMOLViewParams["HideHydrogens"]:
  557         PMLCmds.append("""cmd.hide("(%s and hydro)")""" % (Name))
  558     if re.match("^BallAndStick$", PyMOLViewParams["DisplayMolecule"]):
  559         PMLCmds.append("""cmd.enable("%s")""" % (Name))
  560     else:
  561         PMLCmds.append("""cmd.disable("%s")""" % (Name))
  562     
  563     PML = "\n".join(PMLCmds)
  564     OutFH.write("%s\n" % PML)
  565     
  566     # MolAlone group...
  567     GenerateAndWritePMLForGroup(OutFH, PyMOLObjectNames["MolAloneGroup"], PyMOLObjectNames["MolAloneGroupMembers"], True, "open")
  568 
  569 def WriteOrbitalSpinView(OutFH, CubeFilesInfo, PyMOLObjectNames, OrbitalID, SpinID, FirstSpin):
  570     """Write out PML for viewing spins in an orbital."""
  571 
  572     OutFH.write("""\n""\n"Setting up views for spin %s in orbital %s..."\n""\n""" % (SpinID, OrbitalID))
  573     
  574     # Cube...
  575     SpinCubeFile = CubeFilesInfo["FileName"][OrbitalID][SpinID]
  576     SpinCubeName = PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinCube"]
  577     PMLCmds = []
  578     PMLCmds.append("""cmd.load("%s", "%s")""" % (SpinCubeFile, SpinCubeName))
  579     PMLCmds.append("""cmd.disable("%s")""" % (SpinCubeName))
  580     PMLCmds.append("")
  581     PML = "\n".join(PMLCmds)
  582     OutFH.write("%s\n" % PML)
  583 
  584     ContourLevel1 = CubeFilesInfo["ContourLevel1"][OrbitalID][SpinID]
  585     ContourLevel2 = CubeFilesInfo["ContourLevel2"][OrbitalID][SpinID]
  586     
  587     ContourColor1 = CubeFilesInfo["ContourColor1"][OrbitalID][SpinID]
  588     ContourColor2 = CubeFilesInfo["ContourColor2"][OrbitalID][SpinID]
  589     
  590     ContourWindowFactor = CubeFilesInfo["ContourWindowFactor"][OrbitalID][SpinID]
  591     ContourColorOpacity = CubeFilesInfo["ContourColorOpacity"][OrbitalID][SpinID]
  592 
  593     SetupLocalVolumeColorRamp = CubeFilesInfo["SetupLocalVolumeColorRamp"][OrbitalID][SpinID]
  594     VolumeColorRampName = CubeFilesInfo["VolumeColorRampName"][OrbitalID][SpinID]
  595     
  596     # Volume ramp...
  597     if SetupLocalVolumeColorRamp:
  598         GenerateAndWritePMLForVolumeColorRamp(OutFH, VolumeColorRampName, ContourLevel1, ContourColor1, ContourLevel2, ContourColor2, ContourWindowFactor, ContourColorOpacity)
  599     
  600     # Volume...
  601     SpinVolumeName = PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinVolume"]
  602     PMLCmds = []
  603     PMLCmds.append("""cmd.volume("%s", "%s", "%s")""" % (SpinVolumeName, SpinCubeName, VolumeColorRampName))
  604     PMLCmds.append("""cmd.disable("%s")""" % (SpinVolumeName))
  605     PMLCmds.append("")
  606     PML = "\n".join(PMLCmds)
  607     OutFH.write("%s\n" % PML)
  608     
  609     # Mesh...
  610     SpinMeshName = PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshNegative"]
  611     PMLCmds = []
  612     PMLCmds.append("""cmd.isomesh("%s", "%s", "%s")""" % (SpinMeshName, SpinCubeName, ContourLevel1))
  613     PMLCmds.append("""util.color_deep("%s", "%s")""" % (ContourColor1, SpinMeshName))
  614     PMLCmds.append("""cmd.enable("%s")""" % (SpinMeshName))
  615     PMLCmds.append("")
  616     
  617     SpinMeshName = PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshPositive"]
  618     PMLCmds.append("""cmd.isomesh("%s", "%s", "%s")""" % (SpinMeshName, SpinCubeName, ContourLevel2))
  619     PMLCmds.append("""util.color_deep("%s", "%s")""" % (ContourColor2, SpinMeshName))
  620     PMLCmds.append("""cmd.enable("%s")""" % (SpinMeshName))
  621     PMLCmds.append("")
  622     PML = "\n".join(PMLCmds)
  623     OutFH.write("%s\n" % PML)
  624 
  625     GenerateAndWritePMLForGroup(OutFH, PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshGroup"], PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshGroupMembers"], False, "close")
  626 
  627     # Surface...
  628     SpinSurfaceName = PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceNegative"]
  629     PMLCmds = []
  630     PMLCmds.append("""cmd.isosurface("%s", "%s", "%s")""" % (SpinSurfaceName, SpinCubeName, ContourLevel1))
  631     PMLCmds.append("""util.color_deep("%s", "%s")""" % (ContourColor1, SpinSurfaceName))
  632     PMLCmds.append("""cmd.enable("%s")""" % (SpinSurfaceName))
  633     PMLCmds.append("")
  634     
  635     SpinSurfaceName = PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfacePositive"]
  636     PMLCmds.append("""cmd.isosurface("%s", "%s", "%s")""" % (SpinSurfaceName, SpinCubeName, ContourLevel2))
  637     PMLCmds.append("""util.color_deep("%s", "%s")""" % (ContourColor2, SpinSurfaceName))
  638     PMLCmds.append("""cmd.enable("%s")""" % (SpinSurfaceName))
  639     PMLCmds.append("")
  640     PML = "\n".join(PMLCmds)
  641     OutFH.write("%s\n" % PML)
  642     
  643     GenerateAndWritePMLForGroup(OutFH, PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceGroup"], PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceGroupMembers"], True, "close")
  644 
  645     Enable = True if FirstSpin else False
  646     Action = "open" if FirstSpin else "close"
  647     GenerateAndWritePMLForGroup(OutFH, PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroup"], PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroupMembers"], Enable, Action)
  648     
  649 def GenerateAndWritePMLForVolumeColorRamp(OutFH, ColorRampName, ContourLevel1, ContourColor1, ContourLevel2, ContourColor2, ContourWindowFactor, ContourColorOpacity):
  650     """Write out PML for volume color ramp."""
  651 
  652     RampWindowOffset1 = abs(ContourLevel1 * ContourWindowFactor)
  653     RampWindowOffset2 = abs(ContourLevel2 * ContourWindowFactor)
  654     
  655     PML = """\
  656 cmd.volume_ramp_new("%s", [%.4f, "%s", 0.00, %.4f, "%s", %s, %.4f, "%s", 0.00, %4f, "%s", 0.00, %.4f, "%s", %s, %4f, "%s", 0.00])""" % (ColorRampName, (ContourLevel1 - RampWindowOffset1), ContourColor1, ContourLevel1, ContourColor1, ContourColorOpacity, (ContourLevel1 + RampWindowOffset1), ContourColor1, (ContourLevel2 - RampWindowOffset2), ContourColor2, ContourLevel2, ContourColor2, ContourColorOpacity, (ContourLevel2 + RampWindowOffset2), ContourColor2)
  657     
  658     OutFH.write("%s\n" % PML)
  659 
  660 def GenerateAndWritePMLForGroup(OutFH, GroupName, GroupMembersList, Enable, Action):
  661     """Generate and write PML for group."""
  662     
  663     OutFH.write("""\n""\n"Setting up group %s..."\n""\n""" % GroupName)
  664     
  665     PMLCmds = []
  666     
  667     GroupMembers = " ".join(GroupMembersList)
  668     PMLCmds.append("""cmd.group("%s", "%s")""" % (GroupName, GroupMembers))
  669     
  670     if Enable is not None:
  671         if Enable:
  672             PMLCmds.append("""cmd.enable("%s")""" % GroupName)
  673         else:
  674             PMLCmds.append("""cmd.disable("%s")""" % GroupName)
  675     
  676     if Action is not None:
  677         PMLCmds.append("""cmd.group("%s", action="%s")""" % (GroupName, Action))
  678 
  679     PML = "\n".join(PMLCmds)
  680     
  681     OutFH.write("%s\n" % PML)
  682 
  683 def RetrieveMolCubeFilesInfo(Mol, MolNum):
  684     """Retrieve available cube files info for a molecule."""
  685 
  686     CubeFilesInfo = {}
  687     CubeFilesInfo["OrbitalIDs"] = []
  688     CubeFilesInfo["SpinIDs"] = {}
  689     CubeFilesInfo["FileName"] = {}
  690 
  691     CubeFilesInfo["IsocontourRangeMin"] = {}
  692     CubeFilesInfo["IsocontourRangeMax"] = {}
  693     
  694     CubeFilesInfo["ContourLevel1"] = {}
  695     CubeFilesInfo["ContourLevel2"] = {}
  696     CubeFilesInfo["ContourColor1"] = {}
  697     CubeFilesInfo["ContourColor2"] = {}
  698     
  699     CubeFilesInfo["ContourWindowFactor"] = {}
  700     CubeFilesInfo["ContourColorOpacity"] = {}
  701     
  702     CubeFilesInfo["VolumeColorRampName"] = {}
  703     CubeFilesInfo["SetupLocalVolumeColorRamp"] = {}
  704     
  705     # Setup cube mol info...
  706     CubeFilesInfo["MolPrefix"] = SetupMolPrefix(Mol, MolNum)
  707     CubeFilesInfo["MolName"] = SetupMolName(Mol, MolNum)
  708     CubeFilesInfo["MolFile"] = SetupMolFileName(Mol, MolNum)
  709     
  710     MolPrefix = CubeFilesInfo["MolPrefix"]
  711     CubeFilesCount = 0
  712     for OrbitalID in ["HOMO", "LUMO", "DOMO", "LVMO", "SOMO"]:
  713         CubeFiles = glob.glob("%s*%s.cube" % (MolPrefix, OrbitalID))
  714         if not len(CubeFiles):
  715             continue
  716 
  717         CubeFilesInfo["OrbitalIDs"].append(OrbitalID)
  718         CubeFilesInfo["SpinIDs"][OrbitalID] = []
  719         CubeFilesInfo["FileName"][OrbitalID] = {}
  720         
  721         CubeFilesInfo["IsocontourRangeMin"][OrbitalID] = {}
  722         CubeFilesInfo["IsocontourRangeMax"][OrbitalID] = {}
  723         
  724         CubeFilesInfo["ContourLevel1"][OrbitalID] = {}
  725         CubeFilesInfo["ContourLevel2"][OrbitalID] = {}
  726         CubeFilesInfo["ContourColor1"][OrbitalID] = {}
  727         CubeFilesInfo["ContourColor2"][OrbitalID] = {}
  728         
  729         CubeFilesInfo["ContourWindowFactor"][OrbitalID] = {}
  730         CubeFilesInfo["ContourColorOpacity"][OrbitalID] = {}
  731         
  732         CubeFilesInfo["VolumeColorRampName"][OrbitalID] = {}
  733         CubeFilesInfo["SetupLocalVolumeColorRamp"][OrbitalID] = {}
  734         
  735         AlphaSpinCount, BetaSpinCount, SpinCount, CurrentSpinCount = [0] * 4
  736         for CubeFile in CubeFiles:
  737             if re.match("%s_a_" % MolPrefix, CubeFile):
  738                 SpinID = "Alpha"
  739                 AlphaSpinCount += 1
  740                 CurrentSpinCount = AlphaSpinCount
  741             elif re.match("%s_b_" % MolPrefix, CubeFile):
  742                 SpinID = "Beta"
  743                 BetaSpinCount += 1
  744                 CurrentSpinCount = BetaSpinCount
  745             else:
  746                 SpinID = "Spin"
  747                 SpinCount += 1
  748                 CurrentSpinCount = SpinCount
  749 
  750             # Set up a unique spin ID...
  751             if SpinID in CubeFilesInfo["FileName"][OrbitalID]:
  752                 SpinID = "%s_%s" % (SpinID, CurrentSpinCount)
  753                 
  754             CubeFilesCount += 1
  755             CubeFilesInfo["SpinIDs"][OrbitalID].append(SpinID)
  756             CubeFilesInfo["FileName"][OrbitalID][SpinID] = CubeFile
  757             
  758             IsocontourRangeMin, IsocontourRangeMax, ContourLevel1, ContourLevel2 = SetupIsocontourRangeAndContourLevels(CubeFile)
  759             
  760             CubeFilesInfo["IsocontourRangeMin"][OrbitalID][SpinID] = IsocontourRangeMin
  761             CubeFilesInfo["IsocontourRangeMax"][OrbitalID][SpinID] = IsocontourRangeMax
  762             
  763             CubeFilesInfo["ContourLevel1"][OrbitalID][SpinID] = ContourLevel1
  764             CubeFilesInfo["ContourLevel2"][OrbitalID][SpinID] = ContourLevel2
  765             CubeFilesInfo["ContourColor1"][OrbitalID][SpinID] = OptionsInfo["PyMOLViewParams"]["ContourColor1"]
  766             CubeFilesInfo["ContourColor2"][OrbitalID][SpinID] = OptionsInfo["PyMOLViewParams"]["ContourColor2"]
  767             
  768             CubeFilesInfo["ContourWindowFactor"][OrbitalID][SpinID] = OptionsInfo["PyMOLViewParams"]["VolumeContourWindowFactor"]
  769             CubeFilesInfo["ContourColorOpacity"][OrbitalID][SpinID] = OptionsInfo["PyMOLViewParams"]["VolumeColorRampOpacity"]
  770             
  771             VolumeColorRampName = SetupVolumeColorRampName(CubeFilesInfo)
  772             CubeFilesInfo["VolumeColorRampName"][OrbitalID][SpinID] = VolumeColorRampName
  773             CubeFilesInfo["SetupLocalVolumeColorRamp"][OrbitalID][SpinID] = False if OptionsInfo["PyMOLViewParams"]["SetupGlobalVolumeColorRamp"] else True
  774     
  775     Status = True if CubeFilesCount > 0 else False
  776 
  777     return (Status, CubeFilesInfo)
  778 
  779 def SetupVolumeColorRampName(CubeFilesInfo):
  780     """Setup volume color ramp name and status."""
  781     
  782     PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
  783     
  784     # Setup info for generating local volume color ramps for individual cube files...
  785     VolumeColorRampName = PyMOLViewParams["VolumeColorRamp"]
  786     if PyMOLViewParams["VolumeColorRampAuto"]:
  787         VolumeColorRampName = SetupDefaultVolumeRampName()
  788         if PyMOLViewParams["ContourLevel1Auto"] or PyMOLViewParams["ContourLevel2Auto"]:
  789             VolumeColorRampName = "%s_%s" % (CubeFilesInfo["MolPrefix"], VolumeColorRampName)
  790     
  791     return VolumeColorRampName
  792 
  793 def SetupIsocontourRangeAndContourLevels(CubeFileName):
  794     """Setup isocontour range."""
  795 
  796     PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
  797     
  798     # Setup isocontour range and contour levels...
  799     IsocontourRangeMin, IsocontourRangeMax = Psi4Util.RetrieveIsocontourRangeFromCubeFile(CubeFileName)
  800 
  801     DefaultIsocontourRange = 0.06
  802     if IsocontourRangeMin is None:
  803         IsocontourRangeMin = -DefaultIsocontourRange
  804         if not OptionsInfo["QuietMode"]:
  805             MiscUtil.PrintInfo("Failed to retrieve isocontour range from the cube file. Setting min isocontour value to %s..." % (IsocontourRangeMin))
  806     
  807     if IsocontourRangeMax is None:
  808         IsocontourRangeMax = DefaultIsocontourRange
  809         if not OptionsInfo["QuietMode"]:
  810             MiscUtil.PrintInfo("Failed to retrieve isocontour range from the cube file. Setting max isocontour value to %s..." % (IsocontourRangeMax))
  811 
  812     # Setup contour levels...
  813     ContourLevel = max(abs(IsocontourRangeMin), abs(IsocontourRangeMax)) * PyMOLViewParams["ContourLevelAutoAt"]
  814     if ContourLevel >= 0.01:
  815         ContourLevel = float("%.2f" % ContourLevel)
  816     elif ContourLevel >= 0.001:
  817         ContourLevel = float("%.3f" % ContourLevel)
  818     elif ContourLevel >= 0.0001:
  819         ContourLevel = float("%.4f" % ContourLevel)
  820     
  821     ContourLevel1 = -ContourLevel if PyMOLViewParams["ContourLevel1Auto"] else PyMOLViewParams["ContourLevel1"]
  822     ContourLevel2 = ContourLevel if PyMOLViewParams["ContourLevel2Auto"] else PyMOLViewParams["ContourLevel2"]
  823     
  824     if not OptionsInfo["QuietMode"]:
  825         if IsocontourRangeMin is not None and IsocontourRangeMax is not None:
  826             MiscUtil.PrintInfo("CubeFileName: %s; Isocontour range for %s percent of the density: %.4f to %.4f; ContourLevel1: %s; ContourLevel2: %s" % (CubeFileName, (100 * OptionsInfo["Psi4CubeFilesParams"]["IsoContourThreshold"]), IsocontourRangeMin, IsocontourRangeMax, ContourLevel1, ContourLevel2))
  827 
  828     return (IsocontourRangeMin, IsocontourRangeMax, ContourLevel1, ContourLevel2)
  829 
  830 def SetupPyMOLObjectNames(Mol, MolNum, CubeFilesInfo):
  831     """Setup PyMOL object names."""
  832     
  833     PyMOLObjectNames = {}
  834     PyMOLObjectNames["Orbitals"] = {}
  835     PyMOLObjectNames["Spins"] = {}
  836     
  837     SetupPyMOLObjectNamesForMol(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames)
  838     
  839     for OrbitalID in CubeFilesInfo["OrbitalIDs"]:
  840         SetupPyMOLObjectNamesForOrbital(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames, OrbitalID)
  841         for SpinID in CubeFilesInfo["SpinIDs"][OrbitalID]:
  842             SetupPyMOLObjectNamesForSpin(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames, OrbitalID, SpinID)
  843         
  844     return PyMOLObjectNames
  845 
  846 def SetupPyMOLObjectNamesForMol(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames):
  847     """Setup groups and objects for molecule."""
  848 
  849     MolFileRoot = CubeFilesInfo["MolPrefix"]
  850     
  851     MolGroupName = "%s" % MolFileRoot
  852     PyMOLObjectNames["MolGroup"] = MolGroupName
  853     PyMOLObjectNames["MolGroupMembers"] = []
  854 
  855     # Molecule alone group...
  856     MolAloneGroupName = "%s.Molecule" % (MolGroupName)
  857     PyMOLObjectNames["MolAloneGroup"] = MolAloneGroupName
  858     PyMOLObjectNames["MolGroupMembers"].append(MolAloneGroupName)
  859     
  860     PyMOLObjectNames["MolAloneGroupMembers"] = []
  861 
  862     # Molecule...
  863     MolName = "%s.Molecule" % (MolAloneGroupName)
  864     PyMOLObjectNames["Mol"] = MolName
  865     PyMOLObjectNames["MolAloneGroupMembers"].append(MolName)
  866     
  867     # BallAndStick...
  868     MolBallAndStickName = "%s.BallAndStick" % (MolAloneGroupName)
  869     PyMOLObjectNames["MolBallAndStick"] = MolBallAndStickName
  870     PyMOLObjectNames["MolAloneGroupMembers"].append(MolBallAndStickName)
  871     
  872 def SetupPyMOLObjectNamesForOrbital(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames, OrbitalID):
  873     """Setup groups and objects for orbital."""
  874 
  875     PyMOLObjectNames["Orbitals"][OrbitalID] = {}
  876     PyMOLObjectNames["Spins"][OrbitalID] = {}
  877 
  878     MolGroupName = PyMOLObjectNames["MolGroup"]
  879     
  880     # Setup orbital group...
  881     OrbitalGroupName = "%s.%s" % (MolGroupName, OrbitalID)
  882     PyMOLObjectNames["Orbitals"][OrbitalID]["OrbitalGroup"] = OrbitalGroupName
  883     PyMOLObjectNames["MolGroupMembers"].append(OrbitalGroupName)
  884     PyMOLObjectNames["Orbitals"][OrbitalID]["OrbitalGroupMembers"] = []
  885  
  886 def SetupPyMOLObjectNamesForSpin(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames, OrbitalID, SpinID):
  887     """Setup groups and objects for spin."""
  888 
  889     PyMOLObjectNames["Spins"][OrbitalID][SpinID] = {}
  890     
  891     OrbitalGroupName = PyMOLObjectNames["Orbitals"][OrbitalID]["OrbitalGroup"]
  892     
  893     # Setup spin group at orbital level...
  894     SpinGroupName = "%s.%s" % (OrbitalGroupName, SpinID)
  895     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroup"] = SpinGroupName
  896     PyMOLObjectNames["Orbitals"][OrbitalID]["OrbitalGroupMembers"].append(SpinGroupName)
  897     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroupMembers"] = []
  898 
  899     # Cube...
  900     SpinCubeName = "%s.Cube" % SpinGroupName
  901     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinCube"] = SpinCubeName
  902     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroupMembers"].append(SpinCubeName)
  903     
  904     # Volume...
  905     SpinVolumeName = "%s.Volume" % SpinGroupName
  906     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinVolume"] = SpinVolumeName
  907     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroupMembers"].append(SpinVolumeName)
  908     
  909     # Mesh...
  910     SpinMeshGroupName = "%s.Mesh" % SpinGroupName
  911     SpinMeshNegativeName = "%s.Negative_Phase" % SpinMeshGroupName
  912     SpinMeshPositiveName = "%s.Positive_Phase" % SpinMeshGroupName
  913     
  914     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshGroup"] = SpinMeshGroupName
  915     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroupMembers"].append(SpinMeshGroupName)
  916     
  917     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshNegative"] = SpinMeshNegativeName
  918     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshPositive"] = SpinMeshPositiveName
  919     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshGroupMembers"] = []
  920     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshGroupMembers"].append(SpinMeshNegativeName)
  921     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshGroupMembers"].append(SpinMeshPositiveName)
  922     
  923     # Surface...
  924     SpinSurfaceGroupName = "%s.Surface" % SpinGroupName
  925     SpinSurfaceNegativeName = "%s.Negative_Phase" % SpinSurfaceGroupName
  926     SpinSurfacePositiveName = "%s.Positive_Phase" % SpinSurfaceGroupName
  927     
  928     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceGroup"] = SpinSurfaceGroupName
  929     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroupMembers"].append(SpinSurfaceGroupName)
  930     
  931     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceNegative"] = SpinSurfaceNegativeName
  932     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfacePositive"] = SpinSurfacePositiveName
  933     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceGroupMembers"] = []
  934     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceGroupMembers"].append(SpinSurfaceNegativeName)
  935     PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceGroupMembers"].append(SpinSurfacePositiveName)
  936     
  937 def SetupMolFileName(Mol, MolNum):
  938     """Setup SD file name for a molecule."""
  939 
  940     MolPrefix = SetupMolPrefix(Mol, MolNum)
  941     MolFileName = "%s.sdf" % MolPrefix
  942 
  943     return MolFileName
  944     
  945 def SetupMolCubeFileName(Mol, MolNum, Psi4CubeFileName):
  946     """Setup cube file name for a molecule."""
  947 
  948     FileDir, FileName, FileExt = MiscUtil.ParseFileName(Psi4CubeFileName)
  949     CubeFileName = "%s.%s" % (FileName, FileExt)
  950 
  951     # Replace Psi by MolPrefix...
  952     MolPrefix = SetupMolPrefix(Mol, MolNum)
  953     CubeFileName = re.sub("^Psi", MolPrefix, CubeFileName)
  954     
  955     # Replace prime (') and dprime ('') in the cube file names..
  956     CubeFileName = re.sub("\"", "dblprime", CubeFileName)
  957     CubeFileName = re.sub("\'", "prime", CubeFileName)
  958 
  959     return CubeFileName
  960     
  961 def SetupMolPrefix(Mol, MolNum):
  962     """Get molecule prefix for generating files and directories."""
  963 
  964     MolNamePrefix = ''
  965     if Mol.HasProp("_Name"):
  966         MolNamePrefix = re.sub("[^a-zA-Z0-9]", "_", Mol.GetProp("_Name"))
  967     
  968     MolNumPrefix = "Mol%s" % MolNum
  969 
  970     if OptionsInfo["UseMolNumPrefix"] and OptionsInfo["UseMolNamePrefix"]:
  971         MolPrefix = MolNumPrefix
  972         if len(MolNamePrefix):
  973             MolPrefix = "%s_%s" % (MolNumPrefix, MolNamePrefix)
  974     elif OptionsInfo["UseMolNamePrefix"]:
  975         MolPrefix = MolNamePrefix if len(MolNamePrefix) else MolNumPrefix
  976     elif OptionsInfo["UseMolNumPrefix"]:
  977         MolPrefix = MolNumPrefix
  978     
  979     return MolPrefix
  980 
  981 def SetupMolName(Mol, MolNum):
  982     """Get molecule name."""
  983 
  984     # Test for creating PyMOL object name for molecule...
  985     MolNamePrefix = ''
  986     if Mol.HasProp("_Name"):
  987         MolName = re.sub("[^a-zA-Z0-9]", "_", Mol.GetProp("_Name"))
  988     else:
  989         MolName = "Mol%s" % MolNum
  990     
  991     return MolName
  992 
  993 def SetupDefaultVolumeRampName():
  994     """Setup a default name for a volume ramp."""
  995 
  996     return "psi4_cube_mo"
  997 
  998 def SetupPsi4Mol(Psi4Handle, Mol, MolCount = None):
  999     """Setup a Psi4 molecule object."""
 1000     
 1001     MolGeometry = RDKitUtil.GetPsi4XYZFormatString(Mol, NoCom = True, NoReorient = True)
 1002     
 1003     try:
 1004         Psi4Mol = Psi4Handle.geometry(MolGeometry)
 1005     except Exception as ErrMsg:
 1006         Psi4Mol = None
 1007         if not OptionsInfo["QuietMode"]:
 1008             MiscUtil.PrintWarning("Failed to create Psi4 molecule from geometry string: %s\n" % ErrMsg)
 1009             MolName = RDKitUtil.GetMolName(Mol, MolCount)
 1010             MiscUtil.PrintWarning("Ignoring molecule: %s" % MolName)
 1011     
 1012     return Psi4Mol
 1013 
 1014 def PerformPsi4Cleanup(Psi4Handle):
 1015     """Perform clean up."""
 1016     
 1017     # Clean up after Psi4 run...
 1018     Psi4Handle.core.clean()
 1019     
 1020     # Clean up any leftover scratch files...
 1021     if OptionsInfo["MPMode"]:
 1022         Psi4Util.RemoveScratchFiles(Psi4Handle, OptionsInfo["Psi4RunParams"]["OutputFile"])
 1023 
 1024 def CheckAndValidateMolecule(Mol, MolCount = None):
 1025     """Validate molecule for Psi4 calculations."""
 1026     
 1027     if Mol is None:
 1028         if not OptionsInfo["QuietMode"]:
 1029             MiscUtil.PrintInfo("\nProcessing molecule number %s..." % MolCount)
 1030         return False
 1031     
 1032     MolName = RDKitUtil.GetMolName(Mol, MolCount)
 1033     if not OptionsInfo["QuietMode"]:
 1034         MiscUtil.PrintInfo("\nProcessing molecule %s..." % MolName)
 1035     
 1036     if RDKitUtil.IsMolEmpty(Mol):
 1037         if not OptionsInfo["QuietMode"]:
 1038             MiscUtil.PrintWarning("Ignoring empty molecule: %s\n" % MolName)
 1039         return False
 1040     
 1041     if not RDKitUtil.ValidateElementSymbols(RDKitUtil.GetAtomSymbols(Mol)):
 1042         if not OptionsInfo["QuietMode"]:
 1043             MiscUtil.PrintWarning("Ignoring molecule containing invalid element symbols: %s\n" % MolName)
 1044         return False
 1045     
 1046     # Check for 3D flag...
 1047     if not Mol.GetConformer().Is3D():
 1048         if not OptionsInfo["QuietMode"]:
 1049             MiscUtil.PrintWarning("3D tag is not set for molecule: %s\n" % MolName)
 1050     
 1051     # Check for missing hydrogens...
 1052     if RDKitUtil.AreHydrogensMissingInMolecule(Mol):
 1053         if not OptionsInfo["QuietMode"]:
 1054             MiscUtil.PrintWarning("Missing hydrogens in molecule: %s\n" % MolName)
 1055     
 1056     return True
 1057 
 1058 def SetupMethodNameAndBasisSet(Mol):
 1059     """Setup method name and basis set."""
 1060     
 1061     MethodName = OptionsInfo["MethodName"]
 1062     if OptionsInfo["MethodNameAuto"]:
 1063         MethodName = "B3LYP"
 1064     
 1065     BasisSet = OptionsInfo["BasisSet"]
 1066     if OptionsInfo["BasisSetAuto"]:
 1067         BasisSet = "6-31+G**" if RDKitUtil.IsAtomSymbolPresentInMol(Mol, "S") else "6-31G**"
 1068     
 1069     return (MethodName, BasisSet)
 1070 
 1071 def SetupReferenceWavefunction(Mol):
 1072     """Setup reference wavefunction."""
 1073     
 1074     Reference = OptionsInfo["Reference"]
 1075     if OptionsInfo["ReferenceAuto"]:
 1076         Reference = 'UHF' if (RDKitUtil.GetSpinMultiplicity(Mol) > 1) else 'RHF'
 1077     
 1078     return Reference
 1079 
 1080 def CheckAndSetupOutfilesDir():
 1081     """Check and setup outfiles directory."""
 1082     
 1083     if OptionsInfo["GenerateCubeFiles"]:
 1084         if os.path.isdir(OptionsInfo["OutfilesDir"]):
 1085             if not Options["--overwrite"]:
 1086                 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"]))
 1087     
 1088     if OptionsInfo["VisualizeCubeFiles"]:
 1089         if not Options["--overwrite"]:
 1090             if  os.path.exists(os.path.join(OptionsInfo["OutfilesDir"], OptionsInfo["Outfile"])):
 1091                 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"]))
 1092                     
 1093         if not OptionsInfo["GenerateCubeFiles"]:
 1094             if not os.path.isdir(OptionsInfo["OutfilesDir"]):
 1095                 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"]))
 1096             
 1097             CubeFiles = glob.glob(os.path.join(OptionsInfo["OutfilesDir"], "*.cube"))
 1098             if not len(CubeFiles):
 1099                 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"]))
 1100     
 1101     OutfilesDir = OptionsInfo["OutfilesDir"]
 1102     if OptionsInfo["GenerateCubeFiles"]:
 1103         if not os.path.isdir(OutfilesDir):
 1104             MiscUtil.PrintInfo("\nCreating directory %s..." % OutfilesDir)
 1105             os.mkdir(OutfilesDir)
 1106 
 1107     MiscUtil.PrintInfo("\nChanging directory to %s..." % OutfilesDir)
 1108     os.chdir(OutfilesDir)
 1109 
 1110 def ProcessOptions():
 1111     """Process and validate command line arguments and options."""
 1112     
 1113     MiscUtil.PrintInfo("Processing options...")
 1114     
 1115     # Validate options...
 1116     ValidateOptions()
 1117 
 1118     OptionsInfo["Infile"] = Options["--infile"]
 1119     OptionsInfo["InfilePath"] = os.path.abspath(Options["--infile"])
 1120     
 1121     ParamsDefaultInfoOverride = {"RemoveHydrogens": False}
 1122     OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters("--infileParams", Options["--infileParams"], InfileName = Options["--infile"], ParamsDefaultInfo = ParamsDefaultInfoOverride)
 1123 
 1124     Outfile = Options["--outfile"]
 1125     FileDir, FileName, FileExt = MiscUtil.ParseFileName(Outfile)
 1126     
 1127     OptionsInfo["Outfile"] = Outfile
 1128     OptionsInfo["OutfileRoot"] = FileName
 1129     OptionsInfo["OutfileExt"] = FileExt
 1130     
 1131     OutfilesDir = Options["--outfilesDir"]
 1132     if re.match("^auto$", OutfilesDir, re.I):
 1133         OutfilesDir = "%s_FrontierOrbitals" % OptionsInfo["OutfileRoot"]
 1134     OptionsInfo["OutfilesDir"] = OutfilesDir
 1135     
 1136     OptionsInfo["Overwrite"] = Options["--overwrite"]
 1137     
 1138     # Method, basis set, and reference wavefunction...
 1139     OptionsInfo["BasisSet"] = Options["--basisSet"]
 1140     OptionsInfo["BasisSetAuto"] = True if re.match("^auto$", Options["--basisSet"], re.I) else False
 1141     
 1142     OptionsInfo["MethodName"] = Options["--methodName"]
 1143     OptionsInfo["MethodNameAuto"] = True if re.match("^auto$", Options["--methodName"], re.I) else False
 1144     
 1145     OptionsInfo["Reference"] = Options["--reference"]
 1146     OptionsInfo["ReferenceAuto"] = True if re.match("^auto$", Options["--reference"], re.I) else False
 1147     
 1148     # Run, options, and cube files parameters...
 1149     OptionsInfo["Psi4OptionsParams"] = Psi4Util.ProcessPsi4OptionsParameters("--psi4OptionsParams", Options["--psi4OptionsParams"])
 1150     OptionsInfo["Psi4RunParams"] = Psi4Util.ProcessPsi4RunParameters("--psi4RunParams", Options["--psi4RunParams"], InfileName = OptionsInfo["Infile"])
 1151 
 1152     ParamsDefaultInfoOverride = {}
 1153     OptionsInfo["Psi4CubeFilesParams"] = Psi4Util.ProcessPsi4CubeFilesParameters("--psi4CubeFilesParams", Options["--psi4CubeFilesParams"], ParamsDefaultInfo = ParamsDefaultInfoOverride)
 1154     
 1155     ParamsDefaultInfoOverride = {"ContourColor1": "red", "ContourColor2": "blue", "ContourLevel1": "auto", "ContourLevel2": "auto", "ContourLevelAutoAt": 0.75, "DisplayMolecule": "BallAndStick", "DisplaySphereScale": 0.2, "DisplayStickRadius": 0.1, "HideHydrogens": True,  "MeshWidth": 0.5, "MeshQuality": 2, "SurfaceQuality": 2, "SurfaceTransparency": 0.25, "VolumeColorRamp": "auto", "VolumeColorRampOpacity": 0.2, "VolumeContourWindowFactor": 0.05}
 1156     OptionsInfo["PyMOLViewParams"] = MiscUtil.ProcessOptionPyMOLCubeFileViewParameters("--pymolViewParams", Options["--pymolViewParams"], ParamsDefaultInfo = ParamsDefaultInfoOverride)
 1157     
 1158     SetupGlobalVolumeColorRamp = False
 1159     GlobalVolumeColorRampName = OptionsInfo["PyMOLViewParams"]["VolumeColorRamp"]
 1160     if OptionsInfo["PyMOLViewParams"]["VolumeColorRampAuto"]:
 1161         GlobalVolumeColorRampName = SetupDefaultVolumeRampName()
 1162         if not (OptionsInfo["PyMOLViewParams"]["ContourLevel1Auto"] or OptionsInfo["PyMOLViewParams"]["ContourLevel2Auto"]):
 1163             SetupGlobalVolumeColorRamp = True
 1164     OptionsInfo["PyMOLViewParams"]["GlobalVolumeColorRampName"] = GlobalVolumeColorRampName
 1165     OptionsInfo["PyMOLViewParams"]["SetupGlobalVolumeColorRamp"] = SetupGlobalVolumeColorRamp
 1166 
 1167     Mode = Options["--mode"]
 1168     if re.match("^GenerateCubeFiles$", Mode, re.I):
 1169         GenerateCubeFiles = True
 1170         VisualizeCubeFiles = False
 1171     elif re.match("^VisualizeCubeFiles$", Mode, re.I):
 1172         GenerateCubeFiles = False
 1173         VisualizeCubeFiles = True
 1174     else:
 1175         GenerateCubeFiles = True
 1176         VisualizeCubeFiles = True
 1177     OptionsInfo["Mode"] = Mode
 1178     OptionsInfo["GenerateCubeFiles"] = GenerateCubeFiles
 1179     OptionsInfo["VisualizeCubeFiles"] = VisualizeCubeFiles
 1180     
 1181     OptionsInfo["MPMode"] = True if re.match("^yes$", Options["--mp"], re.I) else False
 1182     OptionsInfo["MPParams"] = MiscUtil.ProcessOptionMultiprocessingParameters("--mpParams", Options["--mpParams"])
 1183     
 1184     OutfilesMolPrefix = Options["--outfilesMolPrefix"]
 1185     if re.match("^MolName$", OutfilesMolPrefix, re.I):
 1186         UseMolNamePrefix = True
 1187         UseMolNumPrefix = False
 1188     elif re.match("^MolNum$", OutfilesMolPrefix, re.I):
 1189         UseMolNamePrefix = False
 1190         UseMolNumPrefix = True
 1191     else:
 1192         UseMolNamePrefix = True
 1193         UseMolNumPrefix = True
 1194     OptionsInfo["OutfilesMolPrefix"] = OutfilesMolPrefix
 1195     OptionsInfo["UseMolNamePrefix"] = UseMolNamePrefix
 1196     OptionsInfo["UseMolNumPrefix"] = UseMolNumPrefix
 1197     
 1198     OptionsInfo["QuietMode"] = True if re.match("^yes$", Options["--quiet"], re.I) else False
 1199     
 1200     CheckAndSetupOutfilesDir()
 1201 
 1202 def RetrieveOptions():
 1203     """Retrieve command line arguments and options."""
 1204     
 1205     # Get options...
 1206     global Options
 1207     Options = docopt(_docoptUsage_)
 1208     
 1209     # Set current working directory to the specified directory...
 1210     WorkingDir = Options["--workingdir"]
 1211     if WorkingDir:
 1212         os.chdir(WorkingDir)
 1213     
 1214     # Handle examples option...
 1215     if "--examples" in Options and Options["--examples"]:
 1216         MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
 1217         sys.exit(0)
 1218 
 1219 def ValidateOptions():
 1220     """Validate option values."""
 1221     
 1222     MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
 1223     MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "sdf sd mol")
 1224 
 1225     MiscUtil.ValidateOptionFileExt("-o, --outfile", Options["--outfile"], "pml")
 1226     
 1227     MiscUtil.ValidateOptionTextValue("--mode", Options["--mode"], "GenerateCubeFiles VisualizeCubeFiles Both")
 1228     MiscUtil.ValidateOptionTextValue("--mp", Options["--mp"], "yes no")
 1229     
 1230     MiscUtil.ValidateOptionTextValue("--outfilesMolPrefix", Options["--outfilesMolPrefix"], "MolNum MolName Both")
 1231     MiscUtil.ValidateOptionTextValue("-q, --quiet", Options["--quiet"], "yes no")
 1232  
 1233 # Setup a usage string for docopt...
 1234 _docoptUsage_ = """
 1235 Psi4VisualizeFrontierOrbitals.py - Visualize frontier molecular orbitals
 1236 
 1237 Usage:
 1238     Psi4VisualizeFrontierOrbitals.py [--basisSet <text>] [--infileParams <Name,Value,...>] [--methodName <text>]
 1239                                      [--mode <GenerateCubeFiles, VisualizeCubeFiles, Both>] [--mp <yes or no>] [--mpParams <Name, Value,...>]
 1240                                      [--outfilesDir <text>] [--outfilesMolPrefix <MolNum, MolName, Both> ] [--overwrite]
 1241                                      [--psi4CubeFilesParams <Name,Value,...>] [--psi4OptionsParams <Name,Value,...>]
 1242                                      [--psi4RunParams <Name,Value,...>] [--pymolViewParams <Name,Value,...>] [--quiet <yes or no>]
 1243                                      [--reference <text>] [-w <dir>] -i <infile> -o <outfile> 
 1244     Psi4VisualizeFrontierOrbitals.py -h | --help | -e | --examples
 1245 
 1246 Description:
 1247     Generate and visualize frontier molecular orbitals for molecules in input
 1248     file. A set of cube files, corresponding to frontier orbitals, is generated for 
 1249     molecules. The cube files are used to create a PyMOL visualization file for
 1250     viewing volumes, meshes, and surfaces representing frontier molecular
 1251     orbitals. An option is available to skip the generation of new cube files
 1252     and use existing cube files to visualize frontier molecular orbitals.
 1253     
 1254     A Psi4 XYZ format geometry string is automatically generated for each molecule
 1255     in input file. It contains atom symbols and 3D coordinates for each atom in a
 1256     molecule. In addition, the formal charge and spin multiplicity are present in the
 1257     the geometry string. These values are either retrieved from molecule properties
 1258     named 'FormalCharge' and 'SpinMultiplicty' or dynamically calculated for a
 1259     molecule.
 1260     
 1261     A set of cube and SD output files is generated for each molecule in input file
 1262     as shown below:
 1263         
 1264         Ouput dir: <OutfileRoot>_FrontierOrbitals or <OutfilesDir>
 1265         
 1266         Closed-shell systems:
 1267         
 1268         <MolIDPrefix>.sdf
 1269         <MolIDPrefix>*HOMO.cube
 1270         <MolIDPrefix>*LUMO.cube
 1271         
 1272         Open-shell systems:
 1273         
 1274         <MolIDPrefix>.sdf
 1275         <MolIDPrefix>*DOMO.cube
 1276         <MolIDPrefix>*LVMO.cube
 1277         <MolIDPrefix>*SOMO.cube
 1278         
 1279         <MolIDPrefix>: Mol<Num>_<MolName>, Mol<Num>, or <MolName>
 1280         
 1281         Abbreviations:
 1282         
 1283         HOMO: Highest Occupied Molecular Orbital
 1284         LUMO: Lowest Unoccupied Molecular Orbital
 1285     
 1286         DOMO: Double Occupied Molecular Orbital
 1287         SOMO: Singly Occupied Molecular Orbital
 1288         LVMO: Lowest Virtual (Unoccupied) Molecular Orbital
 1289         
 1290     In addition, a <OutfileRoot>.pml is generated containing  frontier molecular
 1291     orbitals for all molecules in input file.
 1292     
 1293     The supported input file formats are: Mol (.mol), SD (.sdf, .sd)
 1294      
 1295     The supported output file formats are: PyMOL script file (.pml)
 1296     
 1297     A variety of PyMOL groups and objects are  created for visualization of frontier
 1298     molecular orbitals as shown below:
 1299         
 1300         Closed-shell systems:
 1301         
 1302         <MoleculeID>
 1303             .Molecule
 1304                 .Molecule
 1305                 .BallAndStick
 1306             .HOMO
 1307                 .Alpha
 1308                     .Cube
 1309                     .Volume
 1310                     .Mesh
 1311                         .Negative_Phase
 1312                         .Positive_Phase
 1313                     .Surface
 1314                         .Negative_Phase
 1315                         .Positive_Phase
 1316             .LUMO
 1317                 .Alpha
 1318                     .Cube
 1319                     .Volume
 1320                     .Mesh
 1321                         .Negative_Phase
 1322                         .Positive_Phase
 1323                     .Surface
 1324                         .Negative_Phase
 1325                         .Positive_Phase
 1326         <MoleculeID>
 1327             .Molecule
 1328                 ... ... ...
 1329             .HOMO
 1330                 ... ... ...
 1331             .LUMO
 1332                 ... ... ...
 1333         
 1334         Open-shell systems:
 1335         
 1336         <MoleculeID>
 1337             .Molecule
 1338                 .Molecule
 1339                 .BallAndStick
 1340             .DOMO
 1341                 .Alpha
 1342                     .Cube
 1343                     .Volume
 1344                     .Mesh
 1345                         .Negative_Phase
 1346                         .Positive_Phase
 1347                     .Surface
 1348                         .Negative_Phase
 1349                         .Positive_Phase
 1350                 .Beta
 1351                     .Cube
 1352                     .Volume
 1353                     .Mesh
 1354                         .Negative_Phase
 1355                         .Positive_Phase
 1356                     .Surface
 1357                         .Negative_Phase
 1358                         .Positive_Phase
 1359             .LVMO
 1360                 .Alpha
 1361                     .Cube
 1362                     .Volume
 1363                     .Mesh
 1364                         .Negative_Phase
 1365                         .Positive_Phase
 1366                     .Surface
 1367                         .Negative_Phase
 1368                         .Positive_Phase
 1369                 .Beta
 1370                     .Cube
 1371                     .Volume
 1372                     .Mesh
 1373                         .Negative_Phase
 1374                         .Positive_Phase
 1375                     .Surface
 1376                         .Negative_Phase
 1377                         .Positive_Phase
 1378             .SOMO
 1379                 .Alpha
 1380                     .Cube
 1381                     .Volume
 1382                     .Mesh
 1383                         .Negative_Phase
 1384                         .Positive_Phase
 1385                     .Surface
 1386                         .Negative_Phase
 1387                         .Positive_Phase
 1388                 .Alpha_<Num>
 1389                     ... ... ...
 1390         <MoleculeID>
 1391             .Molecule
 1392                 ... ... ...
 1393             .DOMO
 1394                 ... ... ...
 1395             .LVMO
 1396                 ... ... ...
 1397             .SOMO
 1398                 ... ... ...
 1399 
 1400 Options:
 1401     -b, --basisSet <text>  [default: auto]
 1402         Basis set to use for calculating single point energy before generating
 1403         cube files for frontier molecular orbitals. Default: 6-31+G** for sulfur
 1404         containing molecules; Otherwise, 6-31G** [ Ref 150 ]. The specified 
 1405         value must be a valid Psi4 basis set. No validation is performed.
 1406         
 1407         The following list shows a representative sample of basis sets available
 1408         in Psi4:
 1409             
 1410             STO-3G, 6-31G, 6-31+G, 6-31++G, 6-31G*, 6-31+G*,  6-31++G*, 
 1411             6-31G**, 6-31+G**, 6-31++G**, 6-311G, 6-311+G, 6-311++G,
 1412             6-311G*, 6-311+G*, 6-311++G*, 6-311G**, 6-311+G**, 6-311++G**,
 1413             cc-pVDZ, cc-pCVDZ, aug-cc-pVDZ, cc-pVDZ-DK, cc-pCVDZ-DK, def2-SVP,
 1414             def2-SVPD, def2-TZVP, def2-TZVPD, def2-TZVPP, def2-TZVPPD
 1415             
 1416     -e, --examples
 1417         Print examples.
 1418     -h, --help
 1419         Print this help message.
 1420     -i, --infile <infile>
 1421         Input file name.
 1422     --infileParams <Name,Value,...>  [default: auto]
 1423         A comma delimited list of parameter name and value pairs for reading
 1424         molecules from files. The supported parameter names for different file
 1425         formats, along with their default values, are shown below:
 1426             
 1427             SD, MOL: removeHydrogens,no,sanitize,yes,strictParsing,yes
 1428             
 1429     -m, --methodName <text>  [default: auto]
 1430         Method to use for calculating single point energy before generating
 1431         cube files for frontier molecular orbitals. Default: B3LYP [ Ref 150 ].
 1432         The specified value must be a valid Psi4 method name. No validation is
 1433         performed.
 1434         
 1435         The following list shows a representative sample of methods available
 1436         in Psi4:
 1437             
 1438             B1LYP, B2PLYP, B2PLYP-D3BJ, B2PLYP-D3MBJ, B3LYP, B3LYP-D3BJ,
 1439             B3LYP-D3MBJ, CAM-B3LYP, CAM-B3LYP-D3BJ, HF, HF-D3BJ,  HF3c, M05,
 1440             M06, M06-2x, M06-HF, M06-L, MN12-L, MN15, MN15-D3BJ,PBE, PBE0,
 1441             PBEH3c, PW6B95, PW6B95-D3BJ, WB97, WB97X, WB97X-D, WB97X-D3BJ
 1442             
 1443     --mode <GenerateCubeFiles, VisualizeCubeFiles, or Both>  [default: Both]
 1444         Generate and visualize cube files for frontier molecular orbitals. The 
 1445         'VisualizeCubes' value skips the generation of new cube files and uses
 1446          existing cube files for visualization of molecular orbitals. Multiprocessing
 1447          is not supported during 'VisualizeCubeFiles' value of '--mode' option.
 1448     --mp <yes or no>  [default: no]
 1449         Use multiprocessing.
 1450          
 1451         By default, input data is retrieved in a lazy manner via mp.Pool.imap()
 1452         function employing lazy RDKit data iterable. This allows processing of
 1453         arbitrary large data sets without any additional requirements memory.
 1454         
 1455         All input data may be optionally loaded into memory by mp.Pool.map()
 1456         before starting worker processes in a process pool by setting the value
 1457         of 'inputDataMode' to 'InMemory' in '--mpParams' option.
 1458         
 1459         A word to the wise: The default 'chunkSize' value of 1 during 'Lazy' input
 1460         data mode may adversely impact the performance. The '--mpParams' section
 1461         provides additional information to tune the value of 'chunkSize'.
 1462     --mpParams <Name,Value,...>  [default: auto]
 1463         A comma delimited list of parameter name and value pairs to configure
 1464         multiprocessing.
 1465         
 1466         The supported parameter names along with their default and possible
 1467         values are shown below:
 1468         
 1469             chunkSize, auto
 1470             inputDataMode, Lazy   [ Possible values: InMemory or Lazy ]
 1471             numProcesses, auto   [ Default: mp.cpu_count() ]
 1472         
 1473         These parameters are used by the following functions to configure and
 1474         control the behavior of multiprocessing: mp.Pool(), mp.Pool.map(), and
 1475         mp.Pool.imap().
 1476         
 1477         The chunkSize determines chunks of input data passed to each worker
 1478         process in a process pool by mp.Pool.map() and mp.Pool.imap() functions.
 1479         The default value of chunkSize is dependent on the value of 'inputDataMode'.
 1480         
 1481         The mp.Pool.map() function, invoked during 'InMemory' input data mode,
 1482         automatically converts RDKit data iterable into a list, loads all data into
 1483         memory, and calculates the default chunkSize using the following method
 1484         as shown in its code:
 1485         
 1486             chunkSize, extra = divmod(len(dataIterable), len(numProcesses) * 4)
 1487             if extra: chunkSize += 1
 1488         
 1489         For example, the default chunkSize will be 7 for a pool of 4 worker processes
 1490         and 100 data items.
 1491         
 1492         The mp.Pool.imap() function, invoked during 'Lazy' input data mode, employs
 1493         'lazy' RDKit data iterable to retrieve data as needed, without loading all the
 1494         data into memory. Consequently, the size of input data is not known a priori.
 1495         It's not possible to estimate an optimal value for the chunkSize. The default 
 1496         chunkSize is set to 1.
 1497         
 1498         The default value for the chunkSize during 'Lazy' data mode may adversely
 1499         impact the performance due to the overhead associated with exchanging
 1500         small chunks of data. It is generally a good idea to explicitly set chunkSize to
 1501         a larger value during 'Lazy' input data mode, based on the size of your input
 1502         data and number of processes in the process pool.
 1503         
 1504         The mp.Pool.map() function waits for all worker processes to process all
 1505         the data and return the results. The mp.Pool.imap() function, however,
 1506         returns the the results obtained from worker processes as soon as the
 1507         results become available for specified chunks of data.
 1508         
 1509         The order of data in the results returned by both mp.Pool.map() and 
 1510         mp.Pool.imap() functions always corresponds to the input data.
 1511     -o, --outfile <outfile>
 1512         Output file name for PyMOL PML file. The PML output file, along with cube
 1513         files, is generated in a local directory corresponding to '--outfilesDir'
 1514         option.
 1515     --outfilesDir <text>  [default: auto]
 1516         Directory name containing PML and cube files. Default:
 1517        <OutfileRoot>_FrontierOrbitals. This directory must be present during
 1518         'VisualizeCubeFiles' value of '--mode' option.
 1519     --outfilesMolPrefix <MolNum, MolName, Both>  [default: Both]
 1520         Molecule prefix to use for the names of cube files. Possible values:
 1521         MolNum, MolName, or Both. By default, both molecule number and name
 1522         are used. The format of molecule prefix is as follows: MolNum - Mol<Num>;
 1523         MolName - <MolName>, Both: Mol<Num>_<MolName>. Empty molecule names
 1524         are ignored. Molecule numbers are used for empty molecule names.
 1525     --overwrite
 1526         Overwrite existing files.
 1527     --psi4CubeFilesParams <Name,Value,...>  [default: auto]
 1528         A comma delimited list of parameter name and value pairs for generating
 1529         Psi4 cube files.
 1530         
 1531         The supported parameter names along with their default and possible
 1532         values are shown below:
 1533              
 1534             gridSpacing, 0.2, gridOverage, 4.0, isoContourThreshold, 0.85
 1535              
 1536         gridSpacing: Grid spacing for generating cube files. Units: Bohr. A higher
 1537         value reduces the size of the cube files on the disk. This option corresponds
 1538         to Psi4 option CUBIC_GRID_SPACING.
 1539         
 1540         gridOverage: Grid overage for generating cube files. Units: Bohr.This option
 1541         corresponds to Psi4 option CUBIC_GRID_OVERAGE.
 1542         
 1543         isoContourThreshold: IsoContour values for generating cube files that capture
 1544         specified percent of the probability density using the least amount of grid
 1545         points. Default: 0.85 (85%). This option corresponds to Psi4 option
 1546         CUBEPROP_ISOCONTOUR_THRESHOLD.
 1547     --psi4OptionsParams <Name,Value,...>  [default: none]
 1548         A comma delimited list of Psi4 option name and value pairs for setting
 1549         global and module options. The names are 'option_name' for global options
 1550         and 'module_name__option_name' for options local to a module. The
 1551         specified option names must be valid Psi4 names. No validation is
 1552         performed.
 1553         
 1554         The specified option name and  value pairs are processed and passed to
 1555         psi4.set_options() as a dictionary. The supported value types are float,
 1556         integer, boolean, or string. The float value string is converted into a float.
 1557         The valid values for a boolean string are yes, no, true, false, on, or off. 
 1558     --psi4RunParams <Name,Value,...>  [default: auto]
 1559         A comma delimited list of parameter name and value pairs for configuring
 1560         Psi4 jobs.
 1561         
 1562         The supported parameter names along with their default and possible
 1563         values are shown below:
 1564              
 1565             MemoryInGB, 1
 1566             NumThreads, 1
 1567             OutputFile, auto   [ Possible  values: stdout, quiet, or FileName ]
 1568             ScratchDir, auto   [ Possivle values: DirName]
 1569             RemoveOutputFile, yes   [ Possible values: yes, no, true, or false]
 1570             
 1571         These parameters control the runtime behavior of Psi4.
 1572         
 1573         The default file name for 'OutputFile' is <InFileRoot>_Psi4.out. The PID
 1574         is appended to output file name during multiprocessing as shown:
 1575         <InFileRoot>_Psi4_<PIDNum>.out. The 'stdout' value for 'OutputType'
 1576         sends Psi4 output to stdout. The 'quiet' or 'devnull' value suppresses
 1577         all Psi4 output.
 1578         
 1579         The default 'Yes' value of 'RemoveOutputFile' option forces the removal
 1580         of any existing Psi4 before creating new files to append output from
 1581         multiple Psi4 runs.
 1582         
 1583         The option 'ScratchDir' is a directory path to the location of scratch
 1584         files. The default value corresponds to Psi4 default. It may be used to
 1585         override the deafult path.
 1586     --pymolViewParams <Name,Value,...>  [default: auto]
 1587         A comma delimited list of parameter name and value pairs for visualizing
 1588         cube files in PyMOL.
 1589             
 1590             contourColor1, red, contourColor2, blue,
 1591             contourLevel1, auto, contourLevel2, auto,
 1592             contourLevelAutoAt, 0.75,
 1593             displayMolecule, BallAndStick, displaySphereScale, 0.2,
 1594             displayStickRadius, 0.1, hideHydrogens, yes,
 1595             meshWidth, 0.5, meshQuality, 2,
 1596             surfaceQuality, 2, surfaceTransparency, 0.25,
 1597             volumeColorRamp, auto, volumeColorRampOpacity,0.2
 1598             volumeContourWindowFactor,0.05
 1599             
 1600         contourColor1 and contourColor2: Color to use for visualizing volumes,
 1601         meshes, and surfaces corresponding to the negative and positive values
 1602         in cube files. The specified values must be valid PyMOL color names. No
 1603         validation is performed.
 1604         
 1605         contourLevel1 and contourLevel2: Contour levels to use for visualizing
 1606         volumes, meshes, and surfaces corresponding to the negative and positive
 1607         values in cube files. Default: auto. The specified values for contourLevel1
 1608         and contourLevel2 must be negative and positive numbers.
 1609         
 1610         The contour levels are automatically calculated by default. The isocontour
 1611         range for specified percent of the density is retrieved from the cube files.
 1612         The contour levels are set at 'contourLevelAutoAt' of the absolute maximum
 1613         value of the isocontour range. For example: contour levels are set to plus and
 1614         minus 0.03 at 'contourLevelAutoAt' of 0.5 for isocontour range of -0.06 to
 1615         0.06 covering specified percent of the density.
 1616         
 1617         contourLevelAutoAt: Set contour levels at specified fraction of the absolute
 1618         maximum value of the isocontour range retrieved from  the cube files. This
 1619         option is only used during the automatic calculations of the contour levels.
 1620         
 1621         hideHydrogens: Hide hydrogens in molecules. Default: yes. Possible
 1622         values: yes or no.
 1623         
 1624         displayMolecule: Display mode for molecules. Possible values: Sticks or
 1625         BallAndStick. Both displays objects are created for molecules.
 1626         
 1627         displaySphereScale: Sphere scale for displaying molecule during
 1628         BallAndStick display.
 1629         
 1630         displayStickRadius: Stick radius  for displaying molecule during Sticks
 1631         and BallAndStick display.
 1632         
 1633         hideHydrogens: Hide hydrogens in molecules. Default: yes. Possible
 1634         values: yes or no.
 1635         meshWidth: Line width for mesh lines to visualize cube files.
 1636         
 1637         meshQuality: Mesh quality for meshes to visualize cube files. The
 1638         higher values represents better quality.
 1639         
 1640         meshWidth: Line width for mesh lines to visualize cube files.
 1641         
 1642         surfaceQuality: Surface quality for surfaces to visualize cube files.
 1643         The higher values represents better quality.
 1644         
 1645         surfaceTransparency: Surface transparency for surfaces to visualize cube
 1646         files.
 1647         
 1648         volumeColorRamp: Name of a PyMOL volume color ramp to use for visualizing
 1649         cube files. Default name(s): <OutfielsMolPrefix>_psi4_cube_mo or psi4_cube_mo
 1650         The default volume color ramps are automatically generated using contour
 1651         levels and colors during 'auto' value of 'volumeColorRamp'. An explicitly
 1652         specified value must be a valid PyMOL volume color ramp. No validation is
 1653         preformed.
 1654         
 1655         VolumeColorRampOpacity: Opacity for generating volume color ramps
 1656         for visualizing cube files. This value is equivalent to 1 minus Transparency.
 1657         
 1658         volumeContourWindowFactor: Fraction of contour level representing  window
 1659         widths around contour levels during generation of volume color ramps for
 1660         visualizing cube files. For example, the value of 0.05 implies a ramp window
 1661         size of 0.0015 at contour level of 0.03.
 1662     -q, --quiet <yes or no>  [default: no]
 1663         Use quiet mode. The warning and information messages will not be printed.
 1664     -r, --reference <text>  [default: auto]
 1665         Reference wave function to use for calculating single point energy before
 1666         generating cube files for frontier molecular orbitals. Default: RHF or UHF.
 1667         The default values are Restricted Hartree-Fock (RHF) for closed-shell molecules
 1668         with all electrons paired and Unrestricted artree-Fock (UHF) for open-shell
 1669         molecules with unpaired electrons.
 1670         
 1671         The specified value must be a valid Psi4 reference wave function. No validation
 1672         is performed. For example: ROHF, CUHF, RKS, etc.
 1673         
 1674         The spin multiplicity determines the default value of reference wave function
 1675         for input molecules. It is calculated from number of free radical electrons using
 1676         Hund's rule of maximum multiplicity defined as 2S + 1 where S is the total
 1677         electron spin. The total spin is 1/2 the number of free radical electrons in a 
 1678         molecule. The value of 'SpinMultiplicity' molecule property takes precedence
 1679         over the calculated value of spin multiplicity.
 1680     -w, --workingdir <dir>
 1681         Location of working directory which defaults to the current directory.
 1682 
 1683 Examples:
 1684     To generate and visualize frontier molecular orbitals based on a single point 
 1685     energy calculation using  B3LYP/6-31G** and B3LYP/6-31+G** for non-sulfur
 1686     and sulfur containing molecules in a SD file with 3D structures, use RHF and
 1687     UHF for closed-shell and open-shell molecules, and write a new PML file, type:
 1688 
 1689         % Psi4VisualizeFrontierOrbitals.py -i Psi4Sample3D.sdf
 1690           -o Psi4Sample3DOut.pml
 1691 
 1692     To run the first example to only generate cube files and skip generation of 
 1693     a PML file to visualize frontier molecular orbitals, type:
 1694 
 1695         % Psi4VisualizeFrontierOrbitals.py --mode GenerateCubeFiles
 1696           -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
 1697 
 1698     To run the first example to skip generation of cube files and use existing cube
 1699     files to visualize frontier molecular orbitals and write out a PML file, type:
 1700 
 1701         % Psi4VisualizeFrontierOrbitals.py --mode VisualizeCubeFiles
 1702           -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
 1703 
 1704     To run the first example in multiprocessing mode on all available CPUs
 1705     without loading all data into memory and write out a PML file, type:
 1706 
 1707         % Psi4VisualizeFrontierOrbitals.py --mp yes -i Psi4Sample3D.sdf
 1708             -o Psi4Sample3DOut.pml
 1709 
 1710     To run the first example in multiprocessing mode on all available CPUs
 1711     by loading all data into memory and write out a PML file, type:
 1712 
 1713         % Psi4VisualizeFrontierOrbitals.py  --mp yes --mpParams "inputDataMode,
 1714             InMemory" -i Psi4Sample3D.sdf  -o Psi4Sample3DOut.pml
 1715 
 1716     To run the first example in multiprocessing mode on all available CPUs
 1717     without loading all data into memory along with multiple threads for each
 1718     Psi4 run and write out a SD file, type:
 1719 
 1720         % Psi4VisualizeFrontierOrbitals.py --mp yes --psi4RunParams
 1721           "NumThreads,2" -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
 1722 
 1723     To run the first example in using a specific set of parameters to generate and
 1724     visualize frontier molecular orbitals and write out  a PML file, type:
 1725 
 1726         % Psi4VisualizeFrontierOrbitals.py  --mode both -m SCF -b aug-cc-pVDZ 
 1727           --psi4CubeFilesParams "gridSpacing, 0.2, gridOverage, 4.0"
 1728           --psi4RunParams "MemoryInGB, 2" --pymolViewParams "contourColor1,
 1729           red, contourColor2, blue,contourLevel1, -0.04, contourLevel2, 0.04,
 1730           contourLevelAutoAt, 0.75,volumeColorRamp, auto,
 1731           volumeColorRampOpacity,0.25, volumeContourWindowFactor,0.05"
 1732           -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
 1733 
 1734 Author:
 1735     Manish Sud(msud@san.rr.com)
 1736 
 1737 See also:
 1738     Psi4PerformMinimization.py, Psi4GenerateConformers.py,
 1739     Psi4VisualizeDualDescriptors.py, Psi4VisualizeElectrostaticPotential.py
 1740 
 1741 Copyright:
 1742     Copyright (C) 2024 Manish Sud. All rights reserved.
 1743 
 1744     The functionality available in this script is implemented using Psi4, an
 1745     open source quantum chemistry software package, and RDKit, an open
 1746     source toolkit for cheminformatics developed by Greg Landrum.
 1747 
 1748     This file is part of MayaChemTools.
 1749 
 1750     MayaChemTools is free software; you can redistribute it and/or modify it under
 1751     the terms of the GNU Lesser General Public License as published by the Free
 1752     Software Foundation; either version 3 of the License, or (at your option) any
 1753     later version.
 1754 
 1755 """
 1756 
 1757 if __name__ == "__main__":
 1758     main()