#
#@BEGIN LICENSE
#
# PSI4: an ab initio quantum chemistry software package
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
#@END LICENSE
#
"""Module with utility functions used by several Python functions."""
import os
import sys
import pickle
import psi4
import inputparser
from psiexceptions import *
[docs]def kwargs_lower(kwargs):
"""Function to rebuild and return *kwargs* dictionary
with all keys made lowercase. Should be called by every
function that could be called directly by the user.
"""
caseless_kwargs = {}
if sys.hexversion < 0x03000000:
# Python 2; we have to explicitly use an iterator
for key, value in kwargs.iteritems():
caseless_kwargs[key.lower()] = value
else:
# Python 3; an iterator is implicit
for key, value in kwargs.items():
caseless_kwargs[key.lower()] = value
return caseless_kwargs
[docs]def get_psifile(fileno, pidspace=str(os.getpid())):
"""Function to return the full path and filename for psi file
*fileno* (e.g., psi.32) in current namespace *pidspace*.
"""
psioh = psi4.IOManager.shared_object()
psio = psi4.IO.shared_object()
filepath = psioh.get_file_path(fileno)
namespace = psio.get_default_namespace()
targetfile = filepath + 'psi' + '.' + pidspace + '.' + namespace + '.' + str(fileno)
return targetfile
[docs]def format_molecule_for_input(mol):
"""Function to return a string of the output of
:py:func:`inputparser.process_input` applied to the XYZ
format of molecule, passed as either fragmented
geometry string *mol* or molecule instance *mol*.
Used to capture molecule information from database
modules and for distributed (sow/reap) input files.
For the reverse, see :py:func:`molutil.geometry`.
"""
# when mol is already a string
if isinstance(mol, basestring):
mol_string = mol
mol_name = ''
# when mol is psi4.Molecule or qcdb.Molecule object
else:
# save_string_for_psi4 is the more detailed choice as it includes fragment
# (and possibly no_com/no_reorient) info. but this is only available
# for qcdb Molecules. Since save_string_xyz was added to libmints just
# for the sow/reap purpose, may want to unify these fns sometime.
# the time for unification is nigh
mol_string = mol.create_psi4_string_from_molecule()
#try:
# mol_string = mol.save_string_for_psi4()
#except AttributeError:
# mol_string = mol.save_string_xyz()
mol_name = mol.name()
commands = 'inputparser.process_input("""\nmolecule %s {\n%s\n}\n""", 0)\n' % (mol_name, mol_string)
return eval(commands)
[docs]def format_options_for_input():
"""Function to return a string of commands to replicate the
current state of user-modified options. Used to capture C++
options information for distributed (sow/reap) input files.
.. caution:: Some features are not yet implemented. Buy a developer a coffee.
- Does not cover local (as opposed to global) options.
- Does not work with array-type options.
"""
commands = ''
commands += """\npsi4.set_memory(%s)\n\n""" % (psi4.get_memory())
for chgdopt in psi4.get_global_option_list():
if psi4.has_global_option_changed(chgdopt):
chgdoptval = psi4.get_global_option(chgdopt)
if isinstance(chgdoptval, basestring):
commands += """psi4.set_global_option('%s', '%s')\n""" % (chgdopt, chgdoptval)
elif isinstance(chgdoptval, int) or isinstance(chgdoptval, float):
commands += """psi4.set_global_option('%s', %s)\n""" % (chgdopt, chgdoptval)
else:
raise ValidationError('Option \'%s\' is not of a type (string, int, float, bool) that can be processed.' % (chgdopt))
return commands
[docs]def format_kwargs_for_input(filename, lmode=1, **kwargs):
"""Function to pickle to file *filename* the options dictionary
*kwargs*. Mode *lmode* =2 pickles appropriate settings for
reap mode. Used to capture Python options information for
distributed (sow/reap) input files.
"""
if lmode == 2:
kwargs['mode'] = 'reap'
kwargs['linkage'] = os.getpid()
filename.write('''\npickle_kw = ("""''')
pickle.dump(kwargs, filename)
filename.write('''""")\n''')
filename.write("""\nkwargs = pickle.loads(pickle_kw)\n""")
if lmode == 2:
kwargs['mode'] = 'sow'
del kwargs['linkage']
[docs]def drop_duplicates(seq):
"""Function that given an array *seq*, returns an array without any duplicate
entries. There is no guarantee of which duplicate entry is dropped.
"""
noDupes = []
[noDupes.append(i) for i in seq if not noDupes.count(i)]
return noDupes
[docs]def all_casings(input_string):
"""Function to return a generator of all lettercase permutations
of *input_string*.
"""
if not input_string:
yield ""
else:
first = input_string[:1]
if first.lower() == first.upper():
for sub_casing in all_casings(input_string[1:]):
yield first + sub_casing
else:
for sub_casing in all_casings(input_string[1:]):
yield first.lower() + sub_casing
yield first.upper() + sub_casing
[docs]def getattr_ignorecase(module, attr):
"""Function to extract attribute *attr* from *module* if *attr*
is available in any possible lettercase permutation. Returns
attribute if available, None if not.
"""
array = None
for per in list(all_casings(attr)):
try:
getattr(module, per)
except AttributeError:
pass
else:
array = getattr(module, per)
break
return array
[docs]def import_ignorecase(module):
"""Function to import *module* in any possible lettercase
permutation. Returns module object if available, None if not.
"""
modobj = None
for per in list(all_casings(module)):
try:
modobj = __import__(per)
except ImportError:
pass
else:
break
return modobj
[docs]def extract_sowreap_from_output(sowout, quantity, sownum, linkage, allvital=False):
"""Function to examine file *sowout* from a sow/reap distributed job
for formatted line with electronic energy information about index
*sownum* to be used for construction of *quantity* computations as
directed by master input file with *linkage* kwarg. When file *sowout*
is missing or incomplete files, function will either return zero
(*allvital* is ``False``) or terminate (*allvital* is ``True``) since
some sow/reap procedures can produce meaningful results (database)
from an incomplete set of sown files, while others cannot (gradient,
hessian).
"""
E = 0.0
try:
freagent = open('%s.out' % (sowout), 'r')
except IOError:
if allvital:
raise ValidationError('Aborting upon output file \'%s.out\' not found.\n' % (sowout))
else:
ValidationError('Aborting upon output file \'%s.out\' not found.\n' % (sowout))
return 0.0
else:
while True:
line = freagent.readline()
if not line:
if E == 0.0:
if allvital:
raise ValidationError('Aborting upon output file \'%s.out\' has no %s RESULT line.\n' % (sowout, quantity))
else:
ValidationError('Aborting upon output file \'%s.out\' has no %s RESULT line.\n' % (sowout, quantity))
break
s = line.split()
if (len(s) != 0) and (s[0:3] == [quantity, 'RESULT:', 'computation']):
if int(s[3]) != linkage:
raise ValidationError('Output file \'%s.out\' has linkage %s incompatible with master.in linkage %s.'
% (sowout, str(s[3]), str(linkage)))
if s[6] != str(sownum + 1):
raise ValidationError('Output file \'%s.out\' has nominal affiliation %s incompatible with item %s.'
% (sowout, s[6], str(sownum + 1)))
if (s[8:10] == ['electronic', 'energy']):
E = float(s[10])
psi4.print_out('%s RESULT: electronic energy = %20.12f\n' % (quantity, E))
freagent.close()
return E