MayaChemTools

    1 #!/bin/env python
    2 # File: Psi4Util.py
    3 # Author: Manish Sud <msud@san.rr.com>
    4 #
    5 # Copyright (C) 2024 Manish Sud. All rights reserved.
    6 #
    7 # The functionality available in this file is implemented using Psi4, an open
    8 # source quantum chemistry software package.
    9 #
   10 # This file is part of MayaChemTools.
   11 #
   12 # MayaChemTools is free software; you can redistribute it and/or modify it under
   13 # the terms of the GNU Lesser General Public License as published by the Free
   14 # Software Foundation; either version 3 of the License, or (at your option) any
   15 # later version.
   16 #
   17 # MayaChemTools is distributed in the hope that it will be useful, but without
   18 # any warranty; without even the implied warranty of merchantability of fitness
   19 # for a particular purpose.  See the GNU Lesser General Public License for more
   20 # details.
   21 #
   22 # You should have received a copy of the GNU Lesser General Public License
   23 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
   24 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
   25 # Boston, MA, 02111-1307, USA.
   26 #
   27 
   28 from __future__ import print_function
   29 
   30 import os
   31 import sys
   32 import re
   33 import glob
   34 
   35 import MiscUtil
   36 
   37 __all__ = ["CalculateSinglePointEnergy", "InitializePsi4", "JoinMethodNameAndBasisSet", "ListPsi4RunParamaters", "RetrieveIsocontourRangeFromCubeFile", "RetrieveMinAndMaxValueFromCubeFile", "PerformGeometryOptimization", "ProcessPsi4ConstrainTorrionsParameters", "ProcessPsi4CubeFilesParameters", "ProcessPsi4OptionsParameters", "ProcessPsi4RunParameters", "ProcessPsi4DDXSolvationParameters", "RemoveScratchFiles", "SetupPsi4DDXSolvationOptions", "UpdatePsi4OptionsParameters", "UpdatePsi4RunParameters", "UpdatePsi4OutputFileUsingPID"]
   38 
   39 def InitializePsi4(Psi4RunParams = None,  Psi4OptionsParams = None, PrintVersion = False, PrintHeader = False):
   40     """Import Psi4 module and configure it for running Psi4 jobs.
   41     
   42     Arguments:
   43         Psi4RunParams (dict): Runtime parameter name and value pairs.
   44         Psi4OptionsParams (dict): Option name and value pairs. This is simply
   45             passed to ps4.set_options().      
   46         PrintVersion (bool): Print version number.
   47         PrintHeader (bool): Print header information.
   48 
   49     Returns:
   50         Object: Psi4 module reference.
   51 
   52     """
   53     
   54     # Import Psi4...
   55     try:
   56         import psi4
   57     except ImportError as ErrMsg:
   58         sys.stderr.write("\nFailed to import Psi4 module/package: %s\n" % ErrMsg)
   59         sys.stderr.write("Check/update your Psi4 environment and try again.\n\n")
   60         sys.exit(1)
   61 
   62     Psi4Handle = psi4
   63     
   64     if PrintVersion:
   65         MiscUtil.PrintInfo("Importing Psi4 module (Psi4 v%s)...\n" % (Psi4Handle.__version__))
   66 
   67     # Update Psi4 run paramaters...
   68     if Psi4RunParams is not None:
   69         UpdatePsi4RunParameters(Psi4Handle, Psi4RunParams)
   70 
   71     # Update Psi4 options...
   72     if Psi4OptionsParams is not None:
   73         UpdatePsi4OptionsParameters(Psi4Handle, Psi4OptionsParams)
   74         
   75     # Print header after updating Psi4 run parameters...
   76     if PrintHeader:
   77         Psi4Handle.print_header()
   78     
   79     return Psi4Handle
   80     
   81 def CalculateSinglePointEnergy(psi4, Molecule, Method, BasisSet, ReturnWaveFunction = False, Quiet = False):
   82     """Calculate single point electronic energy in Hartrees using a specified
   83     method and basis set.
   84 
   85     Arguments:
   86         psi4 (Object): Psi4 module reference.
   87         Molecule (Object): Psi4 molecule object.
   88         Method (str): A valid method name.
   89         BasisSet (str): A valid basis set.
   90         ReturnWaveFunction (bool): Return wave function.
   91         Quiet (bool): Flag to print error message.
   92 
   93     Returns:
   94         float: Total electronic energy in Hartrees.
   95         (float, psi4 object): Energy and wavefuction.
   96 
   97     """
   98     
   99     Status = False
  100     Energy, WaveFunction = [None] * 2
  101 
  102     try:
  103         MethodAndBasisSet = JoinMethodNameAndBasisSet(Method, BasisSet)
  104         if ReturnWaveFunction:
  105             Energy, WaveFunction = psi4.energy(MethodAndBasisSet, molecule = Molecule, return_wfn = True)
  106         else:
  107             Energy = psi4.energy(MethodAndBasisSet, molecule = Molecule, return_wfn = False)
  108         Status = True
  109     except Exception as ErrMsg:
  110         if not Quiet:
  111             MiscUtil.PrintWarning("Psi4Util.CalculateSinglePointEnergy: Failed to calculate energy:\n%s\n" % ErrMsg)
  112     
  113     return (Status, Energy, WaveFunction) if ReturnWaveFunction else (Status, Energy)
  114     
  115 def PerformGeometryOptimization(psi4, Molecule, Method, BasisSet, ReturnWaveFunction = True, Quiet = False):
  116     """Perform geometry optimization using a specified method and basis set.
  117     
  118     Arguments:
  119         psi4 (Object): Psi4 module reference.
  120         Molecule (Object): Psi4 molecule object.
  121         Method (str): A valid method name.
  122         BasisSet (str): A valid basis set.
  123         ReturnWaveFunction (bool): Return wave function.
  124         Quiet (bool): Flag to print error message.
  125 
  126     Returns:
  127         float: Total electronic energy in Hartrees.
  128         (float, psi4 object): Energy and wavefuction.
  129 
  130     """
  131     
  132     Status = False
  133     Energy, WaveFunction = [None] * 2
  134 
  135     try:
  136         MethodAndBasisSet = JoinMethodNameAndBasisSet(Method, BasisSet)
  137         if ReturnWaveFunction:
  138             Energy, WaveFunction = psi4.optimize(MethodAndBasisSet, molecule = Molecule, return_wfn = True)
  139         else:
  140             Energy = psi4.optimize(MethodAndBasisSet, molecule = Molecule, return_wfn = False)
  141         Status = True
  142     except Exception as ErrMsg:
  143         if not Quiet:
  144             MiscUtil.PrintWarning("Psi4Util.PerformGeometryOptimization: Failed to perform geometry optimization:\n%s\n" % ErrMsg)
  145     
  146     return (Status, Energy, WaveFunction) if ReturnWaveFunction else (Status, Energy)
  147     
  148 def JoinMethodNameAndBasisSet(MethodName, BasisSet):
  149     """Join method name and basis set using a backslash delimiter.
  150     An empty basis set specification is ignored.
  151 
  152     Arguments:
  153         MethodName (str): A valid method name.
  154         BasisSet (str): A valid basis set or an empty string.
  155 
  156     Returns:
  157         str: MethodName/BasisSet or MethodName
  158 
  159     """
  160     
  161     return MethodName if MiscUtil.IsEmpty(BasisSet) else "%s/%s" % (MethodName, BasisSet)
  162     
  163 def GetAtomPositions(psi4, WaveFunction, InAngstroms = True):
  164     """Retrieve a list of lists containing coordinates of all atoms in the
  165     molecule available in Psi4 wave function. By default, the atom positions
  166     are returned in Angstroms. The Psi4 default is Bohr.
  167 
  168     Arguments:
  169         psi4 (Object): Psi4 module reference.
  170         WaveFunction (Object): Psi4 wave function reference.
  171         InAngstroms (bool): True - Positions in Angstroms; Otherwise, in Bohr.
  172 
  173     Returns:
  174         None or list : List of lists containing atom positions.
  175 
  176     Examples:
  177 
  178         for AtomPosition in Psi4Util.GetAtomPositions(Psi4Handle, WaveFunction):
  179             print("X: %s; Y: %s; Z: %s" % (AtomPosition[0], AtomPosition[1],
  180                 AtomPosition[2]))
  181 
  182     """
  183 
  184     if WaveFunction is None:
  185         return None
  186     
  187     AtomPositions = WaveFunction.molecule().geometry().to_array()
  188     if InAngstroms:
  189         AtomPositions = AtomPositions * psi4.constants.bohr2angstroms
  190     
  191     return AtomPositions.tolist()
  192 
  193 def ListPsi4RunParamaters(psi4):
  194     """List values for a key set of the following Psi4 runtime parameters:
  195     Memory, NumThreads, OutputFile, ScratchDir, DataDir.
  196     
  197     Arguments:
  198         psi4 (object): Psi4 module reference.
  199 
  200     Returns:
  201         None
  202 
  203     """
  204     
  205     MiscUtil.PrintInfo("\nListing Psi4 run options:")
  206     
  207     # Memory in bytes...
  208     Memory = psi4.get_memory()
  209     MiscUtil.PrintInfo("Memory: %s (B); %s (MB)" % (Memory, Memory/(1024*1024)))
  210     
  211     # Number of threads...
  212     NumThreads = psi4.get_num_threads()
  213     MiscUtil.PrintInfo("NumThreads: %s " % (NumThreads))
  214     
  215     # Output file...
  216     OutputFile = psi4.core.get_output_file()
  217     MiscUtil.PrintInfo("OutputFile: %s " % (OutputFile))
  218     
  219     # Scratch dir...
  220     psi4_io = psi4.core.IOManager.shared_object()
  221     ScratchDir = psi4_io.get_default_path()
  222     MiscUtil.PrintInfo("ScratchDir: %s " % (ScratchDir))
  223     
  224     # Data dir...
  225     DataDir = psi4.core.get_datadir()
  226     MiscUtil.PrintInfo("DataDir: %s " % (DataDir))
  227 
  228 def UpdatePsi4OptionsParameters(psi4, OptionsInfo):
  229     """Update Psi4 options using psi4.set_options().
  230     
  231     Arguments:
  232         psi4 (object): Psi4 module reference.
  233         OptionsInfo (dictionary) : Option name and value pairs for setting
  234             global and module options.
  235 
  236     Returns:
  237         None
  238 
  239     """
  240     if OptionsInfo is None:
  241         return
  242 
  243     if len(OptionsInfo) == 0:
  244         return
  245 
  246     try:
  247         psi4.set_options(OptionsInfo)
  248     except Exception as ErrMsg:
  249         MiscUtil.PrintWarning("Psi4Util.UpdatePsi4OptionsParameters: Failed to set Psi4 options\n%s\n" % ErrMsg)
  250 
  251 def UpdatePsi4RunParameters(psi4, RunParamsInfo):
  252     """Update Psi4 runtime parameters. The supported parameter names along with
  253     their default values are as follows: MemoryInGB: 1; NumThreads: 1, OutputFile:
  254     stdout; ScratchDir: auto; RemoveOutputFile: True.
  255 
  256     Arguments:
  257         psi4 (object): Psi4 module reference.
  258         RunParamsInfo (dictionary) : Parameter name and value pairs for
  259             configuring Psi4 jobs.
  260 
  261     Returns:
  262         None
  263 
  264     """
  265 
  266     # Set default values for possible arguments...
  267     Psi4RunParams = {"MemoryInGB": 1, "NumThreads": 1, "OutputFile": "stdout",  "ScratchDir" : "auto", "RemoveOutputFile": True}
  268     
  269     # Set specified values for possible arguments...
  270     for Param in Psi4RunParams:
  271         if Param in RunParamsInfo:
  272             Psi4RunParams[Param] = RunParamsInfo[Param]
  273     
  274     # Memory...
  275     Memory = int(Psi4RunParams["MemoryInGB"]*1024*1024*1024)
  276     psi4.core.set_memory_bytes(Memory, True)
  277     
  278     # Number of threads...
  279     psi4.core.set_num_threads(Psi4RunParams["NumThreads"], quiet = True)
  280 
  281     # Output file...
  282     OutputFile = Psi4RunParams["OutputFile"]
  283     if not re.match("^stdout$", OutputFile, re.I):
  284         # Possible values: stdout, quiet, devnull, or filename
  285         if re.match("^(quiet|devnull)$", OutputFile, re.I):
  286             # Psi4 output is redirected to /dev/null after call to be_quiet function...
  287             psi4.core.be_quiet()
  288         else:
  289             # Delete existing output file at the start of the first Psi4 run...
  290             if Psi4RunParams["RemoveOutputFile"]:
  291                 if os.path.isfile(OutputFile):
  292                     os.remove(OutputFile)
  293                     
  294             # Append to handle output from multiple Psi4 runs for molecules in
  295             # input file...
  296             Append = True
  297             psi4.core.set_output_file(OutputFile, Append)
  298 
  299     # Scratch directory...
  300     ScratchDir = Psi4RunParams["ScratchDir"]
  301     if not re.match("^auto$", ScratchDir, re.I):
  302         if not os.path.isdir(ScratchDir):
  303             MiscUtil.PrintError("ScratchDir is not a directory: %s" % ScratchDir)
  304         psi4.core.IOManager.shared_object().set_default_path(os.path.abspath(os.path.expanduser(ScratchDir)))
  305 
  306 def ProcessPsi4OptionsParameters(ParamsOptionName, ParamsOptionValue):
  307     """Process parameters for setting up Psi4 options and return a map
  308     containing processed parameter names and values.
  309     
  310     ParamsOptionValue is a comma delimited list of Psi4 option name and value
  311     pairs for setting global and module options. The names are 'option_name'
  312     for global options and 'module_name__option_name' for options local to a
  313     module. The specified option names must be valid Psi4 names. No validation
  314     is performed.
  315     
  316     The specified option name and  value pairs are processed and passed to
  317     psi4.set_options() as a dictionary. The supported value types are float,
  318     integer, boolean, or string. The float value string is converted into a float.
  319     The valid values for a boolean string are yes, no, true, false, on, or off. 
  320 
  321     Arguments:
  322         ParamsOptionName (str): Command line input parameters option name.
  323         ParamsOptionValue (str): Comma delimited list of parameter name and value pairs.
  324 
  325     Returns:
  326         dictionary: Processed parameter name and value pairs.
  327 
  328     """
  329 
  330     OptionsInfo = {}
  331     
  332     if re.match("^(auto|none)$", ParamsOptionValue, re.I):
  333         return None
  334 
  335     ParamsOptionValue = ParamsOptionValue.strip()
  336     if not ParamsOptionValue:
  337         PrintError("No valid parameter name and value pairs specified using \"%s\" option" % ParamsOptionName)
  338     
  339     ParamsOptionValueWords = ParamsOptionValue.split(",")
  340     if len(ParamsOptionValueWords) % 2:
  341         MiscUtil.PrintError("The number of comma delimited paramater names and values, %d, specified using \"%s\" option must be an even number." % (len(ParamsOptionValueWords), ParamsOptionName))
  342     
  343     # Validate paramater name and value pairs...
  344     for Index in range(0, len(ParamsOptionValueWords), 2):
  345         Name = ParamsOptionValueWords[Index].strip()
  346         Value = ParamsOptionValueWords[Index + 1].strip()
  347         
  348         if  MiscUtil.IsInteger(Value):
  349             Value = int(Value)
  350         elif MiscUtil.IsFloat(Value):
  351             Value = float(Value)
  352         
  353         OptionsInfo[Name] = Value
  354 
  355     return OptionsInfo
  356 
  357 def ProcessPsi4RunParameters(ParamsOptionName, ParamsOptionValue, InfileName = None, ParamsDefaultInfo = None):
  358     """Process parameters for Psi4 runs and return a map containing processed
  359     parameter names and values.
  360     
  361     ParamsOptionValue a comma delimited list of parameter name and value pairs
  362     for configuring Psi4 jobs.
  363     
  364     The supported parameter names along with their default and possible
  365     values are shown below:
  366     
  367     MemoryInGB,1,NumThreads,1,OutputFile,auto,ScratchDir,auto,
  368     RemoveOutputFile,yes
  369             
  370     Possible  values: OutputFile - stdout, quiet, or FileName; OutputFile -
  371     DirName; RemoveOutputFile - yes, no, true, or false
  372     
  373     These parameters control the runtime behavior of Psi4.
  374     
  375     The default for 'OutputFile' is a file name <InFileRoot>_Psi4.out. The PID
  376     is appened the output file name during multiprocessing. The 'stdout' value
  377     for 'OutputType' sends Psi4 output to stdout. The 'quiet' or 'devnull' value
  378     suppresses all Psi4 output.
  379     
  380     The default 'Yes' value of 'RemoveOutputFile' option forces the removal
  381     of any existing Psi4 before creating new files to append output from
  382     multiple Psi4 runs.
  383     
  384     The option 'ScratchDir' is a directory path to the location of scratch
  385     files. The default value corresponds to Psi4 default. It may be used to
  386     override the deafult path.
  387 
  388     Arguments:
  389         ParamsOptionName (str): Command line Psi4 run parameters option name.
  390         ParamsOptionValues (str): Comma delimited list of parameter name and value pairs.
  391         InfileName (str): Name of input file.
  392         ParamsDefaultInfo (dict): Default values to override for selected parameters.
  393 
  394     Returns:
  395         dictionary: Processed parameter name and value pairs.
  396 
  397     Notes:
  398         The parameter name and values specified in ParamsOptionValues are validated before
  399         returning them in a dictionary.
  400 
  401     """
  402 
  403     ParamsInfo = {"MemoryInGB": 1, "NumThreads": 1, "OutputFile": "auto",  "ScratchDir" : "auto", "RemoveOutputFile": True}
  404     
  405     # Setup a canonical paramater names...
  406     ValidParamNames = []
  407     CanonicalParamNamesMap = {}
  408     for ParamName in sorted(ParamsInfo):
  409         ValidParamNames.append(ParamName)
  410         CanonicalParamNamesMap[ParamName.lower()] = ParamName
  411     
  412     # Update default values...
  413     if ParamsDefaultInfo is not None:
  414         for ParamName in ParamsDefaultInfo:
  415             if ParamName not in ParamsInfo:
  416                 MiscUtil.PrintError("The default parameter name, %s, specified using \"%s\" to function ProcessPsi4RunParameters is not a valid name. Supported parameter names: %s" % (ParamName, ParamsDefaultInfo, " ".join(ValidParamNames)))
  417             ParamsInfo[ParamName] = ParamsDefaultInfo[ParamName]
  418     
  419     if re.match("^auto$", ParamsOptionValue, re.I):
  420         # No specific parameters to process except for parameters with possible auto value...
  421         _ProcessPsi4RunAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue, InfileName)
  422         return ParamsInfo
  423     
  424     ParamsOptionValue = ParamsOptionValue.strip()
  425     if not ParamsOptionValue:
  426         PrintError("No valid parameter name and value pairs specified using \"%s\" option" % ParamsOptionName)
  427     
  428     ParamsOptionValueWords = ParamsOptionValue.split(",")
  429     if len(ParamsOptionValueWords) % 2:
  430         MiscUtil.PrintError("The number of comma delimited paramater names and values, %d, specified using \"%s\" option must be an even number." % (len(ParamsOptionValueWords), ParamsOptionName))
  431     
  432     # Validate paramater name and value pairs...
  433     for Index in range(0, len(ParamsOptionValueWords), 2):
  434         Name = ParamsOptionValueWords[Index].strip()
  435         Value = ParamsOptionValueWords[Index + 1].strip()
  436 
  437         CanonicalName = Name.lower()
  438         if  not CanonicalName in CanonicalParamNamesMap:
  439             MiscUtil.PrintError("The parameter name, %s, specified using \"%s\" is not a valid name. Supported parameter names: %s" % (Name, ParamsOptionName, " ".join(ValidParamNames)))
  440 
  441         ParamName = CanonicalParamNamesMap[CanonicalName]
  442         ParamValue = Value
  443         
  444         if re.match("^MemoryInGB$", ParamName, re.I):
  445             Value = float(Value)
  446             if Value <= 0:
  447                 MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. Supported values: > 0" % (Value, Name, ParamsOptionName))
  448             ParamValue = Value
  449         elif re.match("^NumThreads$", ParamName, re.I):
  450             Value = int(Value)
  451             if Value <= 0:
  452                 MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. Supported values: > 0" % (Value, Name, ParamsOptionName))
  453             ParamValue = Value
  454         elif re.match("^ScratchDir$", ParamName, re.I):
  455             if not re.match("^auto$", Value, re.I):
  456                 if not os.path.isdir(Value):
  457                     MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. It must be a directory name." % (Value, Name, ParamsOptionName))
  458             ParamValue = Value
  459         elif re.match("^RemoveOutputFile$", ParamName, re.I):
  460             if re.match("^(yes|true)$", Value, re.I):
  461                 Value = True
  462             elif re.match("^(no|false)$", Value, re.I):
  463                 Value = False
  464             else:
  465                 MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. Supported values: yes, no, true, or false" % (Value, Name, ParamsOptionName))
  466             ParamValue = Value
  467             
  468         # Set value...
  469         ParamsInfo[ParamName] = ParamValue
  470     
  471     # Handle paramaters with possible auto values...
  472     _ProcessPsi4RunAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue, InfileName)
  473 
  474     return ParamsInfo
  475 
  476 def _ProcessPsi4RunAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue, InfileName):
  477     """Process parameters with possible auto values.
  478     """
  479     
  480     Value = ParamsInfo["OutputFile"]
  481     ParamValue = Value
  482     if re.match("^auto$", Value, re.I):
  483         if InfileName is not None:
  484             # Use InfileName to setup output file. The OutputFile name is automatically updated using
  485             # PID during multiprocessing...
  486             InfileDir, InfileRoot, InfileExt = MiscUtil.ParseFileName(InfileName)
  487             OutputFile = "%s_Psi4.out" % (InfileRoot)
  488         else:
  489             OutputFile = "Psi4.out"
  490     elif re.match("^(devnull|quiet)$", Value, re.I):
  491         OutputFile = "quiet"
  492     else:
  493         # It'll be treated as a filename and processed later...
  494         OutputFile = Value
  495     
  496     ParamsInfo["OutputFile"] = OutputFile
  497 
  498     # OutputFileSpecified is used to track the specified value of the paramater.
  499     # It may be used by the calling function to dynamically override the value of
  500     # OutputFile to suprress the Psi4 output based on the initial value.
  501     ParamsInfo["OutputFileSpecified"] = ParamValue
  502     
  503 def ProcessPsi4ConstrainTorrionsParameters(ParamsOptionName, ParamsOptionValue, ParamsDefaultInfo = None):
  504     """Process parameters for Psi4 constrain torsions around rotatable bonds and
  505     return a map containing processed parameter names and values.
  506     
  507     ParamsOptionValue is a comma delimited list of parameter name and value pairs
  508     for generating cube files.
  509     
  510     The supported parameter names along with their default and possible
  511     values are shown below:
  512     
  513     ignoreHydrogens, yes, rotBondsSMARTSMode, NonStrict,
  514     rotBondsSMARTSPattern, Auto
  515     
  516     Arguments:
  517         ParamsOptionName (str): Command line Psi4 constrain torsions option name.
  518         ParamsOptionValues (str): Comma delimited list of parameter name and value pairs.
  519         ParamsDefaultInfo (dict): Default values to override for selected parameters.
  520 
  521     Returns:
  522         dictionary: Processed parameter name and value pairs.
  523 
  524     """
  525 
  526     ParamsInfo = {"IgnoreHydrogens":True, "RotBondsSMARTSMode":  "SemiStrict", "RotBondsSMARTSPattern": "auto"}
  527     
  528     # Setup a canonical paramater names...
  529     ValidParamNames = []
  530     CanonicalParamNamesMap = {}
  531     for ParamName in sorted(ParamsInfo):
  532         ValidParamNames.append(ParamName)
  533         CanonicalParamNamesMap[ParamName.lower()] = ParamName
  534     
  535     # Update default values...
  536     if ParamsDefaultInfo is not None:
  537         for ParamName in ParamsDefaultInfo:
  538             if ParamName not in ParamsInfo:
  539                 MiscUtil.PrintError("The default parameter name, %s, specified using \"%s\" to function ProcessPsi4ConstrainTorrionsParameters not a valid name. Supported parameter names: %s" % (ParamName, ParamsDefaultInfo, " ".join(ValidParamNames)))
  540             ParamsInfo[ParamName] = ParamsDefaultInfo[ParamName]
  541     
  542     if re.match("^auto$", ParamsOptionValue, re.I):
  543         # No specific parameters to process except for parameters with possible auto value...
  544         _ProcessPsi4ConstrainTorsionsAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue)
  545         return ParamsInfo
  546     
  547     ParamsOptionValue = ParamsOptionValue.strip()
  548     if not ParamsOptionValue:
  549         PrintError("No valid parameter name and value pairs specified using \"%s\" option" % ParamsOptionName)
  550     
  551     ParamsOptionValueWords = ParamsOptionValue.split(",")
  552     if len(ParamsOptionValueWords) % 2:
  553         MiscUtil.PrintError("The number of comma delimited paramater names and values, %d, specified using \"%s\" option must be an even number." % (len(ParamsOptionValueWords), ParamsOptionName))
  554     
  555     # Validate paramater name and value pairs...
  556     for Index in range(0, len(ParamsOptionValueWords), 2):
  557         Name = ParamsOptionValueWords[Index].strip()
  558         Value = ParamsOptionValueWords[Index + 1].strip()
  559 
  560         CanonicalName = Name.lower()
  561         if  not CanonicalName in CanonicalParamNamesMap:
  562             MiscUtil.PrintError("The parameter name, %s, specified using \"%s\" is not a valid name. Supported parameter names: %s" % (Name, ParamsOptionName, " ".join(ValidParamNames)))
  563 
  564         ParamName = CanonicalParamNamesMap[CanonicalName]
  565         ParamValue = Value
  566         
  567         if re.match("^IgnoreHydrogens$", ParamName, re.I):
  568             if re.match("^(yes|true)$", Value, re.I):
  569                 Value = True
  570             elif re.match("^(no|false)$", Value, re.I):
  571                 Value = False
  572             else:
  573                 MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. Supported values: yes, no, true, or false" % (Value, Name, ParamsOptionName))
  574             ParamValue = Value
  575         elif re.match("^RotBondsSMARTSMode$", ParamName, re.I):
  576             if not re.match("^(NonStrict|SemiStrict|Strict|Specify)$", Value, re.I):
  577                 MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. Supported values: NonStrict, SemiStrict, Strict, or Specify" % (Value, Name, ParamsOptionName))
  578             ParamValue = Value
  579         
  580         # Set value...
  581         ParamsInfo[ParamName] = ParamValue
  582 
  583     # Handle paramaters with possible auto values...
  584     _ProcessPsi4ConstrainTorsionsAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue)
  585 
  586     return ParamsInfo
  587 
  588 def _ProcessPsi4ConstrainTorsionsAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue):
  589     """Process parameters with possible auto values.
  590     """
  591 
  592     if re.match("^Specify$", ParamsInfo["RotBondsSMARTSMode"], re.I):
  593         if re.match("^auto$", ParamsInfo["RotBondsSMARTSPattern"], re.I):
  594              MiscUtil.PrintError("The parameter value, auto, specified for parameter name, RotBondsSMARTSPattern, using \"%s\ is not allowed during, specify, value for parameter name, RotBondsSMARTSMode. You must specify a valid SMARTS pattern using parameter name, RotBondsSMARTSPattern." % ParamsOptionName)
  595     else:
  596         if not re.match("^auto$", ParamsInfo["RotBondsSMARTSPattern"], re.I):
  597              MiscUtil.PrintError("The parameter value, %s, specified for parameter name, RotBondsSMARTSPattern, using \"%s\ is not allowed during, %s, value for parameter name, RotBondsSMARTSMode." % (ParamsInfo["RotBondsSMARTSPattern"], ParamsOptionName, ParamsInfo["RotBondsSMARTSMode"]))
  598 
  599     # Setup default SMARTS pattern...
  600     Name = "RotBondsSMARTSMode"
  601     Value = ParamsInfo[Name]
  602     if re.match("^NonStrict$", Value, re.I):
  603         RotBondsSMARTSPattern = "[!$(*#*)&!D1]-&!@[!$(*#*)&!D1]"
  604     elif re.match("^SemiStrict$", Value, re.I):
  605         RotBondsSMARTSPattern = "[!$(*#*)&!D1&!$(C(F)(F)F)&!$(C(Cl)(Cl)Cl)&!$(C(Br)(Br)Br)&!$(C([CH3])([CH3])[CH3])]-!@[!$(*#*)&!D1&!$(C(F)(F)F)&!$(C(Cl)(Cl)Cl)&!$(C(Br)(Br)Br)&!$(C([CH3])([CH3])[CH3])]"
  606     elif re.match("^Strict$", Value, re.I):
  607         RotBondsSMARTSPattern = "[!$(*#*)&!D1&!$(C(F)(F)F)&!$(C(Cl)(Cl)Cl)&!$(C(Br)(Br)Br)&!$(C([CH3])([CH3])[CH3])&!$([CD3](=[N,O,S])-!@[#7,O,S!D1])&!$([#7,O,S!D1]-!@[CD3]=[N,O,S])&!$([CD3](=[N+])-!@[#7!D1])&!$([#7!D1]-!@[CD3]=[N+])]-!@[!$(*#*)&!D1&!$(C(F)(F)F)&!$(C(Cl)(Cl)Cl)&!$(C(Br)(Br)Br)&!$(C([CH3])([CH3])[CH3])]"
  608     elif re.match("^Specify$", Value, re.I):
  609         RotBondsSMARTSPattern = ParamsInfo["RotBondsSMARTSPattern"].strip()
  610         if not len(RotBondsSMARTSPattern):
  611             MiscUtil.PrintError("Empty value specified using parameter name, RotBondsSMARTSPattern, using \"%s\ option" % ParamsOptionName)
  612     else:
  613         MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. Supported values: NonStrict, SemiStrict, Strict, or Specify" % (Value, Name, ParamsOptionName))
  614 
  615     ParamsInfo["RotBondsSMARTSPattern"] = RotBondsSMARTSPattern
  616     
  617     return
  618 
  619 def ProcessPsi4DDXSolvationParameters(ParamsOptionName, ParamsOptionValue, ParamsDefaultInfo = None):
  620     """Process parameters for Psi4 DDX solvation and return a map containing
  621     processed parameter names and values.
  622     
  623     ParamsOptionValue is a space delimited list of parameter name and value pairs
  624     for solvation energy calculatios.
  625     
  626     The supported parameter names along with their default and possible
  627     values are shown below:
  628     
  629     SolvationModel PCM Solvent water solventEpsilon None radiiSet UFF
  630     
  631     solvationModel: Solvation model for calculating solvation energy. The
  632     corresponding Psi4 option is DDX_MODEL.
  633     
  634     solvent: Solvent to use. The corresponding Ps4 option is DDX_SOLVENT.
  635             
  636     solventEpsilon: Dielectric constant of the solvent. The corresponding
  637     Psi4 option is DDX_SOLVENT_EPSILON.
  638             
  639     radiiSet: Radius set for cavity spheres. The corresponding Psi option is
  640     DDX_RADII_SET.
  641 
  642     Arguments:
  643         ParamsOptionName (str): Command line Psi4 DDX solvation option name.
  644         ParamsOptionValues (str): Space delimited list of parameter name and value pairs.
  645         ParamsDefaultInfo (dict): Default values to override for selected parameters.
  646 
  647     Returns:
  648         dictionary: Processed parameter name and value pairs.
  649 
  650     """
  651     
  652     ParamsInfo = {"SolvationModel": "PCM", "Solvent": "water", "SolventEpsilon": None, "RadiiSet": "UFF", "RadiiScaling": "auto"}
  653     
  654     # Setup a canonical paramater names...
  655     ValidParamNames = []
  656     CanonicalParamNamesMap = {}
  657     for ParamName in sorted(ParamsInfo):
  658         ValidParamNames.append(ParamName)
  659         CanonicalParamNamesMap[ParamName.lower()] = ParamName
  660     
  661     # Update default values...
  662     if ParamsDefaultInfo is not None:
  663         for ParamName in ParamsDefaultInfo:
  664             if ParamName not in ParamsInfo:
  665                 MiscUtil.PrintError("The default parameter name, %s, specified using \"%s\" to function ProcessPsi4DDXSolvationParameters not a valid name. Supported parameter names: %s" % (ParamName, ParamsDefaultInfo, " ".join(ValidParamNames)))
  666             ParamsInfo[ParamName] = ParamsDefaultInfo[ParamName]
  667     
  668     ParamsOptionValue = ParamsOptionValue.strip()
  669     if not ParamsOptionValue:
  670         MiscUtil.PrintError("No valid parameter name and value pairs specified using \"%s\" option" % ParamsOptionName)
  671     
  672     if re.match("^auto$", ParamsOptionValue, re.I):
  673         _ProcessPsi4DDXSolvationAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue)
  674         return ParamsInfo
  675 
  676     ParamsOptionValueWords = ParamsOptionValue.split()
  677     if len(ParamsOptionValueWords) % 2:
  678         MiscUtil.PrintError("The number of comma delimited paramater names and values, %d, specified using \"%s\" option must be an even number." % (len(ParamsOptionValueWords), ParamsOptionName))
  679 
  680     for Index in range(0, len(ParamsOptionValueWords), 2):
  681         Name = ParamsOptionValueWords[Index].strip()
  682         Value = ParamsOptionValueWords[Index + 1].strip()
  683 
  684         CanonicalName = Name.lower()
  685         if  not CanonicalName in CanonicalParamNamesMap:
  686             MiscUtil.PrintError("The parameter name, %s, specified using \"%s\" is not a valid name. Supported parameter names: %s" % (Name, ParamsOptionName, " ".join(ValidParamNames)))
  687 
  688         ParamName = CanonicalParamNamesMap[CanonicalName]
  689         ParamValue = Value
  690         
  691         if re.match("^SolvationModel$", ParamName, re.I):
  692             if not re.match("^(COSMO|PCM)$", Value, re.I):
  693                 MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. Supported values: COSMO or PCM" % (Value, Name, ParamsOptionName))
  694             ParamValue = Value
  695         elif re.match("^Solvent$", ParamName, re.I):
  696             if MiscUtil.IsEmpty(Value):
  697                 MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is empty." % (Value, Name, ParamsOptionName))
  698             ParamValue = Value
  699         elif re.match("^SolventEpsilon$", ParamName, re.I):
  700             if  re.match("^none$", Value, re.I):
  701                 Value = None
  702             else:
  703                 if not MiscUtil.IsNumber(Value):
  704                     MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option must be a float." % (Value, Name, ParamsOptionName))
  705                 Value = float(Value)
  706                 if Value <= 0:
  707                     MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. Supported values: >= 0" % (Value, Name, ParamsOptionName))
  708             ParamValue = Value
  709         elif re.match("^RadiiSet$", ParamName, re.I):
  710             if not re.match("^(UFF|Bondi)$", Value, re.I):
  711                 MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. Supported values: UFF or Bondi" % (Value, Name, ParamsOptionName))
  712             ParamValue = Value
  713         elif re.match("^RadiiScaling$", ParamName, re.I):
  714             if not re.match("^auto$", Value, re.I):
  715                 if not MiscUtil.IsNumber(Value):
  716                     MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option must be a float." % (Value, Name, ParamsOptionName))
  717                 Value = float(Value)
  718                 if Value <= 0:
  719                     MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. Supported values: >= 0" % (Value, Name, ParamsOptionName))
  720                 ParamValue = Value
  721         else:
  722             ParamValue = Value
  723         
  724         # Set value...
  725         ParamsInfo[ParamName] = ParamValue
  726     
  727     SolventEpsilon = ParamsInfo["SolventEpsilon"]
  728     if SolventEpsilon is not None and SolventEpsilon > 0.0:
  729         Solvent = ParamsInfo["Solvent"]
  730         if not MiscUtil.IsEmpty(Solvent):
  731             MiscUtil.PrintWarning(" You've specified values for both \"solvent\"  and \"solventEpsilon\" parameters using \"%s\" option. The parameter value, %s, specified for paramater name \"solvent\" is being ignored..." % (ParamsOptionName, Solvent))
  732     
  733     # Handle paramaters with possible auto values...
  734     _ProcessPsi4DDXSolvationAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue)
  735     return ParamsInfo
  736 
  737 def _ProcessPsi4DDXSolvationAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue):
  738     """Process parameters with possible auto values.
  739     """
  740     
  741     ParamValue = "%s" % ParamsInfo["RadiiScaling"]
  742     if re.match("^auto$", ParamValue, re.I):
  743         if re.match("^UFF$", ParamsInfo["RadiiSet"], re.I):
  744             ParamValue = 1.1
  745         elif re.match("^Bondi$", ParamsInfo["RadiiSet"], re.I):
  746             ParamValue = 1.2
  747         else:
  748             ParamValue = 0.0
  749     else:
  750         ParamValue = float(ParamValue)
  751     ParamsInfo["RadiiScaling"] = ParamValue
  752     
  753     return
  754 
  755 def SetupPsi4DDXSolvationOptions(SolvationMode, ParamsInfo):
  756     """Setup Psi4 options for calculating solvation energy using DDX module.
  757     
  758     Arguments:
  759         SolvationMode (bool): Set DDX option for solvation calculation.
  760         ParamsInfo (dict): Psi4 DDX parameter name and value pairs.
  761 
  762     Returns:
  763         dictionary: Psi4 Option name and value pairs.
  764 
  765     """
  766     
  767     # Initialize DDX solvation options...
  768     DDXOptionsInfo = {}
  769     DDXOptionsInfo["DDX"] = True if SolvationMode else False
  770     
  771     # Setup DDX solvation options...
  772     ParamNameToDDXOptionID = {"SolvationModel": "DDX_MODEL", "Solvent": "DDX_SOLVENT", "SolventEpsilon": "DDX_SOLVENT_EPSILON", "RadiiSet": "DDX_RADII_SET", "RadiiScaling": "DDX_RADII_SCALING"}
  773     
  774     for ParamName in ParamNameToDDXOptionID:
  775         DDXOptionID = ParamNameToDDXOptionID[ParamName]
  776         DDXOptionsInfo[DDXOptionID] = ParamsInfo[ParamName]
  777     
  778     # Check for the presence fo both solvent and solvent epsilon parameters...
  779     if DDXOptionsInfo["DDX_SOLVENT_EPSILON"] is None:
  780         DDXOptionsInfo.pop("DDX_SOLVENT_EPSILON", None)
  781     else:
  782         DDXOptionsInfo.pop("DDX_SOLVENT", None)
  783     
  784     return DDXOptionsInfo
  785 
  786 def ProcessPsi4CubeFilesParameters(ParamsOptionName, ParamsOptionValue, ParamsDefaultInfo = None):
  787     """Process parameters for Psi4 runs and return a map containing processed
  788     parameter names and values.
  789     
  790     ParamsOptionValue is a comma delimited list of parameter name and value pairs
  791     for generating cube files.
  792     
  793     The supported parameter names along with their default and possible
  794     values are shown below:
  795     
  796     GridSpacing, 0.2, GridOverage, 4.0, IsoContourThreshold, 0.85
  797     
  798     GridSpacing: Units: Bohr. A higher value reduces the size of the cube files
  799     on the disk. This option corresponds to Psi4 option CUBIC_GRID_SPACING.
  800     
  801     GridOverage: Units: Bohr.This option corresponds to Psi4 option
  802     CUBIC_GRID_OVERAGE.
  803     
  804     IsoContourThreshold captures specified percent of the probability density
  805     using the least amount of grid points. This option corresponds to Psi4 option
  806     CUBEPROP_ISOCONTOUR_THRESHOLD.
  807 
  808     Arguments:
  809         ParamsOptionName (str): Command line Psi4 cube files option name.
  810         ParamsOptionValues (str): Comma delimited list of parameter name and value pairs.
  811         ParamsDefaultInfo (dict): Default values to override for selected parameters.
  812 
  813     Returns:
  814         dictionary: Processed parameter name and value pairs.
  815 
  816     """
  817 
  818     ParamsInfo = {"GridSpacing": 0.2, "GridOverage":  4.0, "IsoContourThreshold": 0.85}
  819     
  820     # Setup a canonical paramater names...
  821     ValidParamNames = []
  822     CanonicalParamNamesMap = {}
  823     for ParamName in sorted(ParamsInfo):
  824         ValidParamNames.append(ParamName)
  825         CanonicalParamNamesMap[ParamName.lower()] = ParamName
  826     
  827     # Update default values...
  828     if ParamsDefaultInfo is not None:
  829         for ParamName in ParamsDefaultInfo:
  830             if ParamName not in ParamsInfo:
  831                 MiscUtil.PrintError("The default parameter name, %s, specified using \"%s\" to function ProcessPsi4CubeFilesParameters not a valid name. Supported parameter names: %s" % (ParamName, ParamsDefaultInfo, " ".join(ValidParamNames)))
  832             ParamsInfo[ParamName] = ParamsDefaultInfo[ParamName]
  833     
  834     if re.match("^auto$", ParamsOptionValue, re.I):
  835         # No specific parameters to process except for parameters with possible auto value...
  836         _ProcessPsi4CubeFilesAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue)
  837         return ParamsInfo
  838     
  839     ParamsOptionValue = ParamsOptionValue.strip()
  840     if not ParamsOptionValue:
  841         PrintError("No valid parameter name and value pairs specified using \"%s\" option" % ParamsOptionName)
  842     
  843     ParamsOptionValueWords = ParamsOptionValue.split(",")
  844     if len(ParamsOptionValueWords) % 2:
  845         MiscUtil.PrintError("The number of comma delimited paramater names and values, %d, specified using \"%s\" option must be an even number." % (len(ParamsOptionValueWords), ParamsOptionName))
  846     
  847     # Validate paramater name and value pairs...
  848     for Index in range(0, len(ParamsOptionValueWords), 2):
  849         Name = ParamsOptionValueWords[Index].strip()
  850         Value = ParamsOptionValueWords[Index + 1].strip()
  851 
  852         CanonicalName = Name.lower()
  853         if  not CanonicalName in CanonicalParamNamesMap:
  854             MiscUtil.PrintError("The parameter name, %s, specified using \"%s\" is not a valid name. Supported parameter names: %s" % (Name, ParamsOptionName, " ".join(ValidParamNames)))
  855 
  856         ParamName = CanonicalParamNamesMap[CanonicalName]
  857         ParamValue = Value
  858         
  859         if re.match("^(GridSpacing|GridOverage)$", ParamName, re.I):
  860             if not MiscUtil.IsFloat(Value):
  861                 MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option must be a float." % (Value, Name, ParamsOptionName))
  862             Value = float(Value)
  863             if Value <= 0:
  864                 MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. Supported values: > 0" % (Value, Name, ParamsOptionName))
  865             ParamValue = Value
  866         elif re.match("^IsoContourThreshold$", ParamName, re.I):
  867             if not MiscUtil.IsFloat(Value):
  868                 MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option must be a float." % (Value, Name, ParamsOptionName))
  869             Value = float(Value)
  870             if Value <= 0 or Value > 1:
  871                 MiscUtil.PrintError("The parameter value, %s, specified for parameter name, %s, using \"%s\" option is not a valid value. Supported values: >= 0 and <= 1" % (Value, Name, ParamsOptionName))
  872             ParamValue = Value
  873         
  874         # Set value...
  875         ParamsInfo[ParamName] = ParamValue
  876     
  877     # Handle paramaters with possible auto values...
  878     _ProcessPsi4CubeFilesAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue)
  879 
  880     return ParamsInfo
  881 
  882 def _ProcessPsi4CubeFilesAutoParameters(ParamsInfo, ParamsOptionName, ParamsOptionValue):
  883     """Process parameters with possible auto values.
  884     """
  885     
  886     # No auto parameters to process
  887     return
  888 
  889 def RetrieveIsocontourRangeFromCubeFile(CubeFileName):
  890     """Retrieve isocontour range values from the cube file. The range
  891     values are retrieved from the second line in the cube file after
  892     the string 'Isocontour range'.
  893     
  894     Arguments:
  895         CubeFileName (str): Cube file name.
  896 
  897     Returns:
  898         float: Minimum range value.
  899         float: Maximum range value.
  900 
  901     """
  902 
  903     IsocontourRangeMin, IsocontourRangeMax = [None] * 2
  904     
  905     CubeFH = open(CubeFileName, "r")
  906     if CubeFH is None:
  907         MiscUtil.PrintError("Couldn't open cube file: %s.\n" % (CubeFileName))
  908 
  909     # Look for isocontour range in the first 2 comments line...
  910     RangeLine = None
  911     LineCount = 0
  912     for Line in CubeFH:
  913         LineCount += 1
  914         Line = Line.rstrip()
  915         if re.search("Isocontour range", Line, re.I):
  916             RangeLine = Line
  917             break
  918         
  919         if LineCount >= 2:
  920             break
  921     CubeFH.close()
  922 
  923     if RangeLine is None:
  924         return (IsocontourRangeMin, IsocontourRangeMax)
  925     
  926     LineWords = RangeLine.split(":")
  927     
  928     ContourRangeWord = LineWords[-1]
  929     ContourRangeWord = re.sub("(\(|\)| )", "", ContourRangeWord)
  930 
  931     ContourLevel1, ContourLevel2 = ContourRangeWord.split(",")
  932     ContourLevel1 = float(ContourLevel1)
  933     ContourLevel2 = float(ContourLevel2)
  934     
  935     if ContourLevel1 < ContourLevel2:
  936         IsocontourRangeMin = ContourLevel1
  937         IsocontourRangeMax = ContourLevel2
  938     else:
  939         IsocontourRangeMin = ContourLevel2
  940         IsocontourRangeMax = ContourLevel1
  941         
  942     return (IsocontourRangeMin, IsocontourRangeMax)
  943     
  944 def RetrieveMinAndMaxValueFromCubeFile(CubeFileName):
  945     """Retrieve minimum and maxmimum grid values from the cube file.
  946     
  947     Arguments:
  948         CubeFileName (str): Cube file name.
  949 
  950     Returns:
  951         float: Minimum value.
  952         float: Maximum value.
  953 
  954     """
  955 
  956     MinValue, MaxValue = [sys.float_info.max, sys.float_info.min]
  957     
  958     CubeFH = open(CubeFileName, "r")
  959     if CubeFH is None:
  960         MiscUtil.PrintError("Couldn't open cube file: %s.\n" % (CubeFileName))
  961 
  962     # Ignore first two comments lines:
  963     #
  964     # The first two lines of the header are comments, they are generally ignored by parsing packages or used as two default labels.
  965     #
  966     # Ignore lines upto the last section of the header lines:
  967     #
  968     # The third line has the number of atoms included in the file followed by the position of the origin of the volumetric data.
  969     # The next three lines give the number of voxels along each axis (x, y, z) followed by the axis vector.
  970     # The last section in the header is one line for each atom consisting of 5 numbers, the first is the atom number, the second
  971     # is the charge, and the last three are the x,y,z coordinates of the atom center.
  972     #
  973     Line = CubeFH.readline()
  974     Line = CubeFH.readline()
  975     Line = CubeFH.readline()
  976     CubeFH.close()
  977     
  978     Line = Line.strip()
  979     LineWords = Line.split()
  980     NumOfAtoms = int(LineWords[0])
  981 
  982     HeaderLinesCount = 6 + NumOfAtoms
  983 
  984     # Ignore header lines...
  985     CubeFH = open(CubeFileName, "r")
  986     LineCount = 0
  987     for Line in CubeFH:
  988         LineCount += 1
  989         if LineCount >= HeaderLinesCount:
  990             break
  991     
  992     # Process values....
  993     for Line in CubeFH:
  994         Line = Line.strip()
  995         for Value in Line.split():
  996             Value = float(Value)
  997             
  998             if Value < MinValue:
  999                 MinValue = Value
 1000             if Value > MaxValue:
 1001                 MaxValue = Value
 1002     
 1003     return (MinValue, MaxValue)
 1004 
 1005 def UpdatePsi4OutputFileUsingPID(OutputFile, PID = None):
 1006     """Append PID to output file name. The PID is automatically retrieved
 1007     during None value of PID.
 1008     
 1009     Arguments:
 1010         OutputFile (str): Output file name.
 1011         PID (int): Process ID or None.
 1012 
 1013     Returns:
 1014         str: Update output file name. Format: <OutFieRoot>_<PID>.<OutFileExt>
 1015 
 1016     """
 1017     
 1018     if re.match("stdout|devnull|quiet", OutputFile, re.I):
 1019         return OutputFile
 1020 
 1021     if PID is None:
 1022         PID = os.getpid()
 1023     
 1024     FileDir, FileRoot, FileExt = MiscUtil.ParseFileName(OutputFile)
 1025     OutputFile = "%s_PID%s.%s" % (FileRoot, PID, FileExt)
 1026     
 1027     return OutputFile
 1028     
 1029 def RemoveScratchFiles(psi4, OutputFile, PID = None):
 1030     """Remove any leftover scratch files associated with the specified output
 1031     file. The file specification, <OutfileRoot>.*<PID>.* is used to collect and
 1032     remove files from the scratch directory. In addition, the file
 1033     psi.<PID>.clean, in current directory is removed.
 1034     
 1035     Arguments:
 1036         psi4 (object): psi4 module reference.
 1037         OutputFile (str): Output file name.
 1038         PID (int): Process ID or None.
 1039 
 1040     Returns:
 1041         None
 1042 
 1043     """
 1044     
 1045     if re.match("stdout|devnull|quiet", OutputFile, re.I):
 1046         # Scratch files are associated to stdout prefix...
 1047         OutputFile = "stdout"
 1048     
 1049     if PID is None:
 1050         PID = os.getpid()
 1051     
 1052     OutfileDir, OutfileRoot, OutfileExt = MiscUtil.ParseFileName(OutputFile)
 1053     
 1054     ScratchOutfilesSpec = os.path.join(psi4.core.IOManager.shared_object().get_default_path(), "%s.*%s.*" % (OutfileRoot, PID))
 1055     for ScratchFile in glob.glob(ScratchOutfilesSpec):
 1056         os.remove(ScratchFile)
 1057 
 1058     # Remove any psi.<PID>.clean in the current directory...
 1059     ScratchFile = os.path.join(os.getcwd(), "psi.%s.clean" % (PID))
 1060     if os.path.isfile(ScratchFile):
 1061         os.remove(ScratchFile)