MayaChemTools

    1 #!/bin/env python
    2 #
    3 # File: Psi4CalculateEnergy.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 shutil
   38 import multiprocessing as mp
   39 
   40 # Psi4 imports...
   41 if (hasattr(shutil, 'which') and shutil.which("psi4") is None):
   42     sys.stderr.write("\nWarning: Failed to find 'psi4' in your PATH indicating potential issues with your\n")
   43     sys.stderr.write("Psi4 environment. The 'import psi4' directive in the global scope of the script\n")
   44     sys.stderr.write("interferes with the multiprocessing functionality. It is imported later in the\n")
   45     sys.stderr.write("local scope during the execution of the script and may fail. Check/update your\n")
   46     sys.stderr.write("Psi4 environment and try again.\n\n")
   47 
   48 # RDKit imports...
   49 try:
   50     from rdkit import rdBase
   51     from rdkit import Chem
   52     from rdkit.Chem import AllChem
   53 except ImportError as ErrMsg:
   54     sys.stderr.write("\nFailed to import RDKit module/package: %s\n" % ErrMsg)
   55     sys.stderr.write("Check/update your RDKit environment and try again.\n\n")
   56     sys.exit(1)
   57 
   58 # MayaChemTools imports...
   59 try:
   60     from docopt import docopt
   61     import MiscUtil
   62     import Psi4Util
   63     import RDKitUtil
   64 except ImportError as ErrMsg:
   65     sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg)
   66     sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n")
   67     sys.exit(1)
   68 
   69 ScriptName = os.path.basename(sys.argv[0])
   70 Options = {}
   71 OptionsInfo = {}
   72 
   73 def main():
   74     """Start execution of the script."""
   75     
   76     MiscUtil.PrintInfo("\n%s (Psi4: Imported later; RDKit v%s; MayaChemTools v%s; %s): Starting...\n" % (ScriptName, rdBase.rdkitVersion, MiscUtil.GetMayaChemToolsVersion(), time.asctime()))
   77     
   78     (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
   79     
   80     # Retrieve command line arguments and options...
   81     RetrieveOptions()
   82     
   83     # Process and validate command line arguments and options...
   84     ProcessOptions()
   85     
   86     # Perform actions required by the script...
   87     CalculateInteractionEnergy()
   88     
   89     MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
   90     MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
   91 
   92 def CalculateInteractionEnergy():
   93     """Calculate interaction energy."""
   94     
   95     # Setup a molecule reader...
   96     MiscUtil.PrintInfo("\nProcessing file %s..." % OptionsInfo["Infile"])
   97     Mols  = RDKitUtil.ReadMolecules(OptionsInfo["Infile"], **OptionsInfo["InfileParams"])
   98     
   99     # Set up a molecule writer...
  100     Writer = RDKitUtil.MoleculesWriter(OptionsInfo["Outfile"], **OptionsInfo["OutfileParams"])
  101     if Writer is None:
  102         MiscUtil.PrintError("Failed to setup a writer for output fie %s " % OptionsInfo["Outfile"])
  103     MiscUtil.PrintInfo("Generating file %s..." % OptionsInfo["Outfile"])
  104 
  105     MolCount, ValidMolCount, EnergyFailedCount = ProcessMolecules(Mols, Writer)
  106 
  107     if Writer is not None:
  108         Writer.close()
  109     
  110     MiscUtil.PrintInfo("\nTotal number of molecules: %d" % MolCount)
  111     MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
  112     MiscUtil.PrintInfo("Number of molecules failed during energy calculation: %d" % EnergyFailedCount)
  113     MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount + EnergyFailedCount))
  114 
  115 def ProcessMolecules(Mols, Writer):
  116     """Process and calculate energy of molecules."""
  117     
  118     if OptionsInfo["MPMode"]:
  119         return ProcessMoleculesUsingMultipleProcesses(Mols, Writer)
  120     else:
  121         return ProcessMoleculesUsingSingleProcess(Mols, Writer)
  122 
  123 def ProcessMoleculesUsingSingleProcess(Mols, Writer):
  124     """Process and calculate energy of molecules using a single process."""
  125     
  126     # Intialize Psi4...
  127     MiscUtil.PrintInfo("\nInitializing Psi4...")
  128     Psi4Handle = Psi4Util.InitializePsi4(Psi4RunParams = OptionsInfo["Psi4RunParams"], Psi4OptionsParams = OptionsInfo["Psi4OptionsParams"], PrintVersion = True, PrintHeader = True)
  129     OptionsInfo["psi4"] = Psi4Handle
  130 
  131     # Setup conversion factor for energy units...
  132     SetupEnergyConversionFactor(Psi4Handle)
  133     
  134     MiscUtil.PrintInfo("\nCalculating interaction energy...")
  135     
  136     (MolCount, ValidMolCount, EnergyFailedCount) = [0] * 3
  137     for Mol in Mols:
  138         MolCount += 1
  139         
  140         if not CheckAndValidateMolecule(Mol, MolCount):
  141             continue
  142         
  143         # Setup a Psi4 molecule...
  144         Psi4Mol = SetupPsi4Mol(Psi4Handle, Mol, MolCount)
  145         if Psi4Mol is None:
  146             continue
  147         
  148         ValidMolCount += 1
  149 
  150         CalcStatus, Energy, EnergyDetails = CalculateMoleculeInteractionEnergy(Psi4Handle, Psi4Mol, Mol, MolCount)
  151         
  152         if not CalcStatus:
  153             if not OptionsInfo["QuietMode"]:
  154                 MiscUtil.PrintWarning("Failed to calculate interaction energy for molecule %s" % RDKitUtil.GetMolName(Mol, MolCount))
  155             
  156             EnergyFailedCount += 1
  157             continue
  158 
  159         WriteMolecule(Writer, Mol, Energy, EnergyDetails)
  160     
  161     return (MolCount, ValidMolCount, EnergyFailedCount)
  162 
  163 def ProcessMoleculesUsingMultipleProcesses(Mols, Writer):
  164     """Process and calculate energy of molecules using  process."""
  165     
  166     MPParams = OptionsInfo["MPParams"]
  167     
  168     # Setup data for initializing a worker process...
  169     InitializeWorkerProcessArgs = (MiscUtil.ObjectToBase64EncodedString(Options), MiscUtil.ObjectToBase64EncodedString(OptionsInfo))
  170     
  171     # Setup a encoded mols data iterable for a worker process...
  172     WorkerProcessDataIterable = RDKitUtil.GenerateBase64EncodedMolStrings(Mols)
  173     
  174     # Setup process pool along with data initialization for each process...
  175     if not OptionsInfo["QuietMode"]:
  176         MiscUtil.PrintInfo("\nConfiguring multiprocessing using %s method..." % ("mp.Pool.imap()" if re.match("^Lazy$", MPParams["InputDataMode"], re.I) else "mp.Pool.map()"))
  177         MiscUtil.PrintInfo("NumProcesses: %s; InputDataMode: %s; ChunkSize: %s\n" % (MPParams["NumProcesses"], MPParams["InputDataMode"], ("automatic" if MPParams["ChunkSize"] is None else MPParams["ChunkSize"])))
  178     
  179     ProcessPool = mp.Pool(MPParams["NumProcesses"], InitializeWorkerProcess, InitializeWorkerProcessArgs)
  180     
  181     # Start processing...
  182     if re.match("^Lazy$", MPParams["InputDataMode"], re.I):
  183         Results = ProcessPool.imap(WorkerProcess, WorkerProcessDataIterable, MPParams["ChunkSize"])
  184     elif re.match("^InMemory$", MPParams["InputDataMode"], re.I):
  185         Results = ProcessPool.map(WorkerProcess, WorkerProcessDataIterable, MPParams["ChunkSize"])
  186     else:
  187         MiscUtil.PrintError("The value, %s, specified for \"--inputDataMode\" is not supported." % (MPParams["InputDataMode"]))
  188 
  189     # Print out Psi4 version in the main process...
  190     MiscUtil.PrintInfo("\nInitializing Psi4...\n")
  191     Psi4Handle  = Psi4Util.InitializePsi4(PrintVersion = True, PrintHeader = False)
  192     OptionsInfo["psi4"] = Psi4Handle
  193     
  194     (MolCount, ValidMolCount, EnergyFailedCount) = [0] * 3
  195     for Result in Results:
  196         MolCount += 1
  197         MolIndex, EncodedMol, CalcStatus, Energy, EnergyDetails = Result
  198         
  199         if EncodedMol is None:
  200             continue
  201         
  202         ValidMolCount += 1
  203 
  204         Mol = RDKitUtil.MolFromBase64EncodedMolString(EncodedMol)
  205         
  206         if not CalcStatus:
  207             if not OptionsInfo["QuietMode"]:
  208                 MolName = RDKitUtil.GetMolName(Mol, MolCount)
  209                 MiscUtil.PrintWarning("Failed to calculate energy for molecule %s" % MolName)
  210             
  211             EnergyFailedCount += 1
  212             continue
  213         
  214         WriteMolecule(Writer, Mol, Energy, EnergyDetails)
  215     
  216     return (MolCount, ValidMolCount, EnergyFailedCount)
  217 
  218 def InitializeWorkerProcess(*EncodedArgs):
  219     """Initialize data for a worker process."""
  220     
  221     global Options, OptionsInfo
  222     
  223     if not OptionsInfo["QuietMode"]:
  224         MiscUtil.PrintInfo("Starting process (PID: %s)..." % os.getpid())
  225     
  226     # Decode Options and OptionInfo...
  227     Options = MiscUtil.ObjectFromBase64EncodedString(EncodedArgs[0])
  228     OptionsInfo = MiscUtil.ObjectFromBase64EncodedString(EncodedArgs[1])
  229 
  230     # Psi4 is initialized in the worker process to avoid creation of redundant Psi4
  231     # output files for each process...
  232     OptionsInfo["Psi4Initialized"]  = False
  233 
  234 def InitializePsi4ForWorkerProcess():
  235     """Initialize Psi4 for a worker process."""
  236     
  237     if OptionsInfo["Psi4Initialized"]:
  238         return
  239 
  240     OptionsInfo["Psi4Initialized"] = True
  241     
  242     # Update output file...
  243     OptionsInfo["Psi4RunParams"]["OutputFile"] = Psi4Util.UpdatePsi4OutputFileUsingPID(OptionsInfo["Psi4RunParams"]["OutputFile"], os.getpid())
  244     
  245     # Intialize Psi4...
  246     OptionsInfo["psi4"] = Psi4Util.InitializePsi4(Psi4RunParams = OptionsInfo["Psi4RunParams"], Psi4OptionsParams = OptionsInfo["Psi4OptionsParams"], PrintVersion = False, PrintHeader = True)
  247     
  248     # Setup conversion factor for energy units...
  249     SetupEnergyConversionFactor(OptionsInfo["psi4"])
  250     
  251 def WorkerProcess(EncodedMolInfo):
  252     """Process data for a worker process."""
  253     
  254     if not OptionsInfo["Psi4Initialized"]:
  255         InitializePsi4ForWorkerProcess()
  256     
  257     MolIndex, EncodedMol = EncodedMolInfo
  258     
  259     CalcStatus = False
  260     Energy = None
  261     EnergyDetails = None
  262     
  263     if EncodedMol is None:
  264         return [MolIndex, None, CalcStatus, Energy, EnergyDetails]
  265     
  266     Mol = RDKitUtil.MolFromBase64EncodedMolString(EncodedMol)
  267     MolCount = MolIndex + 1
  268     
  269     if not CheckAndValidateMolecule(Mol, MolCount):
  270         return [MolIndex, None, CalcStatus, Energy, EnergyDetails]
  271     
  272     # Setup a Psi4 molecule...
  273     Psi4Mol = SetupPsi4Mol(OptionsInfo["psi4"], Mol, MolCount)
  274     if Psi4Mol is None:
  275         return [MolIndex, None, CalcStatus, Energy, EnergyDetails]
  276     
  277     CalcStatus, Energy, EnergyDetails = CalculateMoleculeInteractionEnergy(OptionsInfo["psi4"], Psi4Mol, Mol, MolCount)
  278     
  279     return [MolIndex, RDKitUtil.MolToBase64EncodedMolString(Mol, PropertyPickleFlags = Chem.PropertyPickleOptions.MolProps | Chem.PropertyPickleOptions.PrivateProps), CalcStatus, Energy, EnergyDetails]
  280 
  281 def CalculateMoleculeInteractionEnergy(Psi4Handle, Psi4Mol, Mol, MolNum = None):
  282     """Calculate interaction energy for a molecule containing two components."""
  283     
  284     #  Setup reference wave function...
  285     Reference = SetupReferenceWavefunction(Mol)
  286     Psi4Handle.set_options({'Reference': Reference})
  287     
  288     # Setup method name and basis set...
  289     MethodName, BasisSet = SetupMethodNameAndBasisSet(Mol)
  290     MethodAndBasisSet = Psi4Util.JoinMethodNameAndBasisSet(MethodName, BasisSet)
  291     
  292     Status, Energy, EnergyDetails = [False, None, None]
  293     try:
  294         if OptionsInfo["BSSEType"] is None:
  295             Energy = Psi4Handle.energy(MethodAndBasisSet, molecule = Psi4Mol, return_wfn = False)
  296         else:
  297             Energy = Psi4Handle.energy(MethodAndBasisSet, bsse_type = OptionsInfo["BSSEType"], molecule = Psi4Mol, return_wfn = False)
  298         Status = True
  299     except Exception as ErrMsg:
  300         if not OptionsInfo["QuietMode"]:
  301             MiscUtil.PrintWarning("Failed to calculate interaction energy:\n%s\n" % ErrMsg)
  302     
  303     # Retrieve SAPT energy details...
  304     if Status and OptionsInfo["SAPTMode"]:
  305         EnergyTypeMap = OptionsInfo["SAPTEnergyIDsToTypes"]
  306         EnergyDetails = {}
  307         for EnergyType in EnergyTypeMap:
  308             try:
  309                 EnergyDetails[EnergyType] = Psi4Handle.variable(EnergyTypeMap[EnergyType])
  310             except Exception as ErrMsg:
  311                 EnergyDetails[EnergyType] = None
  312                 if not OptionsInfo["QuietMode"]:
  313                     MiscUtil.PrintWarning("Failed to retrieve value for interaction energy components %s:\n%s\n" % (EnergyType, ErrMsg))
  314     
  315     # Convert energy units...
  316     if Status and OptionsInfo["ApplyEnergyConversionFactor"]:
  317         if Energy is not None:
  318             Energy = Energy * OptionsInfo["EnergyConversionFactor"]
  319         if EnergyDetails is not None:
  320             for EnergyType in EnergyDetails:
  321                 if EnergyDetails[EnergyType] is not None:
  322                     EnergyDetails[EnergyType] = EnergyDetails[EnergyType] * OptionsInfo["EnergyConversionFactor"]
  323     
  324     # Clean up
  325     PerformPsi4Cleanup(Psi4Handle)
  326     
  327     return (Status, Energy, EnergyDetails)
  328 
  329 def SetupPsi4Mol(Psi4Handle, Mol, MolCount = None):
  330     """Setup a Psi4 molecule object."""
  331     
  332     MolGeometry = RDKitUtil.GetPsi4XYZFormatString(Mol, NoCom = True, NoReorient = True, CheckFragments = True)
  333     
  334     try:
  335         Psi4Mol = Psi4Handle.geometry(MolGeometry)
  336     except Exception as ErrMsg:
  337         Psi4Mol = None
  338         if not OptionsInfo["QuietMode"]:
  339             MiscUtil.PrintWarning("Failed to create Psi4 molecule from geometry string: %s\n" % ErrMsg)
  340             MolName = RDKitUtil.GetMolName(Mol, MolCount)
  341             MiscUtil.PrintWarning("Ignoring molecule: %s" % MolName)
  342     
  343     return Psi4Mol
  344 
  345 def PerformPsi4Cleanup(Psi4Handle):
  346     """Perform clean up."""
  347     
  348     # Clean up after Psi4 run ...
  349     Psi4Handle.core.clean()
  350     
  351     # Clean up any leftover scratch files...
  352     if OptionsInfo["MPMode"]:
  353         Psi4Util.RemoveScratchFiles(Psi4Handle, OptionsInfo["Psi4RunParams"]["OutputFile"])
  354 
  355 def CheckAndValidateMolecule(Mol, MolCount = None):
  356     """Validate molecule for Psi4 calculations."""
  357 
  358     if Mol is None:
  359         if not OptionsInfo["QuietMode"]:
  360             MiscUtil.PrintInfo("\nProcessing molecule number %s..." % MolCount)
  361         return False
  362     
  363     MolName = RDKitUtil.GetMolName(Mol, MolCount)
  364     if not OptionsInfo["QuietMode"]:
  365         MiscUtil.PrintInfo("\nProcessing molecule %s..." % MolName)
  366     
  367     if RDKitUtil.IsMolEmpty(Mol):
  368         if not OptionsInfo["QuietMode"]:
  369             MiscUtil.PrintWarning("Ignoring empty molecule: %s\n" % MolName)
  370         return False
  371     
  372     if not RDKitUtil.ValidateElementSymbols(RDKitUtil.GetAtomSymbols(Mol)):
  373         if not OptionsInfo["QuietMode"]:
  374             MiscUtil.PrintWarning("Ignoring molecule containing invalid element symbols: %s\n" % MolName)
  375         return False
  376 
  377     # Check for number of fragments...
  378     NumFragments = RDKitUtil.GetNumFragments(Mol)
  379     if OptionsInfo["SAPTMode"] or OptionsInfo["SNSMP2Mode"]:
  380         if NumFragments != 2 :
  381             if not OptionsInfo["QuietMode"]:
  382                 MiscUtil.PrintWarning("Ignoring molecule containing invalid number of fragments: %s. It must contain exaclty 2 fragments for calculation of interaction energy using SAPT or SNS-MP2 methodology.\n" % NumFragments)
  383             return False
  384     else:
  385         if NumFragments == 1:
  386             if not OptionsInfo["QuietMode"]:
  387                 MiscUtil.PrintWarning("Ignoring molecule containing invalid number of fragments: %s. It must contain more than 1 fragment for calculation of interaction energy using counterpoise correctio methodology.\n" % NumFragments)
  388             return False
  389         
  390     # Check for 3D flag...
  391     if not Mol.GetConformer().Is3D():
  392         if not OptionsInfo["QuietMode"]:
  393             MiscUtil.PrintWarning("3D tag is not set for molecule: %s\n" % MolName)
  394     
  395     # Check for missing hydrogens...
  396     if RDKitUtil.AreHydrogensMissingInMolecule(Mol):
  397         if not OptionsInfo["QuietMode"]:
  398             MiscUtil.PrintWarning("Missing hydrogens in molecule: %s\n" % MolName)
  399 
  400     return True
  401 
  402 def SetupMethodNameAndBasisSet(Mol):
  403     """Setup method name and basis set."""
  404 
  405     return (OptionsInfo["MethodName"], OptionsInfo["BasisSet"])
  406 
  407 def SetupReferenceWavefunction(Mol):
  408     """Setup reference wave function."""
  409 
  410     Reference = OptionsInfo["Reference"]
  411     if OptionsInfo["ReferenceAuto"]:
  412         Reference = 'UHF' if (RDKitUtil.GetSpinMultiplicity(Mol) > 1) else 'RHF'
  413     
  414     return Reference
  415 
  416 def SetupEnergyConversionFactor(Psi4Handle):
  417     """Setup converstion factor for energt units. The Psi4 energy units are Hartrees."""
  418     
  419     EnergyUnits = OptionsInfo["EnergyUnits"]
  420     
  421     ApplyConversionFactor = True
  422     if re.match("^kcal\/mol$", EnergyUnits, re.I):
  423         ConversionFactor = Psi4Handle.constants.hartree2kcalmol
  424     elif re.match("^kJ\/mol$", EnergyUnits, re.I):
  425         ConversionFactor = Psi4Handle.constants.hartree2kJmol
  426     elif re.match("^eV$", EnergyUnits, re.I):
  427         ConversionFactor = Psi4Handle.constants.hartree2ev
  428     else:
  429         ApplyConversionFactor = False
  430         ConversionFactor = 1.0
  431     
  432     OptionsInfo["ApplyEnergyConversionFactor"] = ApplyConversionFactor
  433     OptionsInfo["EnergyConversionFactor"] = ConversionFactor
  434 
  435 def WriteMolecule(Writer, Mol, Energy, EnergyDetails):
  436     """Write molecule."""
  437 
  438     # Setup energy...
  439     EnergyValue = "%.*f" % (OptionsInfo["Precision"], Energy) if Energy is not None else ""
  440     Mol.SetProp(OptionsInfo["EnergyDataFieldLabel"], EnergyValue)
  441         
  442     # Setup SAPT energy details...
  443     if EnergyDetails is not None and OptionsInfo["SAPTMode"]:
  444         for EnergyID in OptionsInfo["SAPTEnergyIDs"]:
  445             Energy = EnergyDetails[EnergyID] if EnergyID in EnergyDetails else None
  446             EnergyValue = "%.*f" % (OptionsInfo["Precision"], Energy) if Energy is not None else ""
  447             
  448             Mol.SetProp(OptionsInfo["SAPTEnergyIDsToLabels"][EnergyID], EnergyValue)
  449     
  450     Writer.write(Mol)
  451 
  452 def ProcessMethodNameAndBasisSetOptions():
  453     """Process method name and basis set options."""
  454     
  455     MethodName = Options["--methodName"]
  456     MethodNameAuto = True if re.match("^auto$", MethodName, re.I) else False
  457     if MethodNameAuto:
  458         MethodName = "SAPT0"
  459     SAPTMode = True if re.match("^SAPT", MethodName, re.I) else False
  460     
  461     SNSMP2Mode = True if re.match("^SNS-MP2$", MethodName, re.I) else False
  462     
  463     BasisSet = Options["--basisSet"]
  464     BasisSetAuto = True if re.match("^auto$", BasisSet, re.I) else False
  465     if BasisSetAuto:
  466         if SAPTMode:
  467             BasisSet = "jun-cc-pVDZ"
  468         elif SNSMP2Mode:
  469             BasisSet = ""
  470         else:
  471             MiscUtil.PrintError("A basis set must be explicitly specified using \"-b, --basisSet\" option for, \"%s\", value of \"-m, --methodName\". " % (MethodName))
  472 
  473     if SNSMP2Mode and not MiscUtil.IsEmpty(BasisSet):
  474         MiscUtil.PrintError("The basis set value, \"%s\", specified using \"-b, --basisSet\" option is not valid. It must be an empty string SNS-MP2 calculations." % (Options["--basisSet"]))
  475         
  476     OptionsInfo["MethodName"] = MethodName
  477     OptionsInfo["MethodNameAuto"] = MethodNameAuto
  478     OptionsInfo["SAPTMode"] = SAPTMode
  479     OptionsInfo["SNSMP2Mode"] = SNSMP2Mode
  480 
  481     OptionsInfo["BasisSet"] = BasisSet
  482     OptionsInfo["BasisSetAuto"] = BasisSetAuto
  483     
  484 def ProcessBSSETypeOption():
  485     """Process basis set superposition energy correction option."""
  486 
  487     BSSEType = None if re.match("^None$", Options["--bsseType"], re.I) or MiscUtil.IsEmpty(Options["--bsseType"]) else Options["--bsseType"]
  488     BSSETypeAuto = True if re.match("^auto$", Options["--bsseType"], re.I) else False
  489     
  490     if BSSETypeAuto:
  491         if OptionsInfo["SAPTMode"] or OptionsInfo["SNSMP2Mode"]:
  492             BSSEType = None
  493         elif re.match("^HF3c$", OptionsInfo["MethodName"], re.I):
  494             BSSEType = "noCP"
  495         else:
  496             BSSEType = "CP"
  497     else:
  498         if OptionsInfo["SAPTMode"] or OptionsInfo["SNSMP2Mode"]:
  499             if BSSEType is not None:
  500                 MiscUtil.PrintError("The BSSE type value, \"%s\", specified using \"--bsseType\" option is not valid for SAPT or SNS-MP2 calculations. It must be set to None." % (Options["--bsseType"]))
  501         else:
  502             if BSSEType is None:
  503                 MiscUtil.PrintWarning("The BSSE type value, \"%s\", specified using \"--bsseType\" option is not valid for \"%s\" calculations. Possible values: CP, noCP or VMFC" % (Options["--bsseType"], OptionsInfo["MethodName"]))
  504                 
  505     OptionsInfo["BSSEType"] = BSSEType
  506     OptionsInfo["BSSETypeAuto"] = BSSETypeAuto
  507 
  508 def ProcessSAPTEnergyDataFieldLabelsOption():
  509     """Process data field labels for SAPT interaction energy components."""
  510 
  511     ParamsOptionName = "--energySAPTDataFieldLabels"
  512     ParamsOptionValue = Options["--energySAPTDataFieldLabels"]
  513     
  514     ParamsIDs = ["ElectrostaticEnergy", "ExchangeEnergy", "InductionEnergy", "DispersionEnergy"]
  515     ParamsIDsToTypes = {"ElectrostaticEnergy": "SAPT ELST ENERGY", "ExchangeEnergy": "SAPT EXCH ENERGY", "InductionEnergy": "SAPT IND ENERGY", "DispersionEnergy": "SAPT DISP ENERGY"}
  516     
  517     UnitsLabel = "(%s)" % Options["--energyUnits"]
  518     ParamsIDsToLabels = {"ElectrostaticEnergy": "Psi4_SAPT_Electrostatic_Energy %s" % UnitsLabel, "ExchangeEnergy": "Psi4_SAPT_Exchange_Energy %s" % UnitsLabel, "InductionEnergy": "Psi4_SAPT_Induction_Energy %s" % UnitsLabel, "DispersionEnergy": "Psi4_SAPT_Dispersion_Energy %s" % UnitsLabel}
  519     
  520     if re.match("^auto$", ParamsOptionValue, re.I):
  521         OptionsInfo["SAPTEnergyIDs"] = ParamsIDs
  522         OptionsInfo["SAPTEnergyIDsToTypes"] = ParamsIDsToTypes
  523         OptionsInfo["SAPTEnergyIDsToLabels"] = ParamsIDsToLabels
  524         return
  525     
  526     # Setup a canonical paramater names...
  527     ValidParamNames = []
  528     CanonicalParamNamesMap = {}
  529     for ParamName in sorted(ParamsIDsToLabels):
  530         ValidParamNames.append(ParamName)
  531         CanonicalParamNamesMap[ParamName.lower()] = ParamName
  532     
  533     ParamsOptionValue = ParamsOptionValue.strip()
  534     if not ParamsOptionValue:
  535         PrintError("No valid parameter name and value pairs specified using \"%s\" option" % ParamsOptionName)
  536     
  537     ParamsOptionValueWords = ParamsOptionValue.split(",")
  538     if len(ParamsOptionValueWords) % 2:
  539         MiscUtil.PrintError("The number of comma delimited paramater names and values, %d, specified using \"%s\" option must be an even number." % (len(ParamsOptionValueWords), ParamsOptionName))
  540     
  541     # Validate paramater name and value pairs...
  542     for Index in range(0, len(ParamsOptionValueWords), 2):
  543         Name = ParamsOptionValueWords[Index].strip()
  544         Value = ParamsOptionValueWords[Index + 1].strip()
  545 
  546         CanonicalName = Name.lower()
  547         if  not CanonicalName in CanonicalParamNamesMap:
  548             MiscUtil.PrintError("The parameter name, %s, specified using \"%s\" is not a valid name. Supported parameter names: %s" % (Name, ParamsOptionName, " ".join(ValidParamNames)))
  549 
  550         ParamName = CanonicalParamNamesMap[CanonicalName]
  551         ParamValue = Value
  552         
  553         # Set value...
  554         ParamsIDsToLabels[ParamName] = ParamValue
  555     
  556     OptionsInfo["SAPTEnergyIDs"] = ParamsIDs
  557     OptionsInfo["SAPTEnergyIDsToTypes"] = ParamsIDsToTypes
  558     OptionsInfo["SAPTEnergyIDsToLabels"] = ParamsIDsToLabels
  559 
  560 def ProcessOptions():
  561     """Process and validate command line arguments and options."""
  562     
  563     MiscUtil.PrintInfo("Processing options...")
  564     
  565     # Validate options...
  566     ValidateOptions()
  567     
  568     OptionsInfo["Infile"] = Options["--infile"]
  569     ParamsDefaultInfoOverride = {"RemoveHydrogens": False}
  570     OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters("--infileParams", Options["--infileParams"], InfileName = Options["--infile"], ParamsDefaultInfo = ParamsDefaultInfoOverride)
  571     
  572     OptionsInfo["Outfile"] = Options["--outfile"]
  573     OptionsInfo["OutfileParams"] = MiscUtil.ProcessOptionOutfileParameters("--outfileParams", Options["--outfileParams"])
  574     
  575     OptionsInfo["Overwrite"] = Options["--overwrite"]
  576 
  577     # Method and basis set...
  578     ProcessMethodNameAndBasisSetOptions()
  579 
  580     # BSSEType option...
  581     ProcessBSSETypeOption()
  582     
  583     # Reference wavefunction...
  584     OptionsInfo["Reference"] = Options["--reference"]
  585     OptionsInfo["ReferenceAuto"] = True if re.match("^auto$", Options["--reference"], re.I) else False
  586     
  587     # Run and options parameters...
  588     OptionsInfo["Psi4OptionsParams"] = Psi4Util.ProcessPsi4OptionsParameters("--psi4OptionsParams", Options["--psi4OptionsParams"])
  589     OptionsInfo["Psi4RunParams"] = Psi4Util.ProcessPsi4RunParameters("--psi4RunParams", Options["--psi4RunParams"], InfileName = OptionsInfo["Infile"])
  590 
  591     # Interaction energy units...
  592     OptionsInfo["EnergyUnits"] = Options["--energyUnits"]
  593     
  594     # Total interaction energy label...
  595     EnergyDataFieldLabel = Options["--energyDataFieldLabel"]
  596     if re.match("^auto$", EnergyDataFieldLabel, re.I):
  597         EnergyLabel = "Psi4_Interaction_Energy"
  598         if OptionsInfo["SAPTMode"]:
  599             EnergyLabel = "Psi4_SAPT_Interaction_Energy"
  600         elif OptionsInfo["SNSMP2Mode"]:
  601             EnergyLabel = "Psi4_SNS-MP2_Interaction_Energy"
  602         EnergyDataFieldLabel = "%s %s" % (EnergyLabel, Options["--energyUnits"])
  603             
  604     OptionsInfo["EnergyDataFieldLabel"] = EnergyDataFieldLabel
  605 
  606     ProcessSAPTEnergyDataFieldLabelsOption()
  607 
  608     OptionsInfo["MPMode"] = True if re.match("^yes$", Options["--mp"], re.I) else False
  609     OptionsInfo["MPParams"] = MiscUtil.ProcessOptionMultiprocessingParameters("--mpParams", Options["--mpParams"])
  610     
  611     OptionsInfo["Precision"] = int(Options["--precision"])
  612     OptionsInfo["QuietMode"] = True if re.match("^yes$", Options["--quiet"], re.I) else False
  613     
  614 def RetrieveOptions():
  615     """Retrieve command line arguments and options."""
  616     
  617     # Get options...
  618     global Options
  619     Options = docopt(_docoptUsage_)
  620     
  621     # Set current working directory to the specified directory...
  622     WorkingDir = Options["--workingdir"]
  623     if WorkingDir:
  624         os.chdir(WorkingDir)
  625     
  626     # Handle examples option...
  627     if "--examples" in Options and Options["--examples"]:
  628         MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
  629         sys.exit(0)
  630 
  631 def ValidateOptions():
  632     """Validate option values."""
  633     
  634     MiscUtil.ValidateOptionTextValue("--bsseType", Options["--bsseType"], "CP noCP VMFC None auto")
  635     
  636     MiscUtil.ValidateOptionTextValue("--energyUnits", Options["--energyUnits"], "Hartrees kcal/mol kJ/mol eV")
  637     
  638     MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
  639     MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "sdf sd mol")
  640     
  641     MiscUtil.ValidateOptionFileExt("-o, --outfile", Options["--outfile"], "sdf sd")
  642     MiscUtil.ValidateOptionsOutputFileOverwrite("-o, --outfile", Options["--outfile"], "--overwrite", Options["--overwrite"])
  643     MiscUtil.ValidateOptionsDistinctFileNames("-i, --infile", Options["--infile"], "-o, --outfile", Options["--outfile"])
  644     
  645     MiscUtil.ValidateOptionTextValue("--mp", Options["--mp"], "yes no")
  646     MiscUtil.ValidateOptionIntegerValue("-p, --precision", Options["--precision"], {">": 0})
  647     
  648     MiscUtil.ValidateOptionTextValue("-q, --quiet", Options["--quiet"], "yes no")
  649 
  650 # Setup a usage string for docopt...
  651 _docoptUsage_ = """
  652 Psi4CalculateInteractionEnergy.py - Calculate interaction energy
  653 
  654 Usage:
  655     Psi4CalculateInteractionEnergy.py [--basisSet <text>] [--bsseType <CP, noCP, VMFC, or None>]
  656                                       [--energyDataFieldLabel <text>] [--energySAPTDataFieldLabels <Type,Label,...,...>]
  657                                       [--energyUnits <text>] [--infileParams <Name,Value,...>] [--methodName <text>]
  658                                       [--mp <yes or no>] [--mpParams <Name, Value,...>] [ --outfileParams <Name,Value,...> ]
  659                                       [--overwrite] [--precision <number> ] [--psi4OptionsParams <Name,Value,...>]
  660                                       [--psi4RunParams <Name,Value,...>] [--quiet <yes or no>]
  661                                       [--reference <text>] [-w <dir>] -i <infile> -o <outfile> 
  662     Psi4CalculateInteractionEnergy.py -h | --help | -e | --examples
  663 
  664 Description:
  665     Calculate interaction energy for molecules using a specified method name and
  666     basis set. The molecules must contain exactly two fragments or disconnected
  667     components for Symmetry Adapted Perturbation Theory  (SAPT) [ Ref 154-155 ]
  668     and Spin-Network-Scaled MP2 (SNS-MP2) [ Ref 156] calculations and more than
  669     one fragment for all other calculations. An arbitrary number of fragments may be
  670     present in a molecule for Basis Set Superposition Energy (BSSE) correction
  671     calculations.
  672 
  673     The interaction energy is calculated at SAPT0 / jun-cc-pVDZ level of theory by
  674     default. The SAPT0 calculations are relatively fast but less accurate. You may
  675     want to consider calculating interaction energy at WB97X-D3 / aug-cc-pVTZ,
  676     B3LYP-D3 / aug-cc-pVTZ, or higher level of theory [ Ref 157 ] to improve the
  677     accuracy of your results. The  WB97X-D3 and B3LYP-D3 calculations rely on
  678     the presence of DFTD3 and gCP Psi4 plugins in your environment.
  679 
  680     The molecules must have 3D coordinates in input file. The molecular geometry
  681     is not optimized before the calculation. In addition, hydrogens must be present
  682     for all molecules in input file. The 3D coordinates are not modified during the
  683     calculation.
  684 
  685     A Psi4 XYZ format geometry string is automatically generated for each molecule
  686     in input file. It contains atom symbols and 3D coordinates for each atom in a
  687     molecule. In addition, the formal charge and spin multiplicity are present in the
  688     the geometry string. These values are either retrieved from molecule properties
  689     named 'FormalCharge' and 'SpinMultiplicty' or dynamically calculated for a
  690     molecule. A double dash separates each fragment or component in a molecule.
  691     The same formal charge and multiplicity values are assigned to each fragment
  692     in a molecule.
  693 
  694     The supported input file formats are: Mol (.mol), SD (.sdf, .sd)
  695 
  696     The supported output file formats are: SD (.sdf, .sd)
  697 
  698 Options:
  699     -b, --basisSet <text>  [default: auto]
  700         Basis set to use for interaction energy calculation. Default: jun-cc-pVDZ for
  701         SAPT calculations; None for SNS-MP2 calculations to use its default basis
  702         set;  otherwise, it must be explicitly specified using this option. The specified
  703         value must be a valid Psi4 basis set. No validation is performed. You may set
  704         an empty string as a value for the basis set.
  705         
  706         The following list shows a representative sample of basis sets available
  707         in Psi4:
  708             
  709             STO-3G, 6-31G, 6-31+G, 6-31++G, 6-31G*, 6-31+G*,  6-31++G*, 
  710             6-31G**, 6-31+G**, 6-31++G**, 6-311G, 6-311+G, 6-311++G,
  711             6-311G*, 6-311+G*, 6-311++G*, 6-311G**, 6-311+G**, 6-311++G**,
  712             cc-pVDZ, cc-pCVDZ, aug-cc-pVDZ, cc-pVDZ-DK, cc-pCVDZ-DK, def2-SVP,
  713             def2-SVPD, def2-TZVP, def2-TZVPD, def2-TZVPP, def2-TZVPPD
  714             
  715     --bsseType <CP, noCP, VMFC, or None>  [default: auto]
  716         Type of Basis Set Superposition Energy (BSSE) correction to apply during the
  717         calculation of interaction energy. Possible values:
  718              
  719             CP: Counterpoise corrected interaction energy
  720             noCP:  Supramolecular interaction energy without any CP correction
  721             VMFC: Valiron-Mayer Function Counterpoise correction
  722             None: The Psi4 option 'bsse_type' is not passed to the energy
  723                 function during the calculation of interaction energy
  724             
  725         Default values:
  726              
  727             None: SAPT and SNS-MP2 calculations. An explicit bsse_type option is not
  728                 valid for these calculations.
  729             HF3c: noCP to use built-in correction
  730             CP: All other calculations
  731             
  732     --energyDataFieldLabel <text>  [default: auto]
  733         Energy data field label for writing interaction energy values. Default:
  734         Psi4_SAPT_Interaction_Energy (<Units>)  for SAPT calculation; 
  735         Psi4_SNS-MP2_Interaction_Energy (<Units>) for SNS-MP2 calculation;
  736         otherwise, Psi4_Interaction_Energy (<Units>) 
  737     --energySAPTDataFieldLabels <Type,Label,...,...>  [default: auto]
  738         A comma delimted interaction energy type and data field label value pairs
  739         for writing individual components of total SAPT interaction energy.
  740         
  741         The supported SAPT energy types along with their default data field label
  742         values are shown below:
  743             
  744             ElectrostaticEnergy, Psi4_SAPT_Electrostatic_Energy (<Units>),
  745             ExchangeEnergy, Psi4_SAPT_Exchange_Energy (<Units>),
  746             InductionEnergy, Psi4_SAPT_Induction_Energy (<Units>),
  747             DispersionEnergy, Psi4_SAPT_Dispersion_Energy (<Units>)
  748             
  749     --energyUnits <text>  [default: kcal/mol]
  750         Energy units. Possible values: Hartrees, kcal/mol, kJ/mol, or eV.
  751     -e, --examples
  752         Print examples.
  753     -h, --help
  754         Print this help message.
  755     -i, --infile <infile>
  756         Input file name.
  757     --infileParams <Name,Value,...>  [default: auto]
  758         A comma delimited list of parameter name and value pairs for reading
  759         molecules from files. The supported parameter names for different file
  760         formats, along with their default values, are shown below:
  761             
  762             SD, MOL: removeHydrogens,no,sanitize,yes,strictParsing,yes
  763             
  764     -m, --methodName <text>  [default: auto]
  765         Method to use for interaction energy calculation. Default: SAPT0. The
  766         specified value must be a valid Psi4 method name. No validation is
  767         performed.
  768         
  769         The following list shows a representative sample of methods available
  770         in Psi4:
  771             
  772             SAPT0, SAPT2, SAPT2+, SAPT2+(CCD), SAPT2+DMP2, SAPT2+(CCD)DMP2
  773             SAPT2+(3), SAPT2+(3)(CCD), SAPT2+DMP2, SAPT2+(CCD)DMP2,
  774             SAPT2+3, SAPT2+3(CCD), SAPT2+(3)DMP2, SAPT2+3(CCD)DMP2, SNS-MP2,
  775             B1LYP, B2PLYP, B2PLYP-D3BJ, B2PLYP-D3MBJ, B3LYP, B3LYP-D3BJ,
  776             B3LYP-D3MBJ, CAM-B3LYP, CAM-B3LYP-D3BJ, HF, HF-D3BJ,  HF3c, M05,
  777             M06, M06-2x, M06-HF, M06-L, MN12-L, MN15, MN15-D3BJ,PBE, PBE0,
  778             PBEH3c, PW6B95, PW6B95-D3BJ, WB97, WB97X, WB97X-D, WB97X-D3BJ
  779             
  780     --mp <yes or no>  [default: no]
  781         Use multiprocessing.
  782          
  783         By default, input data is retrieved in a lazy manner via mp.Pool.imap()
  784         function employing lazy RDKit data iterable. This allows processing of
  785         arbitrary large data sets without any additional requirements memory.
  786         
  787         All input data may be optionally loaded into memory by mp.Pool.map()
  788         before starting worker processes in a process pool by setting the value
  789         of 'inputDataMode' to 'InMemory' in '--mpParams' option.
  790         
  791         A word to the wise: The default 'chunkSize' value of 1 during 'Lazy' input
  792         data mode may adversely impact the performance. The '--mpParams' section
  793         provides additional information to tune the value of 'chunkSize'.
  794     --mpParams <Name,Value,...>  [default: auto]
  795         A comma delimited list of parameter name and value pairs to configure
  796         multiprocessing.
  797         
  798         The supported parameter names along with their default and possible
  799         values are shown below:
  800         
  801             chunkSize, auto
  802             inputDataMode, Lazy   [ Possible values: InMemory or Lazy ]
  803             numProcesses, auto   [ Default: mp.cpu_count() ]
  804         
  805         These parameters are used by the following functions to configure and
  806         control the behavior of multiprocessing: mp.Pool(), mp.Pool.map(), and
  807         mp.Pool.imap().
  808         
  809         The chunkSize determines chunks of input data passed to each worker
  810         process in a process pool by mp.Pool.map() and mp.Pool.imap() functions.
  811         The default value of chunkSize is dependent on the value of 'inputDataMode'.
  812         
  813         The mp.Pool.map() function, invoked during 'InMemory' input data mode,
  814         automatically converts RDKit data iterable into a list, loads all data into
  815         memory, and calculates the default chunkSize using the following method
  816         as shown in its code:
  817         
  818             chunkSize, extra = divmod(len(dataIterable), len(numProcesses) * 4)
  819             if extra: chunkSize += 1
  820         
  821         For example, the default chunkSize will be 7 for a pool of 4 worker processes
  822         and 100 data items.
  823         
  824         The mp.Pool.imap() function, invoked during 'Lazy' input data mode, employs
  825         'lazy' RDKit data iterable to retrieve data as needed, without loading all the
  826         data into memory. Consequently, the size of input data is not known a priori.
  827         It's not possible to estimate an optimal value for the chunkSize. The default 
  828         chunkSize is set to 1.
  829         
  830         The default value for the chunkSize during 'Lazy' data mode may adversely
  831         impact the performance due to the overhead associated with exchanging
  832         small chunks of data. It is generally a good idea to explicitly set chunkSize to
  833         a larger value during 'Lazy' input data mode, based on the size of your input
  834         data and number of processes in the process pool.
  835         
  836         The mp.Pool.map() function waits for all worker processes to process all
  837         the data and return the results. The mp.Pool.imap() function, however,
  838         returns the the results obtained from worker processes as soon as the
  839         results become available for specified chunks of data.
  840         
  841         The order of data in the results returned by both mp.Pool.map() and 
  842         mp.Pool.imap() functions always corresponds to the input data.
  843     -o, --outfile <outfile>
  844         Output file name.
  845     --outfileParams <Name,Value,...>  [default: auto]
  846         A comma delimited list of parameter name and value pairs for writing
  847         molecules to files. The supported parameter names for different file
  848         formats, along with their default values, are shown below:
  849             
  850             SD: kekulize,yes,forceV3000,no
  851             
  852     --overwrite
  853         Overwrite existing files.
  854     --precision <number>  [default: 6]
  855         Floating point precision for writing energy values.
  856     --psi4OptionsParams <Name,Value,...>  [default: none]
  857         A comma delimited list of Psi4 option name and value pairs for setting
  858         global and module options. The names are 'option_name' for global options
  859         and 'module_name__option_name' for options local to a module. The
  860         specified option names must be valid Psi4 names. No validation is
  861         performed.
  862         
  863         The specified option name and  value pairs are processed and passed to
  864         psi4.set_options() as a dictionary. The supported value types are float,
  865         integer, boolean, or string. The float value string is converted into a float.
  866         The valid values for a boolean string are yes, no, true, false, on, or off. 
  867     --psi4RunParams <Name,Value,...>  [default: auto]
  868         A comma delimited list of parameter name and value pairs for configuring
  869         Psi4 jobs.
  870         
  871         The supported parameter names along with their default and possible
  872         values are shown below:
  873              
  874             MemoryInGB, 1
  875             NumThreads, 1
  876             OutputFile, auto   [ Possible  values: stdout, quiet, or FileName ]
  877             ScratchDir, auto   [ Possivle values: DirName]
  878             RemoveOutputFile, yes   [ Possible values: yes, no, true, or false]
  879             
  880         These parameters control the runtime behavior of Psi4.
  881         
  882         The default file name for 'OutputFile' is <InFileRoot>_Psi4.out. The PID
  883         is appended to output file name during multiprocessing as shown:
  884         <InFileRoot>_Psi4_<PIDNum>.out. The 'stdout' value for 'OutputType'
  885         sends Psi4 output to stdout. The 'quiet' or 'devnull' value suppresses
  886         all Psi4 output.
  887         
  888         The default 'Yes' value of 'RemoveOutputFile' option forces the removal
  889         of any existing Psi4 before creating new files to append output from
  890         multiple Psi4 runs.
  891         
  892         The option 'ScratchDir' is a directory path to the location of scratch
  893         files. The default value corresponds to Psi4 default. It may be used to
  894         override the deafult path.
  895     -q, --quiet <yes or no>  [default: no]
  896         Use quiet mode. The warning and information messages will not be printed.
  897     -r, --reference <text>  [default: auto]
  898         Reference wave function to use for interaction energy calculation. Default: RHF
  899         or UHF. The default values are Restricted Hartree-Fock (RHF) for closed-shell
  900         molecules with all electrons paired and Unrestricted Hartree-Fock (UHF) for
  901        open-shell molecules with unpaired electrons.
  902         
  903         The specified value must be a valid Psi4 reference wave function. No validation
  904         is performed. For example: ROHF, CUHF, RKS, etc.
  905         
  906         The spin multiplicity determines the default value of reference wave function
  907         for input molecules. It is calculated from number of free radical electrons using
  908         Hund's rule of maximum multiplicity defined as 2S + 1 where S is the total
  909         electron spin. The total spin is 1/2 the number of free radical electrons in a 
  910         molecule. The value of 'SpinMultiplicity' molecule property takes precedence
  911         over the calculated value of spin multiplicity.
  912         
  913         The 'SpinMultiplicity' molecule property may contain multiples values
  914         corresponding to the number of fragments in a molecule. This property must
  915         not be set for automatic determination of the reference wave functionn.
  916     -w, --workingdir <dir>
  917         Location of working directory which defaults to the current directory.
  918 
  919 Examples:
  920     To calculate interaction energy using SAPT0/aug-cc-pVDZ for molecules in a
  921     SD file, use RHF and UHF for closed-shell and open-shell molecules, and write
  922     a new SD file, type:
  923 
  924         % Psi4CalculateInteractionEnergy.py -i Psi4SampleDimers3D.sdf
  925           -o Psi4SampleDimers3DOut.sdf
  926 
  927     To run the first example for freezing core electrons and setting SCF type to DF
  928     and write out a new SD file, type:
  929 
  930         % Psi4CalculateInteractionEnergy.py --psi4OptionsParams "scf_type, df,
  931           freeze_core, true" -i Psi4SampleDimers3D.sdf -o
  932           Psi4SampleDimers3DOut.sdf
  933 
  934     To calculate interaction energy using SNS2-MP methodology for molecules
  935     in a SD containing 3D structures and write a new SD file, type:
  936 
  937         % Psi4CalculateInteractionEnergy.py -m "sns-mp2"
  938           -i Psi4SampleDimers3D.sdf -o Psi4SampleDimers3DOut.sdf
  939 
  940     To calculate interaction energy at WB97X-D3/aug-cc-pVTZ level of theory,
  941     along with explicit Psi4 run time paramaters, for molecules in a SD containing
  942     3D structures and write a new SD file, type:
  943 
  944         % Psi4CalculateInteractionEnergy.py -m WB97X-D3 -b aug-cc-pVTZ
  945           --bsseType CP --psi4RunParams "NumThreads,4,MemoryInGB,6"
  946           -i Psi4SampleDimers3D.sdf -o Psi4SampleDimers3DOut.sdf
  947 
  948     To calculate interaction energy at  B3LYP-D3/aug-cc-pVTZ level of theory using
  949     default Psi4 run time paramaters for molecules in a SD containing 3D structures
  950     and write a new SD file, type:
  951 
  952         % Psi4CalculateInteractionEnergy.py -m B3LYP-D3 -b aug-cc-pVTZ
  953           --bsseType CP -i Psi4SampleDimers3D.sdf -o Psi4SampleDimers3DOut.sdf
  954 
  955     To calculate interaction energy at  B3LYP-D3/aug-cc-pVTZ level of theory, along
  956     with specifying grid resolution using Psi4 options and explicit Psi4 run time
  957     paramaters, for molecules in a SD containing 3D structures
  958     and write a new SD file, type:
  959 
  960         % Psi4CalculateInteractionEnergy.py -m B3LYP-D3 -b aug-cc-pVTZ
  961           --bsseType CP --psi4OptionsParams "dft_spherical_points, 302,
  962           dft_radial_points, 75" --psi4RunParams "NumThreads,4,MemoryInGB,6"
  963           -i Psi4SampleDimers3D.sdf -o Psi4SampleDimers3DOut.sdf
  964 
  965     To calculate interaction energy at  HF3c level of theory using built-in basis set
  966     for molecules in a SD containing 3D structures and write a new SD file, type:
  967 
  968         % Psi4CalculateInteractionEnergy.py -m HF3c -b "" --bsseType noCP
  969           -i Psi4SampleDimers3D.sdf -o Psi4SampleDimers3DOut.sdf
  970       
  971     To calculate interaction energy at  CCSD(T)/aug-cc-pVDZ level of theory using
  972     default Psi4 run time paramaters for molecules in a SD containing 3D structures
  973     and write a new SD file, type:
  974 
  975         % Psi4CalculateInteractionEnergy.py -m "ccsd(t)" -b "aug-cc-pvdz"
  976           -i Psi4SampleDimers3D.sdf -o Psi4SampleDimers3DOut.sdf
  977 
  978     To run the first example in multiprocessing mode on all available CPUs
  979     without loading all data into memory and write out a SD file, type:
  980 
  981         % Psi4CalculateInteractionEnergy.py --mp yes -i Psi4SampleDimers3D.sdf
  982           -o Psi4SampleDimers3DOut.sdf
  983 
  984     To run the first example in multiprocessing mode on all available CPUs
  985     by loading all data into memory and write out a SD file, type:
  986 
  987         % Psi4CalculateInteractionEnergy.py  --mp yes --mpParams "inputDataMode,
  988           InMemory" -i Psi4SampleDimers3D.sdf  -o Psi4SampleDimers3DOut.sdf
  989 
  990     To run the first example in multiprocessing mode on all available CPUs
  991     without loading all data into memory along with multiple threads for each
  992     Psi4 run and write out a SD file, type:
  993 
  994         % Psi4CalculateInteractionEnergy.py --mp yes --psi4RunParams "NumThreads,2"
  995           -i Psi4SampleDimers3D.sdf -o Psi4SampleDimers3DOut.sdf
  996 
  997 Author:
  998     Manish Sud(msud@san.rr.com)
  999 
 1000 See also:
 1001     Psi4CalculateEnergy.py, Psi4CalculatePartialCharges.py, Psi4PerformMinimization.py,
 1002     Psi4GenerateConformers.py
 1003 
 1004 Copyright:
 1005     Copyright (C) 2024 Manish Sud. All rights reserved.
 1006 
 1007     The functionality available in this script is implemented using Psi4, an
 1008     open source quantum chemistry software package, and RDKit, an open
 1009     source toolkit for cheminformatics developed by Greg Landrum.
 1010 
 1011     This file is part of MayaChemTools.
 1012 
 1013     MayaChemTools is free software; you can redistribute it and/or modify it under
 1014     the terms of the GNU Lesser General Public License as published by the Free
 1015     Software Foundation; either version 3 of the License, or (at your option) any
 1016     later version.
 1017 
 1018 """
 1019 
 1020 if __name__ == "__main__":
 1021     main()