Source code for tcutility.data.functionals
'''
Module used for obtaining information about exchange-correlation functionals.
For example, it can be useful to obtain
'''
import os
from tcutility import results, cache
import re
j = os.path.join
[docs]
@cache.cache
def get(functional_name: str) -> results.Result:
'''
Return information about a given functional.
Args:
functional_name: the name of the functional. It should exist in the :func:`get_available_functionals` keys.
Return:
A :class:`Result <tcutility.results.result.Result>` object containing information about the functional if it exists. Else it will return ``None``.
.. seealso::
:func:`get_available_functionals` for an overview of the information returned.
'''
info = functionals.get(functional_name, None)
if info is None:
info = functionals.get(functional_name_from_path_safe_name(functional_name), None)
if info is None:
raise KeyError(f'Could not find info for functional {functional_name}.')
return info
[docs]
def functional_name_from_path_safe_name(path_safe_name: str) -> results.Result:
'''
Return information about a given functional given its path-safe name.
This can be useful when you want to know the functional from a path name.
Return:
A :class:`Result <tcutility.results.result.Result>` object containing information about the functional if it exists. Else it will return ``None``.
.. seealso::
:func:`get_available_functionals` for an overview of the information returned.
'''
for functional, functional_info in functionals.items():
if path_safe_name.lower() == functional_info.path_safe_name.lower():
return functional
elif path_safe_name.replace('-', '').lower() == functional_info.path_safe_name.replace('-', '').lower():
return functional
[docs]
def get_available_functionals():
'''
Function that returns a dictionary of all available XC-functionals.
Returns:
: A :class:`Result <tcutility.results.result.Result>` object containing information about all available XC-functionals.
The functional names are stored as the keys and the functional information is stored as the values.
The values contain the following information:
- ``name`` **(str)** - the name of the functional.
- ``path_safe_name`` **(str)** - the name of the functional made suitable for file paths.
This name is the same as the normal name, but without parentheses. Asterisks are replaced with lower-case ``s``.
- ``name_no_disp`` **(str)** - the name of functional without the dispersion correction.
- ``category`` **(str)** - the category the functional belongs to.
- ``dispersion`` **(str)** - the dispersion correction part of the functional name.
- ``dispersion_name`` **(str)** - the name of the dispersion correction as it would be written in ADF.
- ``includes_disp`` **(bool)** - whether the functional already includes a dispersion correction.
- ``use_libxc`` **(bool)** - whether the functional is from the LibXC library.
- ``available_in_adf`` **(bool)** - whether the functional is available in ADF.
- ``available_in_band`` **(bool)** - whether the functional is available in BAND.
- ``available_in_orca`` **(bool)** - whether the functional is available in ORCA.
- ``adf_settings`` **(:class:`Result <tcutility.results.result.Result>`)** - the settings that are used to select the functional in the ADF input.
- ``name_latex`` **(str)** - the name of the functional formatted to be used with LaTeX renderers.
- ``name_html`` **(str)** - the name of the functional formatted to be used with HTML renderers.
- ``dois`` **(List[str])** - a list of relevant dois for this functional.
'''
def set_dispersion(func):
disp_map = {
'-D4': 'GRIMME4',
'-D3(BJ)': 'GRIMME3 BJDAMP',
'-D3BJ': 'GRIMME3 BJDAMP',
'-D3': 'GRIMME3',
'-dDsC': 'dDsC',
'-dUFF': 'UFF',
'-MBD': 'MBD',
'-MBD@rsSC': 'MBD',
'-D': 'DEFAULT'
}
# set some default values for useful parameters
func.name_no_disp = func.name
func.dispersion = None
func.dispersion_name = None
# go through every dispersion correction and check if we are using it
for disp_suffix, disp_name in disp_map.items():
if func.name.endswith(disp_suffix):
# dispersion will be the suffix without the -
func.dispersion = disp_suffix[1:]
func.dispersion_name = disp_name
# check if the functional already includes the dispersion correction
if func.includes_disp:
break
# else we set the name of the functional without dispersion for later
func.name_no_disp = func.name[:-len(disp_suffix)]
# get the dispersion settings for ADF. Try to get custom values if they were provided.
func.adf_settings.XC.Dispersion = func.disp_params or disp_name
break
def set_functional(func):
# set the functional settings for ADF
# first go through some special functionals that require special settings
if func.name_no_disp == 'BMK':
func.adf_settings.XC.LibXC = 'HYB_MGGA_X_BMK GGA_C_BMK'
return
if func.name in ['LCY-BLYP', 'LCY-BP86', 'LCY-PBE']:
func.adf_settings.XC.GGA = func.name.split('-')[1]
func.adf_settings.XC.RANGESEP = ''
func.adf_settings.XC.xcfun = ''
return
if func.name in ['CAMY-B3LYP']:
func.adf_settings.XC.Hybrid = 'CAMY-B3LYP'
func.adf_settings.XC.RANGESEP = ''
func.adf_settings.XC.xcfun = ''
return
if func.name == 'GGA:SSB-D':
func.adf_settings.XC.GGA = 'SSB-D'
return
if func.name == 'MetaGGA:SSB-D':
func.adf_settings.XC.MetaGGA = 'SSB-D'
return
if func.name_no_disp == 'HartreeFock':
func.adf_settings.XC.HartreeFock = ''
return
if func.name == 'MP2':
func.adf_settings.XC.MP2 = ''
return
if func.name in ['SOS-MP2', 'SCS-MP2']:
func.adf_settings.XC.MP2 = ''
func.adf_settings.XC.EmpiricalScaling = func.name[:-4]
return
# the normal functionals are defined based on their category, or selected from libxc
if func.use_libxc:
func.adf_settings.XC.LibXC = func.name_no_disp
else:
func.adf_settings.XC[func.category] = func.name_no_disp
# gather all data about available functionals
functionals = results.Result() # store all info in this dict
with open(j(os.path.split(__file__)[0], 'available_functionals.txt')) as file:
lines = file.readlines()
# read the references first
# references are given as
# [refname] doi
# and in-line
# - xcname {options} [refname1][refname2]...
dois = {}
for line in lines:
if not line.startswith('['):
continue
line = line.split('#')[0].strip()
ref, doi = line.strip().split()
ref = ref[1:-1]
dois[ref] = doi
for line in lines:
# there can be empty lines
if not line.strip():
continue
# and comment lines
if line.startswith('#'):
continue
# we don't read the references here
if line.startswith('['):
continue
# functional names are given starting with -
# category names without -
if not line.startswith('- '):
curr_category = line.strip()
continue
line = line.split('#')[0].strip()
# store data about the func in a dict
func = results.Result()
func.category = curr_category
# separate the functional name from the line
functional_name = line[2:].split('!')[0].split(',')[0].split('[')[0].strip()
func.name = functional_name
func.name_latex = functional_name
func.name_html = functional_name
func.path_safe_name = functional_name.replace(')', '').replace('(', '').replace('*', 's').replace(' ', '-')
if functional_name.startswith('WB'):
func.name_latex = func.name_latex.replace('WB', r'$\omega$B')
func.name_html = func.name_html.replace('WB', 'ωB')
if 'r2SCAN' in functional_name:
func.name_latex = func.name_latex.replace('r2SCAN', r'r$^2$SCAN')
func.name_html = func.name_html.replace('r2SCAN', 'r<sup>2</sup>SCAN')
if 'and' in functional_name:
func.name_latex = func.name_latex.replace('and', '&')
func.name_html = func.name_html.replace('and', '&')
if '*' in functional_name:
func.name_latex = func.name_latex.replace('*', r'$^*$')
func.name_html = func.name_html.replace('*', '<sup>*</sup>')
if 'B2PIPLYP' in functional_name:
func.name_latex = func.name_latex.replace('B2PIPLYP', r'B2$\pi$PLYP')
func.name_html = func.name_html.replace('B2PIPLYP', 'B2πPLYP')
# check if custom params were given for dispersion
if 'GRIMME' in line:
# func.disp_params = line.split('!')[0].split(',')[1].strip().strip("'")
func.disp_params = re.findall(r"'(.*)'", line)
func.use_libxc = '!libxc' in line
func.includes_disp = '!includesdisp' in line
func.available_in_adf = '!noadf' not in line
func.available_in_band = '!band' in line
func.available_in_orca = '!orca' in line
# get references
refs = re.findall(r'\[([a-zA-Z0-9]+)\]', line)
func.dois = [dois[ref] for ref in refs]
set_dispersion(func)
set_functional(func)
functionals[functional_name] = func
return functionals
functionals = get_available_functionals()
categories = []
for functional in functionals:
if get(functional).category not in categories:
categories.append(get(functional).category)