Source code for psi4.driver.p4util.util

#
# @BEGIN LICENSE
#
# Psi4: an open-source quantum chemistry software package
#
# Copyright (c) 2007-2024 The Psi4 Developers.
#
# The copyrights for code used from other parties are included in
# the corresponding files.
#
# This file is part of Psi4.
#
# Psi4 is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3.
#
# Psi4 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with Psi4; 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 for use in input files."""

__all__ = [
    "copy_file_to_scratch",
    "copy_file_from_scratch",
    "cubeprop",
    "get_memory",
    "libint2_configuration",
    "libint2_print_out",
    "oeprop",
    "set_memory",
]

import os
import re
from typing import Dict, List, Union

from psi4 import core

from .exceptions import ValidationError
from .prop_util import *


[docs] def oeprop(wfn: core.Wavefunction, *args: List[str], **kwargs): """Evaluate one-electron properties. :returns: None :param wfn: set of molecule, basis, orbitals from which to compute properties :param args: Arbitrary-number of properties to be computed from *wfn*. See :ref:`Available One-Electron Properties <table:oe_features>`. :type title: str :param title: label prepended to all psivars computed :examples: >>> # [1] Moments with specific label >>> E, wfn = energy('hf', return_wfn=True) >>> oeprop(wfn, 'DIPOLE', 'QUADRUPOLE', title='H3O+ SCF') """ oe = core.OEProp(wfn) if 'title' in kwargs: oe.set_title(kwargs['title']) for prop in args: oe.add(prop.upper()) # If we're doing MBIS, we want the free-atom volumes # in order to compute volume ratios, # but only if we're calling oeprop as the whole molecule free_atom = kwargs.get('free_atom',False) if "MBIS_VOLUME_RATIOS" in prop.upper() and not free_atom: core.print_out(" Computing free-atom volumes\n") free_atom_volumes(wfn) oe.compute()
[docs] def cubeprop(wfn: core.Wavefunction, **kwargs): """Evaluate properties on a grid and generate cube files. .. versionadded:: 0.5 *wfn* parameter passed explicitly :returns: None :param wfn: set of molecule, basis, orbitals from which to generate cube files :examples: >>> # [1] Cube files for all orbitals >>> E, wfn = energy('b3lyp', return_wfn=True) >>> cubeprop(wfn) >>> # [2] Cube files for density (alpha, beta, total, spin) and four orbitals >>> # (two alpha, two beta) >>> set cubeprop_tasks ['orbitals', 'density'] >>> set cubeprop_orbitals [5, 6, -5, -6] >>> E, wfn = energy('scf', return_wfn=True) >>> cubeprop(wfn) """ # By default compute the orbitals if not core.has_global_option_changed('CUBEPROP_TASKS'): core.set_global_option('CUBEPROP_TASKS', ['ORBITALS']) cp = core.CubeProperties(wfn) cp.compute_properties()
[docs] def set_memory(inputval: Union[str, int, float], execute: bool = True, quiet: bool = False) -> int: """Reset the total memory allocation. Parameters ---------- inputval Memory value. An Integer or float is taken literally as bytes to be set. A string is taken as a unit-containing value (e.g., 30 mb), which is case-insensitive. execute When False, interpret *inputval* without setting in Psi4 core. quiet When True, do not print to the output file. Returns ------- int Number of bytes of memory set. Raises ------ ValidationError When <500MiB or disallowed type or misformatted. Examples -------- >>> # [1] Passing absolute number of bytes >>> psi4.set_memory(600000000) >>> psi4.get_memory() Out[1]: 600000000L >>> # [2] Passing memory value as string with units >>> psi4.set_memory('30 GB') >>> psi4.get_memory() Out[2]: 30000000000L >>> # Good examples >>> psi4.set_memory(800000000) # 800000000 >>> psi4.set_memory(2004088624.9) # 2004088624 >>> psi4.set_memory(1.0e9) # 1000000000 >>> psi4.set_memory('600 mb') # 600000000 >>> psi4.set_memory('600.0 MiB') # 629145600 >>> psi4.set_memory('.6 Gb') # 600000000 >>> psi4.set_memory(' 100000000kB ') # 100000000000 >>> psi4.set_memory('2 eb') # 2000000000000000000 >>> # Bad examples >>> psi4.set_memory({}) # odd type >>> psi4.set_memory('') # no info >>> psi4.set_memory("8 dimms") # unacceptable units >>> psi4.set_memory("1e5 gb") # string w/ exponent >>> psi4.set_memory("5e5") # string w/o units >>> psi4.set_memory(2000) # mem too small >>> psi4.set_memory(-5e5) # negative (and too small) """ # Handle memory given in bytes directly (int or float) if isinstance(inputval, (int, float)): val = inputval units = '' # Handle memory given as a string elif isinstance(inputval, str): memory_string = re.compile(r'^\s*(\d*\.?\d+)\s*([KMGTPBE]i?B)\s*$', re.IGNORECASE) matchobj = re.search(memory_string, inputval) if matchobj: val = float(matchobj.group(1)) units = matchobj.group(2) else: raise ValidationError("""Invalid memory specification: {}. Try 5e9 or '5 gb'.""".format(repr(inputval))) else: raise ValidationError("""Invalid type {} in memory specification: {}. Try 5e9 or '5 gb'.""".format( type(inputval), repr(inputval))) # Units decimal or binary? multiplier = 1000 if "i" in units.lower(): multiplier = 1024 units = units.lower().replace("i", "").upper() # Build conversion factor, convert units unit_list = ["", "KB", "MB", "GB", "TB", "PB", "EB"] mult = 1 for unit in unit_list: if units.upper() == unit: break mult *= multiplier memory_amount = int(val * mult) # Check minimum memory requirement min_mem_allowed = 262144000 if memory_amount < min_mem_allowed: raise ValidationError( """set_memory(): Requested {:.3} MiB ({:.3} MB); minimum 250 MiB (263 MB). Please, sir, I want some more.""" .format(memory_amount / 1024**2, memory_amount / 1000**2)) if execute: core.set_memory_bytes(memory_amount, quiet) return memory_amount
[docs] def get_memory() -> int: """Return the total memory allocation in bytes.""" return core.get_memory()
[docs] def copy_file_to_scratch(filename: str, prefix: str, namespace: str, unit: int, move: bool = False): """Move a file into scratch following the naming convention. Parameters ---------- filename Full path to file. prefix Computation prefix, usually 'psi'. namespace Context namespace, usually molecule name. unit Unit number, e.g. 32. move Whether to copy (default) or move? Examples -------- >>> # Assume PID is 12345 and SCRATCH is /scratch/parrish/ >>> copy_file_to_scratch('temp', 'psi', 'h2o', 32): Out[1]: -cp ./temp /scratch/parrish/psi.12345.h2o.32 >>> copy_file_to_scratch('/tmp/temp', 'psi', 'h2o', 32): Out[2]: -cp /tmp/temp /scratch/parrish/psi.12345.h2o.32 >>> copy_file_to_scratch('/tmp/temp', 'psi', '', 32): Out[3]: -cp /tmp/temp /scratch/parrish/psi.12345.32 >>> copy_file_to_scratch('/tmp/temp', 'psi', '', 32, True): Out[4]: -mv /tmp/temp /scratch/parrish/psi.12345.32 """ pid = str(os.getpid()) scratch = core.IOManager.shared_object().get_file_path(int(unit)) cp = '/bin/cp' if move: cp = '/bin/mv' unit = str(unit) target = '' target += prefix target += '.' target += pid if len(namespace): target += '.' target += namespace target += '.' target += unit command = ('%s %s %s/%s' % (cp, filename, scratch, target)) os.system(command)
[docs] def copy_file_from_scratch(filename: str, prefix: str, namespace: str, unit: int, move: bool = False): """Move a file out of scratch following the naming convention. Parameters ---------- filename Full path to target file. prefix Computation prefix, usually 'psi'. namespace Context namespace, usually molecule name. unit Unit number, e.g. 32 move Whether to copy (default) or move? Examples -------- >>> # Assume PID is 12345 and SCRATCH is /scratch/parrish/ >>> copy_file_to_scratch('temp', 'psi', 'h2o', 32): Out[1]: -cp /scratch/parrish/psi.12345.h2o.32 .temp >>> copy_file_to_scratch('/tmp/temp', 'psi', 'h2o', 32): Out[2]: -cp /scratch/parrish/psi.12345.h2o.32 /tmp/temp >>> copy_file_to_scratch('/tmp/temp', 'psi', '', 32): Out[3]: -cp /scratch/parrish/psi.12345.32 /tmp/temp >>> copy_file_to_scratch('/tmp/temp', 'psi', '', 32, True): Out[4]: -mv /scratch/parrish/psi.12345.32 /tmp/temp """ pid = str(os.getpid()) scratch = core.IOManager.shared_object().get_file_path(int(unit)) cp = '/bin/cp' if move: cp = '/bin/mv' unit = str(unit) target = '' target += prefix target += '.' target += pid if len(namespace): target += '.' target += namespace target += '.' target += unit command = ('%s %s/%s %s' % (cp, scratch, target, filename)) os.system(command)
[docs] def libint2_configuration() -> Dict[str, List[int]]: """Returns information on integral classes, derivatives, and AM from currently linked Libint2. Returns ------- Dictionary of integrals classes with values an array of max angular momentum per derivative level. Usual configuration returns: `{'eri': [5, 4, 3], 'eri2': [6, 5, 4], 'eri3': [6, 5, 4], 'onebody': [6, 5, 4]}` """ if "eri_c4_d0_l2" in core._libint2_configuration(): return _l2_config_style_eri_c4() elif "eri_dddd_d0" in core._libint2_configuration(): return _l2_config_style_eri_llll()
def _l2_config_style_eri_llll(): skel = {"onebody_": [], "eri_c4_": [], "eri_c3_": [], "eri_c2_": []} skel_re = {"onebody_": r"onebody_\w_d\d", "eri_c4_": r"eri_\w..._d\d", "eri_c3_": r"eri_\w.._d\d", "eri_c2_": r"eri_\w._d\d"} amstr = "SPDFGHIKLMNOQRTUVWXYZ" libint2_configuration = core._libint2_configuration() for k, v in skel_re.items(): t = re.findall(v, libint2_configuration) skel[k] = t for cat in list(skel.keys()): der_max_store = [] for der in ["d0", "d1", "d2"]: lmax = -1 for itm2 in skel[cat]: if itm2.endswith(der): lmax = max(amstr.index(itm2[-4].upper()), lmax) der_max_store.append(None if lmax == -1 else lmax) skel[cat] = der_max_store # rename keys from components skel["onebody"] = skel.pop("onebody_") skel["eri"] = skel.pop("eri_c4_") skel["eri3"] = skel.pop("eri_c3_") skel["eri2"] = skel.pop("eri_c2_") return skel def _l2_config_style_eri_c4(): skel = {"onebody_": [], "eri_c4_": [], "eri_c3_": [], "eri_c2_": []} for itm in core._libint2_configuration().split(";"): for cat in list(skel.keys()): if itm.startswith(cat): skel[cat].append(itm[len(cat):]) for cat in list(skel.keys()): der_max_store = [] for der in ["d0_l", "d1_l", "d2_l"]: lmax = -1 for itm2 in skel[cat]: if itm2.startswith(der): lmax = max(int(itm2[len(der):]), lmax) der_max_store.append(None if lmax == -1 else lmax) skel[cat] = der_max_store # rename keys from components skel["onebody"] = skel.pop("onebody_") skel["eri"] = skel.pop("eri_c4_") skel["eri3"] = skel.pop("eri_c3_") skel["eri2"] = skel.pop("eri_c2_") return skel
[docs] def libint2_print_out() -> None: ams = libint2_configuration() core.print_out(" => Libint2 <=\n\n"); # when L2 is pure cmake core.print_out(core.libint2_citation()); core.print_out(f" Primary basis highest AM E, G, H: {', '.join(('-' if d is None else str(d)) for d in ams['eri'])}\n") core.print_out(f" Auxiliary basis highest AM E, G, H: {', '.join(('-' if d is None else str(d)) for d in ams['eri3'])}\n") core.print_out(f" Onebody basis highest AM E, G, H: {', '.join(('-' if d is None else str(d)) for d in ams['onebody'])}\n") # excluding sph_emultipole core.print_out(f" Solid Harmonics ordering: {core.libint2_solid_harmonics_ordering()}\n")