Source code for mybigdft.iofiles.inputparams
r"""
The :class:`InputParams` class is meant to represent the input
parameters of a BigDFT calculation in a yaml format.
It also comes with two functions:
* :func:`check` to check that a dictionary is only made of valid BigDFT
input parameters.
* :func:`clean` to clean a dictionary of input parameters so that only
non-default values are kept in memory.
"""
from __future__ import print_function
import warnings
from copy import deepcopy
from collections import MutableMapping
import yaml
try:
from yaml import CLoader as Loader, CDumper as Dumper
except ImportError: # pragma: no cover
from yaml import Loader, Dumper
from mybigdft.globals import DEFAULT_PARAMETERS, INPUT_PARAMETERS_DEFINITIONS
from .posinp import Posinp
__all__ = ["check", "clean", "InputParams"]
[docs]class InputParams(MutableMapping):
r"""
Class allowing to initialize, read, write and interact with the
input parameters of a BigDFT calculation.
"""
def __init__(self, params=None):
r"""
Input parameters are initialized from a yaml dictionary.
Parameters
----------
data : dict
yaml dictionary of the input parameters.
>>> InputParams({'dft': {'hgrids': [0.35]*3}})
{'dft': {'hgrids': [0.35, 0.35, 0.35]}}
Default values are cleaned from the input parameters:
>>> InputParams({'dft': {'hgrids': [0.45]*3}})
{}
The input parameters can be empty:
>>> InputParams()
{}
Initializing with unknown parameters raises a ``KeyError``:
>>> InputParams({'dfpt': {'hgrids': [0.35]*3}})
Traceback (most recent call last):
...
KeyError: "Unknown key 'dfpt'"
>>> InputParams({'dft': {'hgrid': [0.35]*3}})
Traceback (most recent call last):
...
KeyError: "Unknown key 'hgrid' in 'dft'"
"""
if params is None:
params = {}
if "posinp" in params:
self.posinp = Posinp.from_dict(params.pop("posinp"))
else:
self.posinp = None
self.params = clean(params)
[docs] @classmethod
def from_file(cls, filename):
r"""
Initialize an InputParams instance from a BigDFT input file.
Parameters
----------
filename : str
Name of the file to read.
Returns
-------
InputParams
InputParams instance initialized from the file.
"""
with open(filename, "r") as stream:
return cls.from_string(stream)
[docs] @classmethod
def from_string(cls, string):
r"""
Initialize an InputParams instance from a string.
Parameters
----------
string : str
Input parameters dictionary as a string.
Returns
-------
InputParams
InputParams instance initialized from the string.
>>> InputParams.from_string("{'dft': {'rmult': [6, 8]}}")
{'dft': {'rmult': [6, 8]}}
"""
params = yaml.load(string, Loader=Loader)
return cls(params=params)
@property
def params(self):
"""
Returns
-------
dict
Input parameters.
"""
return self._params
@params.setter
def params(self, params):
self._params = params
@property
def posinp(self):
r"""
Returns
-------
Posinp or None
Initial positions contained in the input parameters
"""
return self._posinp
@posinp.setter
def posinp(self, new):
if new is not None and not isinstance(new, Posinp):
raise ValueError("Update the posinp attribute with a Posinp instance")
else:
self._posinp = new
def __getitem__(self, key):
r"""
"""
return self.params[key]
[docs] def __setitem__(self, key, value):
r"""
Set the input parameters after making sure that they are valid.
Parameters
----------
key : str
Input parameters key.
value : dict
Value of the given key of input parameters.
Warns
-----
UserWarning
If the proposed update does not modify the input parameters.
"""
if key == "posinp":
# Set the new posinp
self.posinp = Posinp.from_dict(value)
else:
# Check that the key and its value are valid.
params = {key: value}
cleaned_params = clean(params)
# Set the input parameters with cleaned parameters
if cleaned_params == {}:
try:
# Update with default params
del self.params[key]
except KeyError:
warnings.warn("Nothing to update.", UserWarning)
else:
# Update with cleaned params
self.params[key] = cleaned_params[key]
def __delitem__(self, key):
del self.params[key]
def __iter__(self):
return iter(self.params)
def __len__(self):
return len(self.params)
def __repr__(self):
return repr(self.params)
[docs] def write(self, filename):
"""
Write the input parameters on disk.
Parameters
----------
filename : str
Name of the input file.
"""
with open(filename, "w") as stream:
self.params = clean(self.params) # Make sure it is valid
yaml.dump(self.params, stream=stream, Dumper=Dumper)
[docs]def clean(params, keyword=None):
"""
Parameters
----------
params : dict
Trial BigDFT input parameters.
keyword: NoneType or str
Main key of input parameters key to be checked (used to check
CheSS input parameters).
Returns
-------
dict
Input parameters whose values are not their default, after
checking that all the keys in `params` correspond to actual
BigDFT parameters.
"""
# # Make sure to convert hgrids to a list beforehand
# if "dft" in params and "hgrids" in params["dft"]:
# hgrids = deepcopy(params["dft"]["hgrids"])
# if not isinstance(hgrids, list):
# params["dft"]["hgrids"] = [hgrids]*3
# Use a copy of params to update the input parameters while looping
# on params. This copy will be returned.
real_params = deepcopy(params)
# Use the correct reference for the input variables
default = DEFAULT_PARAMETERS
if keyword is not None:
default = default[keyword]
# Set real_params['output']['orbitals'] to 'None' when it is False
if (
"output" in real_params
and real_params["output"] is not None
and "orbitals" in real_params["output"]
and not real_params["output"]["orbitals"]
):
real_params["output"]["orbitals"] = "None"
# Check the validity of the given input parameters
check(real_params, keyword=keyword)
# Return the cleaned input parameters
for key, value in params.items():
# The key might be empty (e.g.: logfile with many documents)
if value is None:
del real_params[key]
continue
# Delete the child keys whose values are default
for child_key, child_value in value.items():
default_value = default[key][child_key]
if child_value == default_value:
del real_params[key][child_key]
# Delete the key if it is empty
if real_params[key] == {}:
del real_params[key]
# Clean the chess input variables as well
if "chess" in real_params:
chess_params = clean(real_params["chess"], keyword="chess")
if chess_params != {}:
real_params["chess"] = chess_params
else:
del real_params["chess"]
# Remove the cumbersome geopt key if ncount_cluster_x is the only
# key (it happens when the input parameters are read from a Logfile)
dummy_value = {"ncount_cluster_x": 1}
if "geopt" in real_params and real_params["geopt"] == dummy_value:
del real_params["geopt"]
return real_params
[docs]def check(params, keyword=None):
"""
Check that the keys of `params` correspond to BigDFT parameters.
Parameters
----------
params : dict
Trial input parameters.
Raises
------
KeyError
If a key or a sub-key is not a BigDFT parameter.
ValueError
If a value is invalid (not in the correct range, not in the
possible values, the value of the master key does not allow for
the key to be defined)
"""
parameters = INPUT_PARAMETERS_DEFINITIONS
if keyword is not None:
parameters = parameters[keyword]
for key, value in params.items():
# Check the key
if key not in parameters:
raise KeyError("Unknown key '{}'".format(key))
if value is not None:
for subkey, subvalue in value.items():
# Check the subkey
key_definition = parameters[key]
if subkey not in key_definition:
raise KeyError("Unknown key '{}' in '{}'".format(subkey, key))
# Check the subvalue:
check_value(subkey, subvalue, key, value, key_definition)
def check_value(subkey, subvalue, key, value, key_definition):
r"""
Check the value of an input parameter is valid
Parameters
----------
subkey : str
Name of the BigDFT input parameter under consideration.
subvalue
Value of the BigDFT input parameter under consideration.
key : str
Base key of the BigDFT input parameter under consideration.
value : dict
Value of the base key.
key_definition : dict
Definition of the base key.
Raises
------
ValueError
If a value is invalid (not in the correct range, not in the
possible values, the value of the master key does not allow for
the key to be defined)
"""
subkey_definition = key_definition[subkey]
# If default value, no need to worry anymore
default = subkey_definition.get("default")
if subvalue == default:
return
# If the subkey is conditioned by the value of another subkey, check
# that this other subkey has a valid value
condition = subkey_definition.get("CONDITION")
if condition is not None:
master_key = condition["MASTER_KEY"]
possible_values = condition["WHEN"]
if value[master_key] not in possible_values:
raise ValueError(
"Condition '{} in {}' not met for '{}' in '{}' (got {})".format(
master_key, possible_values, subkey, key, subvalue
)
)
# It must be in the exclusive values
possible_values = subkey_definition.get("EXCLUSIVE")
if possible_values and subvalue not in possible_values:
raise ValueError(
"'{}' in '{}' not in the possible values (got {}, not in {})".format(
subkey, subkey, subvalue, possible_values
)
)
# It must be in the correct range
valid_range = subkey_definition.get("RANGE")
if valid_range:
if isinstance(subvalue, list):
value_in_range = all([in_range(val, valid_range) for val in subvalue])
else:
value_in_range = in_range(subvalue, valid_range)
if not value_in_range:
raise ValueError(
"'{}' in '{}' not in valid range (got {}, not in {})".format(
subkey, key, subvalue, valid_range
)
)
def in_range(value, valid_range):
r"""
Check if the value is in the expected range.
Returns
-------
bool
`True` if the value is in the valid range, else `False`
"""
return float(valid_range[0]) <= float(value) <= float(valid_range[1])