Source code for ase2sprkkr.configuration

""" This module contains configuration, that could be changed, preferrably
by .config/ase2sprkkr/__init__.py file"""

import os
import re
from typing import Union
from .common.decorators import cache
from .common.grammar_types import CustomMixed, AlwaysQString, Array, Bool, Keyword, Integer
from .common.container_definitions import SectionDefinition, ConfigurationRootDefinition
from .common.configuration_containers import RootConfigurationContainer
from .common.value_definitions import ValueDefinition
from .common.options import Option
import warnings
import shutil
import platformdirs


[docs] class Section(SectionDefinition): info_in_data_description = True dir_common_attributes = False
[docs] def _get_suffix(*_): return os.environ.get('SPRKKR_EXECUTABLE_SUFFIX','')
[docs] def user_preferences_file(): """ Return filename with user preferences """ return os.path.join(platformdirs.user_config_dir('ase2sprkkr', 'ase2sprkkr'), '__init__.py')
[docs] def load_user_preferences(): """ Load user defined preferences from ``$HOME/.config/ase2sprkkr/__init__.py`` """ file = user_preferences_file() try: if os.path.isfile(file): import types import importlib.machinery loader = importlib.machinery.SourceFileLoader('ase2sprkkr.personal', file) mod = types.ModuleType(loader.name) loader.exec_module(mod) except Exception as e: import warnings warnings.warn(f'Can not import {file} file with the user preferences: \n{e}')
[docs] @cache def find_default_mpi_runner(): for r in [ 'mpirun', 'mpirun.opmpirun', 'mpirun.mpich' ]: if shutil.which(r): return [ r ] return False
[docs] @cache def get_default_mpi_runner(): out = find_default_mpi_runner() if out: return out if config.mpi_warning(): warnings.warn("No MPI runner found. Disabling MPI!!!")
[docs] def mpi_runner(mpi): """ Return a shell command to execute a mpi task. Parameters ---------- mpi_runner: Union[bool,str,list,int] - If True is given, return the default mpi-runner - If False is given, no mpi-runner is returned. - If 'auto' is given, it is the same as True, however * no warning is given if no mpi is found * MPI is not used, if only one CPU is available - If a string is given, it is interpreted as a list of one item - If a list (of strings) is given, the user specified its own runner, use it as is as the parameters for subprocess.run. - If an integer is given, it is interpreted as the number of processes: the default mpi-runner is used, and the parameters to specify the number of processes. Return ------ mpi_runner: list List of strings with the executable and its parameters, e.g. :: ['mpirun', '-np', '4'] """ if mpi is None: mpi=config.running.mpi() if mpi is False: return None if mpi is True: mpi=find_default_mpi_runner() if isinstance(mpi, list): return mpi if isinstance(mpi, str): if mpi == 'auto': if hasattr(os, 'sched_getaffinity') and len(os.sched_getaffinity(0))==1: return None return find_default_mpi_runner() return [ mpi ] if isinstance(mpi, int): return find_default_mpi_runner() + ['-np', str(mpi)] return mpi
[docs] class Configuration(RootConfigurationContainer):
[docs] def store_value_permanent(self, name:str, value, doc=None, doc_regex:bool or str=False): """ Set/remove permanently the value with given name in the config file. No name/value checking. If the value is present, it is changed, otherwise, new line with setting the value is added to the file. If the value is to be removed, all the lines value = .... will be removed from the config file. Parameters ---------- name Name of the value value Value to be set doc If it is not None: - if the value is to be removed and it is preceeded by the line containing the given docstring, prefixed by ' # ', the preceding line with docstring is removed as well. - if the value is to be added, the docstring is added before the value as well. doc_regex If True, the doc parameter can be given as regular expression. """ global user_preference_file file = user_preferences_file() if not os.path.isfile(file): raise ValueError("Please, generate the user prefernce file using 'ase2sprkkr config -d' first.") with open(file, 'r+') as f: content = f.read() pattern=f"(#?\\s*)*config.{name}\\s+=" if value is None: line = '' cnt = 0 last = '' if doc: if not doc_regex: pre = re.escape(doc) else: pre = doc pre = f"(?:(?:^|\n)# {pre} *)?" else: pre = '' else: line = f"config.{name} = {value}\n" cnt = 1 pre = '' last= f"(?!(.*\n)*{pattern})" content, replaced = re.subn(f"{pre}(^|\n){pattern}[^\n]*(\n|$){last}", r'\1' + line, content, cnt) if replaced: f.seek(0) f.write(content) elif value is None: return else: i=len(content) - 1 while i>=0: if content[i] not in ('\r','\n', ' ', '\t'): break i-=1 f.seek(i + 1) if i>0: f.write("\n\n") if doc: f.write(f"# {doc}\n") f.write(line) f.truncate()
[docs] class ConfigurationOption(Option):
[docs] def set_permanent(self, value, doc=None, doc_regex: Union[bool, str]=False): """ Set the value and store it in the config file See :meth:`Configuration.store_value_permament` for the meaning of the parameters """ self.set(value) self.store_permanent()
[docs] def store_permanent(self, doc=None, doc_regex: Union[bool, str]=False): """ Store the actual value in the config file See :meth:`Configuration.store_value_permament` for the meaning of the parameters """ val = self() if val is not None: val = self._definition.type.string(val) config.store_value_permanent(self.get_path(), val)
[docs] class ConfigValueDefinition(ValueDefinition): result_class = ConfigurationOption
[docs] class ConfigFileDefinition(ConfigurationRootDefinition): dir_common_attributes = False result_class = Configuration
V = ConfigValueDefinition """ The definition of ASE2SPRKKR configuration """ definition = ConfigFileDefinition('config', [ Section('running', [ V('empty_spheres', Keyword({ True : 'Always do empty spheres finding.', False: 'Newer do empty spheres finding.', 'auto': 'Do empty spheres finding for unconverged potential not containing any vaccuum atom.' }, transform=None, quote='"'), default_value='auto', info="Run empty spheres finding before calculation? Default value ``auto`` means only for SCF calculations not containing any vacuum atom."), V('print_output', Keyword({ True: 'Print all output of SPRKKR executables to screen.', False: 'Do not print any output of SPRKKR executables.', 'info': 'Print only brief information about iterations of SCF cycle.', }, transform=None, quote='"'), default_value='info', info="Print output of SPRKKR calculation. Default value ``info`` prints only short info each iteration."), V('mpi', CustomMixed(Bool, Array(AlwaysQString.I), Integer.I), is_optional=True, default_value=None, info='Use mpi for calculation? List of strings means yes, use the given strings as mpi runner and its params (e.g. [ "mpirun", "-n", "4" ]). Default None means try to autodetect. Integer number means use the standard runner with a given number of processes.'), V('mpi_warning', True, info='Warn, if no MPI is found.') ], info='Default values for SPRKKR calculator parameters.'), Section('executables', [ V('suffix', AlwaysQString.I, default_value=_get_suffix, info="This suffix is appended (if not stated otherwise) to the SPRKKR " "executable names."), V('dir', AlwaysQString.I, is_optional=True, info='Directory, from which the executables will be runned. None mean use the default environment variable PATH mechanism') ], info="Configuration, that affects how the execubables are runned"), Section('nomad', [ V('token', AlwaysQString.I, info = "Token for NOMAD upload", is_optional=True) ]) ]) config = definition.create_object()