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