From 9399078334d9402f6e04f2ee23b7be2f0a5aef62 Mon Sep 17 00:00:00 2001 From: Matteo Giantomassi Date: Fri, 12 Jan 2018 13:35:26 +0100 Subject: [PATCH] Remove htc module --- .coveragerc | 2 - abipy/abilab.py | 8 +- abipy/abio/decorators.py | 2 +- abipy/abio/inputs.py | 4 +- abipy/abio/tests/test_abivars.py | 2 +- abipy/abio/tests/test_variable.py | 17 + abipy/{htc => abio}/variable.py | 183 ++- abipy/core/structure.py | 2 +- abipy/core/testing.py | 61 - abipy/dev_scripts/fftprof.py | 4 +- abipy/electrons/gsr.py | 2 +- abipy/examples/flows/run_ldaus.py | 4 +- abipy/flowtk/abiobjects.py | 157 +++ abipy/flowtk/tests/test_abiobjects.py | 55 + abipy/gui/fftprof.py | 2 +- abipy/htc/__init__.py | 22 - abipy/htc/abinitfiles.py | 195 --- abipy/htc/abinitinput.py | 313 ----- abipy/htc/filesfile.py | 145 -- abipy/htc/input.py | 1348 ------------------- abipy/htc/inputfile.py | 394 ------ abipy/htc/jobfile.py | 806 ----------- abipy/htc/launcher.py | 914 ------------- abipy/htc/tests/__init__.py | 0 abipy/htc/tests/test_filesfile.py | 49 - abipy/htc/tests/test_input.py | 211 --- abipy/htc/tests/test_inputfile.py | 83 -- abipy/htc/tests/test_jobfile.py | 165 --- abipy/htc/utils.py | 199 --- abipy/integration_tests/TODO.md | 2 + abipy/{htc/fftbench.py => tools/fftprof.py} | 60 +- abipy/tools/tests/test_duck.py | 1 - abipy/tools/tests/test_fftprof.py | 34 + docs/api/abio_api.rst | 8 + docs/api/tools_api.rst | 8 + 35 files changed, 405 insertions(+), 5057 deletions(-) create mode 100644 abipy/abio/tests/test_variable.py rename abipy/{htc => abio}/variable.py (87%) create mode 100644 abipy/flowtk/tests/test_abiobjects.py delete mode 100644 abipy/htc/__init__.py delete mode 100644 abipy/htc/abinitfiles.py delete mode 100644 abipy/htc/abinitinput.py delete mode 100644 abipy/htc/filesfile.py delete mode 100644 abipy/htc/input.py delete mode 100644 abipy/htc/inputfile.py delete mode 100644 abipy/htc/jobfile.py delete mode 100644 abipy/htc/launcher.py delete mode 100644 abipy/htc/tests/__init__.py delete mode 100644 abipy/htc/tests/test_filesfile.py delete mode 100644 abipy/htc/tests/test_input.py delete mode 100644 abipy/htc/tests/test_inputfile.py delete mode 100644 abipy/htc/tests/test_jobfile.py delete mode 100644 abipy/htc/utils.py rename abipy/{htc/fftbench.py => tools/fftprof.py} (88%) create mode 100644 abipy/tools/tests/test_fftprof.py diff --git a/.coveragerc b/.coveragerc index fbe39c3a..ac93f151 100644 --- a/.coveragerc +++ b/.coveragerc @@ -28,11 +28,9 @@ omit = */site-packages/* abipy/data/* abipy/examples/* - abipy/htc/* abipy/gui/* abipy/gw/* abipy/extensions/* abipy/gw/* abipy/scripts/* - abipy/integration_tests/* ./docs/ diff --git a/abipy/abilab.py b/abipy/abilab.py index 760c3184..c420dd63 100644 --- a/abipy/abilab.py +++ b/abipy/abilab.py @@ -34,7 +34,7 @@ from abipy.core.structure import (Lattice, Structure, StructureModifier, datafra mp_match_structure, mp_search, cod_search) from abipy.core.mixins import CubeFile from abipy.core.kpoints import set_atol_kdiff -from abipy.htc.input import AbiInput, LdauParams, LexxParams, input_gen +#from abipy.htc.input import LdauParams, LexxParams from abipy.abio.robots import Robot from abipy.abio.inputs import AbinitInput, MultiDataset, AnaddbInput, OpticInput from abipy.abio.abivars import AbinitInputFile @@ -165,8 +165,8 @@ def dir2abifiles(top, recurse=True): """ Analyze the filesystem starting from directory `top` and return an ordered dictionary mapping the directory name to the list - of files supported by `abiopen` contained within that directory. - If not `recurse`, children directories are not analyzed. + of files supported by ``abiopen`` contained within that directory. + If not ``recurse``, children directories are not analyzed. """ dl = collections.defaultdict(list) @@ -187,7 +187,7 @@ def dir2abifiles(top, recurse=True): def isabifile(filepath): """ - Return True if `filepath` can be opened with `abiopen`. + Return True if `filepath` can be opened with ``abiopen``. """ try: abifile_subclass_from_filename(filepath) diff --git a/abipy/abio/decorators.py b/abipy/abio/decorators.py index 26934f13..ab2e8bab 100644 --- a/abipy/abio/decorators.py +++ b/abipy/abio/decorators.py @@ -13,7 +13,7 @@ try: except ImportError: from pymatgen.serializers.json_coders import pmg_serialize -from abipy.htc.input import LdauParams, LexxParams +from abipy.flowtk.abiobjects import LdauParams, LexxParams from .inputs import AbinitInput, MultiDataset import logging diff --git a/abipy/abio/inputs.py b/abipy/abio/inputs.py index 1cb630a7..22dab065 100644 --- a/abipy/abio/inputs.py +++ b/abipy/abio/inputs.py @@ -26,7 +26,7 @@ except ImportError: from abipy.core.structure import Structure from abipy.core.mixins import Has_Structure from abipy.core.kpoints import has_timrev_from_kptopt -from abipy.htc.variable import InputVariable +from abipy.abio.variable import InputVariable from abipy.abio.abivars import is_abivar, is_anaddb_var from abipy.abio.abivars_db import get_abinit_variables from abipy.abio.input_tags import * @@ -1755,7 +1755,7 @@ class AbinitInput(six.with_metaclass(abc.ABCMeta, AbstractInput, MSONable, Has_S Returns: List of dictionaries with the Abinit variables defining the irreducible perturbation - + Example: [{'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]}, diff --git a/abipy/abio/tests/test_abivars.py b/abipy/abio/tests/test_abivars.py index e3a9e18e..433a8fc5 100644 --- a/abipy/abio/tests/test_abivars.py +++ b/abipy/abio/tests/test_abivars.py @@ -1,4 +1,4 @@ -"""Tests for structure module""" +"""Tests for abivars module""" from __future__ import print_function, division, unicode_literals, absolute_import import numpy as np diff --git a/abipy/abio/tests/test_variable.py b/abipy/abio/tests/test_variable.py new file mode 100644 index 00000000..f614f67b --- /dev/null +++ b/abipy/abio/tests/test_variable.py @@ -0,0 +1,17 @@ +"""Tests for variable module.""" +from __future__ import print_function, division, unicode_literals, absolute_import + +import abipy.data as abidata + +from abipy.core.testing import AbipyTest +from abipy.abio.variable import InputVariable + + +class TestInputVariable(AbipyTest): + + def test_inputvariable(self): + """Testing InputVariable.""" + v = InputVariable(name="ecut", value=5) + assert v.name == "ecut" + assert not v.units + assert str(v) == " ecut 5" diff --git a/abipy/htc/variable.py b/abipy/abio/variable.py similarity index 87% rename from abipy/htc/variable.py rename to abipy/abio/variable.py index d0a18612..97185ca9 100644 --- a/abipy/htc/variable.py +++ b/abipy/abio/variable.py @@ -1,14 +1,14 @@ -from __future__ import print_function, division #, unicode_literals +from __future__ import print_function, division, absolute_import #, unicode_literals import string import warnings -from .utils import flatten, listify, is_number, is_iter - +import collections import numpy as np -__all__ = ['InputVariable', 'SpecialInputVariable'] - +__all__ = [ + 'InputVariable', +] _SPECIAL_DATASET_INDICES = (':', '+', '?') @@ -60,8 +60,9 @@ def convert_number(value): class InputVariable(object): - """An Abinit input variable.""" - + """ + An Abinit input variable. + """ def __init__(self, name, value, units='', valperline=3): self._name = name @@ -89,9 +90,9 @@ class InputVariable(object): def name(self): return self._name - @name.setter - def name(self, name): - self._name = name + #@name.setter + #def name(self, name): + # self._name = name @property def basename(self): @@ -111,50 +112,49 @@ class InputVariable(object): def __str__(self): """Declaration of the variable in the input file.""" - value = self.value if value is None or not str(value): return '' - + var = self.name line = ' ' + var - + # By default, do not impose a number of decimal points floatdecimal = 0 - + # For some inputs, impose number of decimal points... if any(inp in var for inp in ('xred', 'xcart', 'rprim', 'qpt', 'kpt')): #TODO Shouldn't do that floatdecimal = 16 - + # ...but not for those if any(inp in var for inp in ('ngkpt', 'kptrlatt', 'ngqpt', 'ng2qpt')): #TODO Shouldn't do that floatdecimal = 0 - + if isinstance(value, np.ndarray): n = 1 for i in np.shape(value): n *= i value = np.reshape(value, n) value = list(value) - + # values in lists if isinstance(value, (list, tuple)): - + # Reshape a list of lists into a single list if all(isinstance(v, (list, tuple)) for v in value): line += self.format_list2d(value, floatdecimal) - + else: # Maximum number of values per line. #valperline = 3 #if any(inp in var for inp in ['bdgw']): # #TODO Shouldn't do that # valperline = 2 - + line += self.format_list(value, floatdecimal) - + # scalar values else: line += ' ' + str(value) @@ -162,7 +162,7 @@ class InputVariable(object): # Add units if self.units: line += ' ' + self.units - + return line def format_scalar(self, val, floatdecimal=0): @@ -173,33 +173,32 @@ class InputVariable(object): sval = str(val) if sval.lstrip('-').lstrip('+').isdigit() and floatdecimal == 0: return sval - + try: fval = float(val) except: return sval - + if fval == 0 or (abs(fval) > 1e-3 and abs(fval) < 1e4): form = 'f' addlen = 5 else: form = 'e' addlen = 8 - + ndec = max(len(str(fval-int(fval)))-2, floatdecimal) ndec = min(ndec, 10) - + sval = '{v:>{l}.{p}{f}}'.format(v=fval, l=ndec+addlen, p=ndec, f=form) - + sval = sval.replace('e', 'd') - + return sval def format_list2d(self, values, floatdecimal=0): """Format a list of lists.""" - lvals = flatten(values) - + # Determine the representation if all(isinstance(v, int) for v in lvals): type_all = int @@ -210,7 +209,7 @@ class InputVariable(object): type_all = float except: type_all = str - + # Determine the format width = max(len(str(s)) for s in lvals) if type_all == int: @@ -218,22 +217,22 @@ class InputVariable(object): elif type_all == str: formatspec = '>{0}'.format(width) else: - + # Number of decimal maxdec = max(len(str(f-int(f)))-2 for f in lvals) ndec = min(max(maxdec, floatdecimal), 10) - + if all(f == 0 or (abs(f) > 1e-3 and abs(f) < 1e4) for f in lvals): formatspec = '>{w}.{p}f'.format(w=ndec+5, p=ndec) else: formatspec = '>{w}.{p}e'.format(w=ndec+8, p=ndec) - + line = '\n' for L in values: for val in L: line += ' {v:{f}}'.format(v=val, f=formatspec) line += '\n' - + return line.rstrip('\n') def format_list(self, values, floatdecimal=0): @@ -242,17 +241,17 @@ class InputVariable(object): The result might be spread among several lines. """ line = '' - + # Format the line declaring the value for i, val in enumerate(values): line += ' ' + self.format_scalar(val, floatdecimal) if self.valperline is not None and (i+1) % self.valperline == 0: line += '\n' - + # Add a carriage return in case of several lines if '\n' in line.rstrip('\n'): line = '\n' + line - + return line.rstrip('\n') @staticmethod @@ -261,18 +260,17 @@ class InputVariable(object): Interpret a string variable and attempt to return a value of the appropriate type. If all else fails, return the initial string. """ - value = None - + try: for part in sval.split(): - + if '*' in part: # cases like istwfk *1 if part[0] == '*': value = None break - + # cases like acell 3*3.52 else: n = int(part.split('*')[0]) @@ -281,22 +279,22 @@ class InputVariable(object): value = [] value += n * [f] continue - + # Fractions if '/' in part: (num, den) = (float(part.split('/')[i]) for i in range(2)) part = num / den - + # Unit if part in _UNITS.keys(): - + if value is None: warnings.warn("Could not apply the unit token '%s'." % part) elif isinstance(value, list): value.append(part) else: value = [value, part] - + # Convert if False: if isinstance(value, list): @@ -307,15 +305,15 @@ class InputVariable(object): break else: value *= _UNITS[part] - + continue - + # Convert try: val = convert_number(part) except: val = part - + if value is None: value = val elif isinstance(value, list): @@ -324,10 +322,10 @@ class InputVariable(object): value = [value, val] except: value = None - + if value is None: value = sval - + return value @classmethod @@ -383,49 +381,50 @@ class InputVariable(object): return self.sorting_name == other.sorting_name -class SpecialInputVariable(InputVariable): - """ - An Abinit input variable which can be set by replacing - the special dataset indices according to - : --> __s - + --> __i - ? --> __a - hence allowing for pythonic names. - """ - @property - def name(self): - return self._name +def is_number(s): + """Returns True if the argument can be made a float.""" + try: + float(s) + return True + except: + return False - @name.setter - def name(self, name): - name = self.internal_to_declared(name) - self._name = name - @property - def internal_name(self): - return self.declared_to_internal(self.name) +def is_iter(obj): + """Return True if the argument is list-like.""" + return hasattr(obj, '__iter__') - @staticmethod - def internal_to_declared(name): - """ - Make the conversion - __s --> : - __i --> + - __a --> ? - """ - for old, new in _SPECIAL_CONVERSION: - name = name.replace(old, new) - return name - @staticmethod - def declared_to_internal(name): - """ - Make the conversion - : --> __s - + --> __i - ? --> __a - """ - for new, old in _SPECIAL_CONVERSION: - name = name.replace(old, new) - return name +#def is_scalar(obj): +# """Return True if the argument is not list-like.""" +# return not is_iter + +def flatten(iterable): + """Make an iterable flat, i.e. a 1d iterable object.""" + iterator = iter(iterable) + array, stack = collections.deque(), collections.deque() + while True: + try: + value = next(iterator) + except StopIteration: + if not stack: + return tuple(array) + iterator = stack.pop() + else: + if not isinstance(value, str) \ + and isinstance(value, collections.Iterable): + stack.append(iterator) + iterator = iter(value) + else: + array.append(value) + +#def listify(obj): +# """Return a flat list out of the argument.""" +# if not obj: +# obj = list() +# elif is_iter(obj): +# obj = list(flatten(obj)) +# else: +# obj = [obj] +# return deepcopy(obj) diff --git a/abipy/core/structure.py b/abipy/core/structure.py index eb652aa5..cfaf716f 100644 --- a/abipy/core/structure.py +++ b/abipy/core/structure.py @@ -575,7 +575,7 @@ class Structure(pymatgen.Structure, NotebookWriter): @property def abi_string(self): """Return a string with the ABINIT input associated to this structure.""" - from abipy.htc.variable import InputVariable + from abipy.abio.variable import InputVariable lines = [] app = lines.append abivars = self.to_abivars() diff --git a/abipy/core/testing.py b/abipy/core/testing.py index e77bc257..fc5dd40c 100644 --- a/abipy/core/testing.py +++ b/abipy/core/testing.py @@ -506,64 +506,3 @@ class AbipyTest(PymatgenTest): @wraps(get_gsinput_si) def get_gsinput_si(*args, **kwargs): return get_gsinput_si(*args, **kwargs) - - -class AbipyFileTest(AbipyTest): - """ - Test class for files with a __str__ attribute. - At setup, must set the 'file' attribute of the AbipyFileTest. - """ - file = None - - @staticmethod - def normalize(string): - string = string.replace('\t', ' ') - string = string.replace("$", " CASH ") - string = string.replace("(", " LP ") - string = string.replace(")", " RP ") - string = string.replace("*", " STAR ") - string = string.strip() - string = '\n'.join([line.strip() for line in string.splitlines()]) - - return string - - def assertContains(self, expression): - """ - Assert that the string representation of the file contains 'expression' - 'expression' is trimmed of leading new line. - Each line of 'expression' is trimmed of blank spaces. - Empty lines are ignored. - """ - expression = self.normalize(expression) - ref = self.normalize(str(self.file)) - - return self.assertRegexpMatches(ref, expression) - - def assertContainsNot(self, expression): - """ - Assert that the string representation of the file does not contain - 'expression'. - """ - expression = self.normalize(expression) - ref = self.normalize(str(self.file)) - - return self.assertNotRegexpMatches(ref, expression) - - def assertEmpty(self): - """Assert the string representation is empty.""" - s = str(self.file).strip() - self.assertFalse(bool(s)) - - def assertOrder(self, expression1, expression2): - """ - Assert that the string representation of the file - contains 'expression1' before 'expression2'. - """ - expression1 = self.normalize(expression1) - expression2 = self.normalize(expression2) - ref = self.normalize(str(self.file)) - self.assertRegexpMatches(ref, expression1) - self.assertRegexpMatches(ref, expression2) - last = ref.split(expression1)[-1] - - return self.assertRegexpMatches(last, expression2) diff --git a/abipy/dev_scripts/fftprof.py b/abipy/dev_scripts/fftprof.py index 1ceaaa67..c6eda3c1 100755 --- a/abipy/dev_scripts/fftprof.py +++ b/abipy/dev_scripts/fftprof.py @@ -7,7 +7,7 @@ from __future__ import print_function, division, unicode_literals, absolute_impo import sys -from abipy.htc.fftbench import FFT_Benchmark +from abipy.tools.fftprof import FFTBenchmark def main(): @@ -18,7 +18,7 @@ def main(): # Plot the benchmark results saved in the files for prof_file in prof_files: - FFT_Benchmark.from_file(prof_file).plot() + FFTBenchmark.from_file(prof_file).plot() return 0 diff --git a/abipy/electrons/gsr.py b/abipy/electrons/gsr.py index 48bda207..be8bbf35 100644 --- a/abipy/electrons/gsr.py +++ b/abipy/electrons/gsr.py @@ -15,7 +15,6 @@ from monty.functools import lazy_property from pymatgen.core.units import EnergyArray, ArrayWithUnit from pymatgen.entries.computed_entries import ComputedEntry, ComputedStructureEntry from abipy.core.mixins import AbinitNcFile, Has_Header, Has_Structure, Has_ElectronBands, NotebookWriter -from prettytable import PrettyTable from abipy.tools.plotting import add_fig_kwargs, get_ax_fig_plt, get_axarray_fig_plt from abipy.abio.robots import Robot from abipy.electrons.ebands import ElectronsReader, RobotWithEbands @@ -317,6 +316,7 @@ class EnergyTerms(AttrDict): @property def table(self): """PrettyTable object with the results.""" + from prettytable import PrettyTable table = PrettyTable(["Term", "Value"]) for k, doc in self._NAME2DOC.items(): table.add_row([k, self[k]]) diff --git a/abipy/examples/flows/run_ldaus.py b/abipy/examples/flows/run_ldaus.py index d015565a..8bbe0e57 100755 --- a/abipy/examples/flows/run_ldaus.py +++ b/abipy/examples/flows/run_ldaus.py @@ -15,6 +15,8 @@ import abipy.data as abidata import abipy.abilab as abilab import abipy.flowtk as flowtk +from abipy.flowtk.abiobjects import LdauParams + def make_scf_nscf_dos_inputs(structure, pseudos, luj_params, paral_kgb=1): # Input file taken from tldau_2.in @@ -96,7 +98,7 @@ def build_flow(options): for u in u_values: # Apply U-J on Ni only. - luj_params = abilab.LdauParams(usepawu, structure) + luj_params = LdauParams(usepawu, structure) luj_params.luj_for_symbol("Ni", l=2, u=u, j=0.1*u, unit="eV") scf_input, nscf_input, dos_input = make_scf_nscf_dos_inputs(structure, pseudos, luj_params) diff --git a/abipy/flowtk/abiobjects.py b/abipy/flowtk/abiobjects.py index b257ba05..f0174d77 100644 --- a/abipy/flowtk/abiobjects.py +++ b/abipy/flowtk/abiobjects.py @@ -1,3 +1,160 @@ from __future__ import print_function, division, unicode_literals, absolute_import +import collections + +from pymatgen.core.units import Energy from pymatgen.io.abinit.abiobjects import * + + +#__all__ = [ +# "LdauParams", +# "LexxParams", +#] + +class LujForSpecie(collections.namedtuple("LdauForSpecie", "l u j unit")): + """ + This object stores the value of l, u, j used for a single atomic specie. + """ + def __new__(cls, l, u, j, unit): + """ + Args: + l: Angular momentum (int or string). + u: U value + j: J Value + unit: Energy unit for u and j. + """ + l = l + u, j = Energy(u, unit), Energy(j, unit) + return super(cls, LujForSpecie).__new__(cls, l, u, j, unit) + + +class LdauParams(object): + """ + This object stores the parameters for LDA+U calculations with the PAW method + It facilitates the specification of the U-J parameters in the Abinit input file. + (see `to_abivars`). The U-J operator will be applied only on the atomic species + that have been selected by calling `lui_for_symbol`. + + To setup the Abinit variables for a LDA+U calculation in NiO with a + U value of 5 eV applied on the nickel atoms: + + .. code-block:: python + + luj_params = LdauParams(usepawu=1, structure=nio_structure) + # Apply U-J on Ni only. + u = 5.0 + luj_params.luj_for_symbol("Ni", l=2, u=u, j=0.1*u, unit="eV") + + print(luj_params.to_abivars()) + """ + def __init__(self, usepawu, structure): + """ + Arg: + usepawu: ABINIT variable `usepawu` defining the LDA+U method. + structure: |Structure| object. + """ + self.usepawu = usepawu + self.structure = structure + self._params = {} + + @property + def symbols_by_typat(self): + return [specie.symbol for specie in self.structure.types_of_specie] + + def luj_for_symbol(self, symbol, l, u, j, unit="eV"): + """ + Args: + symbol: Chemical symbol of the atoms on which LDA+U should be applied. + l: Angular momentum. + u: Value of U. + j: Value of J. + unit: Energy unit of U and J. + """ + if symbol not in self.symbols_by_typat: + raise ValueError("Symbol %s not in symbols_by_typat:\n%s" % (symbol, self.symbols_by_typat)) + + if symbol in self._params: + raise ValueError("Symbol %s is already present in LdauParams! Cannot overwrite:\n" % symbol) + + self._params[symbol] = LujForSpecie(l=l, u=u, j=j, unit=unit) + + def to_abivars(self): + """Returns a dict with the Abinit variables.""" + lpawu, upawu, jpawu = [], [], [] + + for symbol in self.symbols_by_typat: + p = self._params.get(symbol, None) + + if p is not None: + l, u, j = p.l, p.u.to("eV"), p.j.to("eV") + else: + l, u, j = -1, 0, 0 + + lpawu.append(int(l)) + upawu.append(float(u)) + jpawu.append(float(j)) + + # convert upawu and jpaw to string so that we can use + # eV unit in the Abinit input file (much more readable). + return dict( + usepawu=self.usepawu, + lpawu=" ".join(map(str, lpawu)), + upawu=" ".join(map(str, upawu)) + " eV", + jpawu=" ".join(map(str, jpawu)) + " eV") + + +class LexxParams(object): + """ + This object stores the parameters for local exact exchange calculations with the PAW method + It facilitates the specification of the LEXX parameters in the Abinit input file. + (see `to_abivars`). The LEXX operator will be applied only on the atomic species + that have been selected by calling `lexx_for_symbol`. + + To perform a LEXX calculation for NiO in which the LEXX is computed only for the l=2 + channel of the nickel atoms: + + .. code-block:: python + + lexx_params = LexxParams(nio_structure) + lexx_params.lexx_for_symbol("Ni", l=2) + + print(lexc_params.to_abivars()) + """ + def __init__(self, structure): + """ + Arg: + structure: |Structure| object. + """ + self.structure = structure + self._lexx_for_symbol = {} + + @property + def symbols_by_typat(self): + return [specie.symbol for specie in self.structure.types_of_specie] + + def lexx_for_symbol(self, symbol, l): + """ + Enable LEXX for the given chemical symbol and the angular momentum l + + Args: + symbol: Chemical symbol of the atoms on which LEXX should be applied. + l: Angular momentum. + """ + if symbol not in self.symbols_by_typat: + err_msg = "Symbol %s not in symbols_by_typat:\n%s" % (symbol, self.symbols_by_typat) + raise ValueError(err_msg) + + if symbol in self._lexx_for_symbol: + raise ValueError("Symbol %s is already present in LdauParams! Cannot overwrite:" % symbol) + + self._lexx_for_symbol[symbol] = l + + def to_abivars(self): + """Returns a dict with the Abinit variables.""" + lexx_typat = [] + + for symbol in self.symbols_by_typat: + l = self._lexx_for_symbol.get(symbol, -1) + lexx_typat.append(int(l)) + + return dict(useexexch=1, lexexch=" ".join(map(str, lexx_typat))) diff --git a/abipy/flowtk/tests/test_abiobjects.py b/abipy/flowtk/tests/test_abiobjects.py new file mode 100644 index 00000000..b97c8db4 --- /dev/null +++ b/abipy/flowtk/tests/test_abiobjects.py @@ -0,0 +1,55 @@ +"""Tests for flowtk.abiobjects module.""" +from __future__ import print_function, division, unicode_literals + +import numpy as np +import abipy.data as abidata + +from abipy.core.testing import AbipyTest +from abipy.flowtk.abiobjects import LdauParams, LexxParams + + +class LdauLexxTest(AbipyTest): + + def test_nio(self): + """Test LdauParams and LexxParams.""" + aequal, atrue = self.assertEqual, self.assertTrue + + structure = abidata.structure_from_ucell("NiO") + pseudos = abidata.pseudos("28ni.paw", "8o.2.paw") + + u = 8.0 + luj_params = LdauParams(usepawu=1, structure=structure) + luj_params.luj_for_symbol("Ni", l=2, u=u, j=0.1*u, unit="eV") + avars = luj_params.to_abivars() + + self.serialize_with_pickle(luj_params, test_eq=False) + + atrue(avars["usepawu"] == 1), + aequal(avars["lpawu"], "2 -1"), + aequal(avars["upawu"], "8.0 0.0 eV"), + aequal(avars["jpawu"], "0.8 0.0 eV"), + + # Cannot add UJ for non-existent species. + with self.assertRaises(ValueError): + luj_params.luj_for_symbol("Foo", l=2, u=u, j=0.1*u, unit="eV") + + # Cannot overwrite UJ. + with self.assertRaises(ValueError): + luj_params.luj_for_symbol("Ni", l=1, u=u, j=0.1*u, unit="eV") + + lexx_params = LexxParams(structure) + lexx_params.lexx_for_symbol("Ni", l=2) + avars = lexx_params.to_abivars() + + self.serialize_with_pickle(lexx_params, test_eq=False) + + aequal(avars["useexexch"], 1), + aequal(avars["lexexch"], "2 -1"), + + # Cannot add LEXX for non-existent species. + with self.assertRaises(ValueError): + lexx_params.lexx_for_symbol("Foo", l=2) + + # Cannot overwrite LEXX. + with self.assertRaises(ValueError): + lexx_params.lexx_for_symbol("Ni", l=1) diff --git a/abipy/gui/fftprof.py b/abipy/gui/fftprof.py index 74b4f23c..832b250e 100644 --- a/abipy/gui/fftprof.py +++ b/abipy/gui/fftprof.py @@ -5,7 +5,7 @@ import wx from collections import OrderedDict from abipy.gui import awx -from abipy.htc.fftbench import FFTProf +from abipy.tools.fftprof import FFTProf from abipy.gui.editor import SimpleTextViewer from .awx.panels import LinspaceControl diff --git a/abipy/htc/__init__.py b/abipy/htc/__init__.py deleted file mode 100644 index 796d50bd..00000000 --- a/abipy/htc/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -""" -Abinit files handling library -""" -from __future__ import print_function, division, unicode_literals - -__all__ = [] - -_mods = [ - 'input', - 'abinitfiles', - 'filesfile', - 'variable', - 'inputfile', - 'jobfile', - 'abinitinput', - 'launcher', -] - -#for _mod in _mods: -# exec('from .' + _mod + ' import *') -# exec('__all__.extend(' + _mod + '.__all__)') -# exec('del ' + _mod) \ No newline at end of file diff --git a/abipy/htc/abinitfiles.py b/abipy/htc/abinitfiles.py deleted file mode 100644 index ab4f1ebb..00000000 --- a/abipy/htc/abinitfiles.py +++ /dev/null @@ -1,195 +0,0 @@ -""" -Base class for an ABINIT calculation. -""" -from __future__ import print_function, division #, unicode_literals -from os.path import basename, dirname, join, splitext, realpath - -__all__ = ["AbinitFiles"] - - -# =========================================================================== # - -class AbinitFiles(object): - """ - A manager for the abinit file names. - - A calculation is organised as follow:: - - CalcDir/ - |-- CalcRoot.in - |-- CalcRoot.out - |-- input_data/ - | |-- idat_CalcRoot_DS2_DEN - |-- run/ - | |-- CalcRoot.files - | |-- CalcRoot.sh - | |-- CalcRoot.log - | |-- out_data/ - | | |-- odat_CalcRoot_DS1_DEN - | | |-- odat_CalcRoot_DS1_WFK - | |-- tmp_data/ - | | |-- tmp_CalcRoot_LOG_0001 - | | | - """ - - _directory = { - 'inp' : '', - 'out' : '', - 'job' : 'run', - 'files' : 'run', - 'log' : 'run', - 'idat' : 'input_data', - 'odat' : join('run', 'out_data'), - 'tmp' : join('run', 'tmp_data')} - - _prefix = { - 'inp' : '', - 'out' : '', - 'job' : '', - 'files' : '', - 'log' : '', - 'idat' : 'idat_', - 'odat' : 'odat_', - 'tmp' : 'tmp_'} - - _suffix = { - 'inp' : '.in', - 'out' : '.out', - 'files' : '.files', - 'log' : '.log', - 'idat' : '', - 'odat' : '', - 'tmp' : '', - 'job' : '.sh'} - - def _form_name(self, filetype): - d = self._directory[filetype] - pr = self._prefix[filetype] - sf = self._suffix[filetype] - return join(self.absdir, d, pr + self.basename + sf) - - def __init__(self, rootname='AbinitCalculation/root'): - - # Take the root name - if '.' in rootname: - rootname = splitext(rootname)[0] - - # The calculation must be in a directory. - if rootname == basename(rootname): - rootname = join(rootname, 'calc') - - self.set_rootname(rootname) - - def set_rootname(self, name): - """Set the root name.""" - self.__dict__['rootname'] = name - self.__dict__['absdir'] = realpath(dirname(name)) - - @property - def dirname(self): - """The top-level directory.""" - return dirname(self.rootname) - - @property - def basename(self): - """The basename of the root name.""" - return basename(self.rootname) - - @property - def name(self): - """The root name.""" - return self.rootname - - @property - def files_name(self): - """The name of the ".files" file given to abinit.""" - return self._form_name('files') - - @property - def job_name(self): - """The submission script name.""" - return self._form_name('job') - - @property - def input_name(self): - """The input file name.""" - return self._form_name('inp') - - @property - def log_name(self): - """The name of the log file.""" - return self._form_name('log') - - @property - def idat_root(self): - """The root name for input data files.""" - return self._form_name('idat') - - @property - def odat_root(self): - """The root name for output data files.""" - return self._form_name('odat') - - @property - def tmp_root(self): - """The root name for temporaty data files.""" - return self._form_name('tmp') - - @property - def output_name(self): - """The output file produced (based on the name only).""" - return self._form_name('out') - - def get_odat(self, datatype, dtset=0): - """ - Returns an output data file name. - - Args: - datatype: - The type of datafile, e.g. 'DEN' or 'WFK'. - - dtset: - The dataset index from which to take the data file. - If 0 (the default), no dataset index is used. - """ - file = self.odat_root - - if int(dtset) > 0: file += '_DS' + str(dtset) - - file += '_' + datatype.upper().lstrip('_') - - return file - - def get_idat(self, datatype, dtset=0): - """ - Returns an input data file name. - - Args: - datatype: - The type of datafile, e.g. 'DEN' or 'WFK'. - - dtset: - The dataset index from which to take the data file. - If 0 (the default), no dataset index is used. - """ - file = self.idat_root - - if int(dtset) > 0: file += '_DS' + str(dtset) - - file += '_' + datatype.upper().lstrip('_') - - return file - - def get_netcdf(self, datatype, dtset=0): - """ - Returns a netcdf output data file name. - - Args: - datatype: - The type of datafile, 'DEN' or 'WFK'. - - dtset: - The dataset index from which to take the data file. - If 0 (the default), no dataset index is used. - """ - return self.get_odat(datatype, dtset=dtset) + '.nc' diff --git a/abipy/htc/abinitinput.py b/abipy/htc/abinitinput.py deleted file mode 100644 index 514fbdd1..00000000 --- a/abipy/htc/abinitinput.py +++ /dev/null @@ -1,313 +0,0 @@ -from __future__ import print_function, division #, unicode_literals - -import subprocess - -from os import makedirs, readlink, symlink -from os.path import basename, dirname, exists, join, realpath - -from abipy.profile import abipy_env -from .filesfile import FilesFile -from .abinitfiles import AbinitFiles -from .inputfile import InputFile -from .jobfile import JobFile, PBSJobFile, SGEJobFile, SlurmJobFile - -__all__ = ['AbinitInput'] - -# =========================================================================== # - -class AbinitInput(AbinitFiles): - """ - To Create an Abinit calculation. - The AbinitInput contains three main internal objects: - an :class:`~abipy.htc.InputFile`, a :class:`~abipy.htc.FilesFile`, - and a :class:`~abipy.htc.JobFile`. - - **Arguments**: - name: - A string of the form 'directory/rootname' or simply 'directory'. - jobtype: - The type of job, e.g. 'PBS', 'SGE', 'Slurm' or None. - - **Keyword arguments for the inputfile**: - variables: - A dictionary of input variables for the calculation. - - **Keyword arguments for the filesfile**: - pseudodir: - The directory in which to look for the pseudopotential files. - pseudos: - A list of pseudopotential files. - - **Keyword arguments for the jobfile**: - bindir: - The directory in which to look for binaries. - executable: - The binary to be executed. Default is 'abinit'. - mpirun: - The mpi runner. E.g. 'mpiexec -npernode 6'. - modules: - List of modules which will be loaded with 'module load'. - lines_before: - List of lines to be executed before the main execution. - lines_after: - List of lines to be executed after the main execution. - other_lines: - List of command lines for the job submission system. - - Any property of the JobFile or the selected subclass - (PBSJobFile, SGEJobFile, SlurmJobFile, ...) - - .. code-block:: python - - >> calc = AbinitInput('Mycalc/rootname', jobtype='PBS', - .. pseudodir='Data', - .. bindir='/path/to/binaries/', - .. lines_before=['cd ${PBS_O_WORKDIR}']) - >> - >> # Input file properties - >> calc.ecut = 10. - >> calc.tolwfr = 1e-8 - >> - >> calc.kptopt = 1 - >> calc.ngkpt = [2, 2, 2] - >> calc.nshiftk = 1 - >> calc.shiftk = [0.0, 0.0, 0.0] - >> - >> unit_cell = { - .. 'acell' : 3*[8.6277], - .. 'rprim' : [[.0, .5, .5], - .. [.5, .0, .5], - .. [.5, .5, .0]], - .. 'ntypat' : 2, - .. 'znucl' : [30, 8], - .. 'natom' : 2, - .. 'typat' : [1, 2], - .. 'xred' : [[.0, .0, .0], - .. [.25,.25,.25]]} - >> - >> calc.set_variables(unit_cell) - >> - >> # Files file properties - >> calc.set_pseudos('Zn.psp', 'O.psp') - >> - >> # Job file properties - >> calc.set_jobname('MyTest') - >> calc.set_nodes(2) - >> calc.set_ppn(12) - >> calc.set_memory('24gb') - >> calc.set_runtime(48) - >> calc.set_mpirun('mpiexec -npernode 6') - >> - >> calc.write() - - As you can see, setting an attribute of the AbinitInput object - will result in setting that input variable in the input file. - - Note also that *all* of the :class:`~abipy.htc.JobFile` functions are available - though the AbinitInput. - """ - - def __init__(self, name='Calculation/abinit', jobtype='PBS', **kwargs): - - AbinitFiles.__init__(self, name) - - # Initialize the inputfile - self._setattr(inputfile = InputFile(self.input_name)) - - # Initialize the filesfile - self._setattr(filesfile = FilesFile(self.files_name, - input=self.input_name, - output=self.output_name, - idat_root=self.idat_root, - odat_root=self.odat_root, - tmp_root=self.tmp_root)) - - # Initialize the jobfile - jobargs = dict(name=self.job_name, executable='abinit', - input=self.files_name, - log=self.log_name) - - #jobtype = abipy_env.get_uservar("jobtype", kwargs) - if jobtype is None: jobtype = '' - - if jobtype.lower() == 'pbs': - self._setattr(jobfile = PBSJobFile(**jobargs)) - - elif jobtype.lower() == 'sge': - self._setattr(jobfile = SGEJobFile(**jobargs)) - - elif jobtype.lower() == 'slurm': - self._setattr(jobfile = SlurmJobFile(**jobargs)) - - else: - self._setattr(jobfile = JobFile(**jobargs)) - - # Create setter function for jobfile attributes. - for prop in self.jobfile.properties(): - function = 'set_' + prop - self.__dict__[function] = getattr(self.jobfile, function) - - # Pairs of (target, pointer) to be linked. - self._setattr(_to_link = list()) - - # Other arguments - for key in self.properties(): - val = abipy_env.get_default(key, kwargs) - if val is not None: - getattr(self, 'set_' + key)(val) - - def _setattr(self, **kwargs): - self.__dict__.update(kwargs) - - def __setattr__(self, name, val): - setattr(self.inputfile, name, val) - - def write(self, *args, **kwargs): - """ - Write all the files for the calculation. - """ - - force = kwargs.get("force", False) - if not force and self.inputfile.exists: - print("Cannot overwrite: %s\tUse -f to force file creation." % self.name) - return - - # Create directories - for file in (self.input_name, self.output_name, self.job_name, - self.files_name, self.log_name, self.idat_root, - self.odat_root, self.tmp_root): - directory = dirname(file) - if not exists(directory): - makedirs(directory) - - # Link files - for pair in self._to_link: - self._link(*pair) - - # Write files - self.inputfile.write() - self.filesfile.write() - self.jobfile.write() - - def link_idat(self, file, dtset='auto', datatype='auto'): - """ - Make a symbolic link for input data. - - Args: - file: - The name of the file to be linked. - dtset: - The index of the dataset which should read the file. - By default, it is read from the file name. - Set to zero for no dataset index. - datatype: - The type of datafile, e.g. 'DEN' or 'WFK'. - By default, it is read from the file name. - """ - if datatype == 'auto': - datatype = file.split('_')[-1] - - if dtset == 'auto': - dtset = file.split('_DS', 1)[-1].split('_')[0] - try: - dtset = int(dtset) - except: - dtset = 0 - - self._to_link.append((file, self.get_idat(datatype, dtset))) - - def link_odat(self, file, dtset='auto', datatype='auto'): - """ - Make a symbolic link for output data. - - Args: - file: - The name of the file to be linked. - dtset: - The index of the dataset from which the file belongs. - By default, it is read from the file name. - Set to zero for no dataset index. - datatype: - The type of datafile, e.g. 'DEN' or 'WFK'. - By default, it is read from the file name. - """ - if datatype == 'auto': - datatype = file.split('_')[-1] - - if dtset == 'auto': - dtset = file.split('_DS', 1)[-1].split('_')[0] - try: - dtset = int(dtset) - except: - dtset = 0 - - self._to_link.append((file, self.get_odat(datatype, dtset))) - - def link_io(self, idtset, odtset, datatype): - """ - Make a symbolic link from an output data to an input data. - - Args: - idtset: - The dataset index of the input file. - odtset: - The dataset index of the output file. - datatype: - The type of datafile, e.g. 'DEN' or 'WFK'. - """ - idat = self.get_idat(datatype, idtset) - odat = self.get_odat(datatype, odtset) - self._to_link.append((odat, idat)) - - def set_pseudodir(self, pseudodir): - """Set the directory for the pseudopotentials. Linked to FilesFile.""" - return self.filesfile.set_pseudodir(pseudodir) - - def set_pseudos(self, *pseudopotentials): - """Set the pseudopotential files. Linked to FilesFile.""" - return self.filesfile.set_pseudos(*pseudopotentials) - - def read(self, *args, **kwargs): - """Read an input file. Linked to InputFile.""" - return self.inputfile.read(*args, **kwargs) - - def set_variables(self, *args, **kwargs): - """Set input variables. Linked to InputFile.""" - return self.inputfile.set_variables(*args, **kwargs) - - def set_comment(self, *args, **kwargs): - """Set a comment in the input file. Linked to InputFile.""" - return self.inputfile.set_comment(*args, **kwargs) - - def properties(self): - """Return the list of properties with a 'set_' function.""" - funcs = filter(lambda s: s.startswith('set_'), dir(self)) - return [ f.split('set_', 1)[-1] for f in funcs ] - - @staticmethod - def _link(target, pointer): - """ - Create the symbolic link target <-- pointer. - Overwrite an existing link, but don't overwrite a file. - """ - - atarget = realpath(target) - pointerdir = realpath(dirname(pointer)) - apointer = join(pointerdir, basename(pointer)) - - if not exists(pointerdir): - subprocess.call(('mkdir', '-p', pointerdir)) - - try: - symlink(atarget, apointer) - except OSError: - try: - oldtarget = realpath(readlink(apointer)) - if oldtarget == atarget: - return - else: - subprocess.call(('ln', '-fs', atarget, apointer)) - except OSError: - raise OSError("Unable to link the files. " + - "Maybe {0} exists and is not a link.".format(apointer)) - diff --git a/abipy/htc/filesfile.py b/abipy/htc/filesfile.py deleted file mode 100644 index 5a41d2aa..00000000 --- a/abipy/htc/filesfile.py +++ /dev/null @@ -1,145 +0,0 @@ -from __future__ import print_function, division #, unicode_literals -import warnings - -from os import makedirs -from os.path import dirname, join, exists, realpath -from copy import deepcopy - -from .abinitfiles import AbinitFiles - -__all__ = ['FilesFile'] - -# =========================================================================== # - - -class FilesFile(object): - """ - A .files file used as input for abinit. - - **Keyword arguments:** - - input: Input file. - output: Output file. - idat_root: Root for input data. - odat_root: Root for output data. - tmp_root: Root for temporary files. - pseudodir: The directory for the pseudopotentials. - pseudos: List of pseudopotential files. - - .. code-block:: python - - >> filesfile = Filesfile('run/abinit.files', - .. input='abinit.in', - .. output='abinit.out', - .. idat_root='run/data_files/idat_', - .. odat_root='run/data_files/odat_', - .. tmp_root='run/data_files/tmp_',) - .. pseudodir='/path/to/pseudopotential/directory',) - >> - >> filesfile.set_pseudos(['H.psp', 'O.psp']) - >> filesfile.write() - >> with open('run/abinit.files', 'r') as f: print f.read() - ../CalcRoot.in - ../CalcRoot.out - data_files/idat_ - data_files/odat_ - tmp_files/tmp_ - /path/to/pseudopotential/directory/H.psp - /path/to/pseudopotential/directory/O.psp - """ - - def __init__(self, name='calc.files', **kwargs): - - self.name = name - - self.input = 'calc.in' - self.output = 'calc.out' - self.idat_root = 'idat_calc' - self.odat_root = 'odat_calc' - self.tmp_root = 'tmp_calc' - - self.pseudodir = '.' - self.pseudos = list() - - for (arg, val) in kwargs.items(): - getattr(self, 'set_' + arg)(val) - - def set_input(self, name): - """Set the input file.""" - self.input = name - - def set_output(self, name): - """Set the output file.""" - self.output = name - - def set_idat_root(self, name): - """Set root for input data.""" - self.idat_root = name - - def set_odat_root(self, name): - """Set root for output data.""" - self.odat_root = name - - def set_tmp_root(self, name): - """Set root for temporary files.""" - self.tmp_root = name - - def set_pseudodir(self, directory): - """Set the directory for the pseudopotentials.""" - self.pseudodir = realpath(directory) - - def set_pseudos(self, *pseudos): - """ - Sets the pseudopotential files. - - **Arguments:** - pseudos: - Pseudopotential files. - - Both of these syntax work:: - >> f.set_pseudos('H.psp', 'O.psp') - >> f.set_pseudos(['H.psp', 'O.psp']) - """ - if not pseudos: - self.pseudos = list() - return - - elif '__iter__' in dir(pseudos[0]): - pseudos = tuple(pseudos[0]) - - self.pseudos = pseudos - - @property - def pseudos(self): - """Pseudopotential files.""" - return [ join(self.pseudodir, pseudo) for pseudo in self._pseudos ] - - @pseudos.setter - def pseudos(self, vals): - self._pseudos = deepcopy(vals) - - @property - def dirname(self): - """The directory containing the file.""" - return dirname(self.name) - - def check_pseudos(self): - """Issue a warning for each pseudopotential file not found.""" - for pseudo in self.pseudos: - if not exists(pseudo): - warnings.warn('Pseudopotential file not found: ' + pseudo) - - def __str__(self): - lines = [self.input, self.output, self.idat_root, self.odat_root, - self.tmp_root] + self.pseudos - return '\n'.join(lines) + '\n' - - def write(self): - """Write the file.""" - self.check_pseudos() - - if self.dirname and not exists(self.dirname): - makedirs(self.dirname) - - with open(self.name, 'w') as f: - f.write(str(self)) diff --git a/abipy/htc/input.py b/abipy/htc/input.py deleted file mode 100644 index c636c2b2..00000000 --- a/abipy/htc/input.py +++ /dev/null @@ -1,1348 +0,0 @@ -""" -This module defines objects that faciliate the creation of the -ABINIT input files. The syntax is similar to the one used -in ABINIT with small differences. -""" -from __future__ import print_function, division, unicode_literals, absolute_import - -import os -import collections -import warnings -import itertools -import copy -import six -import abc -import json -import numpy as np - -from collections import OrderedDict -from monty.dev import deprecated -from monty.collections import dict2namedtuple -from monty.string import is_string, list_strings -from monty.os.path import which -from monty.json import MSONable -from pymatgen.core.units import Energy -try: - from pymatgen.util.serialization import pmg_serialize -except ImportError: - from pymatgen.serializers.json_coders import pmg_serialize -from abipy.flowtk import PseudoTable, Pseudo, TaskManager, AbinitTask, NetcdfReader -from pymatgen.io.abinit.abiinspect import yaml_read_irred_perts -from abipy.core.structure import Structure -from abipy.core.mixins import Has_Structure -from .variable import InputVariable -from abipy.abio.abivars import is_abivar, is_anaddb_var -from abipy.abio.abivars_db import get_abinit_variables - -import logging -logger = logging.getLogger(__file__) - - -__all__ = [ - "AbiInput", - "LdauParams", - "LexxParams", - "input_gen", -] - -# Variables that must have a unique value throughout all the datasets. -_ABINIT_NO_MULTI = [ - "jdtset", - "ndtset", - "ntypat", - "znucl", - "cpus", - "cpum", - "cpuh", - "npsp", - "timopt", - "iatcon", - "natcon", - "nconeq", - "prtxtypat", - "wtatcon", -] - - -def straceback(): - """Returns a string with the traceback.""" - import traceback - return traceback.format_exc() - - -def _idt_varname(varname): - """ - Find the dataset index from the name of the variable. - Return the dataset index and the name of the variable with the numeric index removed. - dataset index is set to 0 for global variable. - - >>> assert _idt_varname("foo") == (0, "foo") - >>> assert _idt_varname("foo1") == (1, "foo") - """ - if not varname[-1].isdigit(): - return 0, varname - - # Find the index of the dataset. - idt = "" - for i, c in enumerate(reversed(varname)): - if c.isdigit(): - idt += c - else: - break - - # Strip the numeric index in varname - return int("".join(reversed(idt))), varname[:len(varname)-i] - - -@six.add_metaclass(abc.ABCMeta) -class Input(six.with_metaclass(abc.ABCMeta, MSONable, object)): - """ - Base class for Input objects. - - An input object must define have a make_input method - that returns the string representation used by the client code. - """ - def deepcopy(self): - """Deep copy of the input.""" - return copy.deepcopy(self) - - def write(self, filepath): - """ - Write the input file to file to `filepath`. Returns a string with the input. - """ - dirname = os.path.dirname(filepath) - if not os.path.exists(dirname): os.makedirs(dirname) - - # Write the input file. - input_string = str(self) - with open(filepath, "wt") as fh: - fh.write(input_string) - - return input_string - - #@abc.abstractmethod - #def make_input(self): - - #@abc.abstractproperty - #def structure(self): - - #def set_structure(self, structure): - - -class AbinitInputError(Exception): - """Base error class for exceptions raised by `AbiInput`""" - -#from monty.dev import deprecated -#from abipy.abio.inputs import AbinitInput -#@deprecated(replacement=AbinitInput, -# message="This class is deprecated and will be removed in abipy0.2.") -class AbiInput(Input, Has_Structure): - """ - This object represents an ABINIT input file. It supports multi-datasets a - and provides an easy-to-use interface for defining the variables of the calculation. - - Usage example: - - .. code-block:: python - - inp = AbiInput(pseudos="si.pspnc", pseudo_dir="directory_with_pseudos") - inp.set_structure("si.cif") - inp.set_vars(ecut=10, nband=3) - - # Print it to get the final input. - print(inp) - """ - Error = AbinitInputError - - def __init__(self, pseudos, pseudo_dir="", structure=None, ndtset=1, comment="", decorators=None): - """ - Args: - pseudos: String or list of string with the name of the pseudopotential files. - pseudo_dir: Name of the directory where the pseudopotential files are located. - structure: file with the structure, :class:`Structure` object or dictionary with ABINIT geo variable - ndtset: Number of datasets. - comment: Optional string with a comment that will be placed at the beginning of the file. - decorators: List of `AbinitInputDecorator` objects. - """ - # Dataset[0] contains the global variables common to the different datasets - # Dataset[1:ndtset+1] stores the variables specific to the different datasets. - self._ndtset = ndtset - - self._datasets = [] - for i in range(ndtset+1): - dt0 = None - if i > 0: dt0 = self._datasets[0] - self._datasets.append(Dataset(index=i, dt0=dt0)) - - self._datasets[0]["ndtset"] = ndtset - - # Setup of the pseudopotential files. - if isinstance(pseudos, PseudoTable): - self._pseudos = pseudos - - elif all(isinstance(p, Pseudo) for p in pseudos): - self._pseudos = PseudoTable(pseudos) - - else: - # String(s) - pseudo_dir = os.path.abspath(pseudo_dir) - pseudo_paths = [os.path.join(pseudo_dir, p) for p in list_strings(pseudos)] - - missing = [p for p in pseudo_paths if not os.path.exists(p)] - if missing: - raise self.Error("Cannot find the following pseudopotential files:\n%s" % str(missing)) - - self._pseudos = PseudoTable(pseudo_paths) - - if structure is not None: self.set_structure(structure) - if comment is not None: self.set_comment(comment) - - self._decorators = [] if not decorators else decorators - - def __str__(self): - """String representation i.e. the input file read by Abinit.""" - if self.ndtset > 1: - s = "" - for i, dataset in enumerate(self): - header = "### DATASET %d ###" % i - if i == 0: - header = "### GLOBAL VARIABLES ###" - - str_dataset = str(dataset) - if str_dataset: - header = len(header) * "#" + "\n" + header + "\n" + len(header) * "#" + "\n" - s += "\n" + header + str(dataset) + "\n" - else: - # single datasets ==> don't append the dataset index to the variables in self[1] - # this trick is needed because Abinit complains if ndtset is not specified - # and we have variables that end with the dataset index e.g. acell1 - # We don't want to specify ndtset here since abinit will start to add DS# to - # the input and output files thus complicating the algorithms we have to use - # to locate the files. - d = self[0].deepcopy() - d.update(self[1]) - s = d.to_string(post="") - - # Add JSON section with pseudo potentials. - ppinfo = ["\n\n\n#"] - d = {"pseudos": [p.as_dict() for p in self.pseudos]} - ppinfo.extend(json.dumps(d, indent=4).splitlines()) - ppinfo.append("") - - return s + "\n#".join(ppinfo) - - def __getitem__(self, key): - return self._datasets[key] - - def __getattr__(self, varname): - try: - return super(AbiInput, self).__geattr__(self, varname) - except AttributeError: - try: - idt, varname = _idt_varname(varname) - return self._datasets[idt][varname] - except Exception as exc: - raise AttributeError(str(exc)) - - def __setattr__(self, varname, value): - if varname.startswith("ndtset"): - raise self.Error("Cannot change read-only variable ndtset") - - if varname.startswith("_"): - # Standard behaviour for "private" variables. - super(AbiInput, self).__setattr__(varname, value) - else: - idt, varname = _idt_varname(varname) - - if idt == 0: - # Global variable. - self[0].set_var(varname, value) - else: - if not (self.ndtset >= idt >= 1): - raise self.Error("Invalid dataset index %d, ndtset %d " % (idt, self.ndtset)) - - self[idt].set_var(varname, value) - - # Handle no_multi variables. - if varname in _ABINIT_NO_MULTI: - - if varname in self[0]: - glob_value = np.array(self[0][varname]) - isok = np.all(glob_value == np.array(value)) - if not isok: - err_msg = "NO_MULTI variable: dataset 0: %s, dataset %d: %s" % (str(glob_value), idt, str(value)) - raise self.Error(err_msg) - else: - self[0].set_var(varname, value) - - @property - def ndtset(self): - """Number of datasets.""" - return self[0]["ndtset"] - - @property - def pseudos(self): - """List of :class:`Pseudo` objects.""" - return self._pseudos - - @property - def ispaw(self): - """True if PAW calculation.""" - return all(p.ispaw for p in self.pseudos) - - @property - def isnc(self): - """True if norm-conserving calculation.""" - return all(p.isnc for p in self.pseudos) - - @property - def num_valence_electrons(self): - """Number of valence electrons computed from the pseudos and the structure.""" - return self.structure.num_valence_electrons(self.pseudos) - - @property - def structure(self): - """ - Returns the :class:`Structure` associated to the ABINIT input. - - Raises: - ValueError if we have multi datasets with different - structure so that users will learn that multiple datasets are bad. - """ - structures = [dt.structure for dt in self[1:]] - - if all(s == structures[0] for s in structures): - return structures[0] - - raise ValueError("Cannot extract a unique structure from an input file with multiple datasets!\n" + - "Please DON'T use multiple datasets with different unit cells!") - - def is_abivar(self, varname): - """True if varname is a valid Abinit variable.""" - return is_abivar(varname) - - def new_with_vars(self, *args, **kwargs): - """Generate a new input (deep copy) with these variables""" - new = self.deepcopy() - new.set_vars(*args, **kwargs) - return new - - @property - def decorators(self): - return self._decorators - - def register_decorator(self, decorator): - """Register a :class:`AbinitInputDecorator`.""" - self._decorators.append(decorator.as_dict()) - - def generate(self, **kwargs): - """ - Generate new inputs by replacing the variables specified in kwargs. - - .. code-block:: python - - # To generate two input files with different values of ecut: - for inp_ecut in gs_inp.generate(ecut=[10, 20])): - print("do something with inp_ecut %s" % inp_ecut) - - # To generate four input files with all the possible combinations of ecut and nsppol: - for inp_ecut in gs_inp.generate(ecut=[10, 20], nsppol=[1, 2]): - print("do something with inp_ecut %s" % inp_ecut) - """ - if self.ndtset != 1: raise ValueError("Cannot use generate methods when ndtset != 1") - return input_gen(self, **kwargs) - - def _dtset2range(self, dtset): - """Helper function to convert dtset into a range. Accepts int, slice objects or iterable.""" - if isinstance(dtset, int): - return [dtset] - - elif isinstance(dtset, slice): - start = dtset.start if dtset.start is not None else 0 - stop = dtset.stop - step = dtset.step if dtset.step is not None else 1 - return range(start, stop, step) - - elif isinstance(dtset, collections.Iterable): - return dtset - - raise self.Error("Don't know how to convert %s to a range-like object" % str(dtset)) - - def split_datasets(self): - """ - Split an input file with multiple datasets into a list of `ndtset` distinct input files. - """ - news = [] - for i in range(self.ndtset): - my_vars = self[i+1].allvars - my_vars.pop("ndtset", None) - - # Cannot use get*, ird* variables since links must be explicit. - for varname in my_vars: - if varname.startswith("get") or varname.startswith("ird"): - err_msg = "get* or ird variables should not be present in the input when you split it into datasets" - raise self.Error(err_msg) - - new = self.__class__(pseudos=self.pseudos, ndtset=1, decorators=self._decorators) - new.set_vars(**my_vars) - new.set_mnemonics(self.mnemonics) - news.append(new) - - return news - - def set_vars(self, *args, **kwargs): - """ - Set the value of a set of input variables. - - Args: - dtset: Int with the index of the dataset, slice object of iterable - kwargs: Dictionary with the variables. - - Returns: - self - """ - dtset = kwargs.pop("dtset", 0) - kwargs.update(dict(*args)) - for idt in self._dtset2range(dtset): - self[idt].set_vars(**kwargs) - - return self - - # Alias - set_variables = set_vars - set_variables = deprecated(replacement=set_vars)(set_variables) - - def remove_vars(self, keys, dtset=0): - """ - Remove the variables listed in keys - - Args: - dtset: Int with the index of the dataset, slice object of iterable - keys: List of variables to remove. - """ - for idt in self._dtset2range(dtset): - self[idt].remove_vars(keys) - - # Alias - remove_variables = remove_vars - remove_variables = deprecated(replacement=remove_vars)(remove_variables) - - def set_comment(self, comment, dtset=0): - """Set the main comment in the input file.""" - for idt in self._dtset2range(dtset): - self[idt].set_comment(comment) - - def set_mnemonics(self, boolean): - """True if mnemonics should be printed""" - self._mnemonics = bool(boolean) - for idt in range(self.ndtset): - self[idt].set_mnemonics(boolean) - - @property - def mnemonics(self): - """Return True if mnemonics should be printed""" - try: - return self._mnemonics - except AttributeError: - return False - - def get_ibz(self, ngkpt=None, shiftk=None, kptopt=None, qpoint=None, workdir=None, manager=None): - """ - This function, computes the list of points in the IBZ and the corresponding weights. - It should be called with an input file that contains all the mandatory variables required by ABINIT. - - Args: - ngkpt: Number of divisions for the k-mesh (default None i.e. use ngkpt from self) - shiftk: Shiftks (default None i.e. use shiftk from self) - qpoint: qpoint in reduced coordinates. Used to shift the k-mesh (default None i.e no shift) - workdir: Working directory of the fake task used to compute the ibz. Use None for temporary dir. - manager: :class:`TaskManager` of the task. If None, the manager is initialized from the config file. - - Returns: - `namedtuple` with attributes: - points: `ndarray` with points in the IBZ in reduced coordinates. - weights: `ndarray` with weights of the points. - - .. warning:: - - Multiple datasets are ignored. Only the list of k-points for dataset 1 are returned. - """ - if self.ndtset != 1: - raise RuntimeError("get_ibz cannot be used if the input contains more than one dataset") - - # Avoid modifications in self. - inp = self.split_datasets()[0].deepcopy() - - # The magic value that makes ABINIT print the ibz and then stop. - inp.prtkpt = -2 - - if ngkpt is not None: inp.ngkpt = ngkpt - if shiftk is not None: - inp.shiftk = np.reshape(shiftk, (-1,3)) - inp.nshiftk = len(inp.shiftk) - - if kptopt is not None: - inp.kptopt = kptopt - - if qpoint is not None: - inp.qptn, inp.nqpt = qpoint, 1 - - # Build a Task to run Abinit in a shell subprocess - task = AbinitTask.temp_shell_task(inp, workdir=workdir, manager=manager) - task.start_and_wait(autoparal=False) - - # Read the list of k-points from the netcdf file. - try: - with NetcdfReader(os.path.join(task.workdir, "kpts.nc")) as r: - ibz = collections.namedtuple("ibz", "points weights") - return ibz(points=r.read_value("reduced_coordinates_of_kpoints"), - weights=r.read_value("kpoint_weights")) - - except Exception as exc: - # Try to understand if it's a problem with the Abinit input. - report = task.get_event_report() - if report.errors: raise self.Error(str(report)) - raise exc - - def get_irred_perts(self, ngkpt=None, shiftk=None, kptopt=None, qpoint=None, workdir=None, manager=None): - """ - This function, computes the list of irreducible perturbations for DFPT. - It should be called with an input file that contains all the mandatory variables required by ABINIT. - - Args: - ngkpt: Number of divisions for the k-mesh (default None i.e. use ngkpt from self) - shiftk: Shiftks (default None i.e. use shiftk from self) - qpoint: qpoint in reduced coordinates. Used to shift the k-mesh (default None i.e no shift) - workdir: Working directory of the fake task used to compute the ibz. Use None for temporary dir. - manager: :class:`TaskManager` of the task. If None, the manager is initialized from the config file. - - Returns: - List of dictionaries with the Abinit variables defining the irreducible perturbation - Example: - - [{'idir': 1, 'ipert': 1, 'qpt': [0.25, 0.0, 0.0]}, - {'idir': 2, 'ipert': 1, 'qpt': [0.25, 0.0, 0.0]}] - - .. warning:: - - Multiple datasets are ignored. Only the list of k-points for dataset 1 are returned. - """ - if self.ndtset != 1: - raise RuntimeError("get_irred_perts cannot be used if the input contains more than one dataset") - - warnings.warn("get_irred_perts is still under development.") - # Avoid modifications in self. - inp = self.split_datasets()[0].deepcopy() - - # Use the magic value paral_rf = -1 to get the list of irreducible perturbations for this q-point. - d = dict( - paral_rf=-1, - rfatpol=[1, len(inp.structure)], # Set of atoms to displace. - rfdir=[1, 1, 1], # Along this set of reduced coordinate axis. - ) - inp.set_vars(d) - - # Build a Task to run Abinit in a shell subprocess - task = AbinitTask.temp_shell_task(inp, workdir=workdir, manager=manager) - task.start_and_wait(autoparal=False) - - # Parse the file to get the perturbations. - try: - return yaml_read_irred_perts(task.log_file.path) - except Exception as exc: - # Try to understand if it's a problem with the Abinit input. - report = task.get_event_report() - if report.errors: raise self.Error(str(report)) - raise exc - - def linspace(self, varname, start, stop, endpoint=True): - """ - Returns `ndtset` evenly spaced samples, calculated over the interval [`start`, `stop`]. - - The endpoint of the interval can optionally be excluded. - - Args: - start: The starting value of the sequence. - stop: The end value of the sequence, unless `endpoint` is set to False. - In that case, the sequence consists of all but the last of ``ndtset + 1`` - evenly spaced samples, so that `stop` is excluded. Note that the step - size changes when `endpoint` is False. - """ - if varname in self[0]: - raise self.Error("varname %s is already defined in globals" % varname) - - lspace = np.linspace(start, stop, num=self.ndtset, endpoint=endpoint, retstep=False) - - for dataset, value in zip(self[1:], lspace): - dataset.set_var(varname, value) - - def arange(self, varname, start, stop, step): - """ - Return evenly spaced values within a given interval. - - Values are generated within the half-open interval ``[start, stop)`` - (in other words, the interval including `start` but excluding `stop`). - - When using a non-integer step, such as 0.1, the results will often not - be consistent. It is better to use ``linspace`` for these cases. - - Args: - start: Start of interval. The interval includes this value. The default start value is 0. - stop: End of interval. The interval does not include this value, except - in some cases where `step` is not an integer and floating point - step: Spacing between values. For any output `out`, this is the distance - between two adjacent values, ``out[i+1] - out[i]``. The default - step size is 1. If `step` is specified, `start` must also be given. - """ - if varname in self[0]: - raise self.Error("varname %s is already defined in globals" % varname) - - arang = np.arange(start=start, stop=stop, step=step) - if len(arang) != self.ndtset: - raise self.Error("len(arang) %d != ndtset %d" % (len(arang), self.ndtset)) - - for (dataset, value) in zip(self[1:], arang): - dataset.set_var(varname, value) - - def product(self, *items): - """ - Cartesian product of input iterables. Equivalent to nested for-loops. - - .. code-block:: python - - inp.product("tsmear", "ngkpt", [[2,2,2], [4,4,4]], [0.1, 0.2, 0.3]) - """ - # Split items into varnames and values - for i, item in enumerate(items): - if not is_string(item): break - - varnames, values = items[:i], items[i:] - if len(varnames) != len(values): - raise self.Error("The number of variables must equal the number of lists") - - varnames = [ [varnames[i]] * len(values[i]) for i in range(len(values))] - varnames = itertools.product(*varnames) - values = itertools.product(*values) - - idt = 0 - for names, values in zip(varnames, values): - idt += 1 - self[idt].set_vars(**{k: v for k, v in zip(names, values)}) - - if idt != self.ndtset: - raise self.Error("The number of configurations must equal ndtset while %d != %d" % (idt, self.ndtset)) - - def set_structure(self, structure, dtset=0): - """Set the :class:`Structure` object for the specified dtset.""" - structure = Structure.as_structure(structure) - - if dtset is None: - dtset = slice(self.ndtset+1) - for idt in self._dtset2range(dtset): - self[idt].set_structure(structure) - - @deprecated(set_structure) - def set_structure_from_file(self, filepath, dtset=0, cls=Structure): - """ - Set the :class:`Structure` object for the specified dtset. - data is read from filepath. cls specifies the class to instantiate. - """ - structure = cls.from_file(filepath) - self.set_structure(structure, dtset=dtset) - return structure - - def set_kmesh(self, ngkpt, shiftk, kptopt=1, dtset=0): - """Set the variables defining the k-point sampling for the specified dtset.""" - for idt in self._dtset2range(dtset): - self[idt].set_kmesh(ngkpt, shiftk, kptopt=kptopt) - - def set_autokmesh(self, nksmall, kptopt=1, dtset=0): - """ - Set the variables (ngkpt, shift, kptopt) for the sampling of the BZ. - - Args: - nksmall: Number of k-points used to sample the smallest lattice vector. - kptopt: Option for the generation of the mesh (ABINIT variable). - """ - for idt in self._dtset2range(dtset): - self[idt].set_autokmesh(nksmall, kptopt=kptopt) - - def set_autokpath(self, ndivsm, dtset=0): - """ - Set automatically the k-path from the lattice - and the number of divisions for the smallest segment (ndivsm) - """ - for idt in self._dtset2range(dtset): - self[idt].set_kpath(ndivsm, kptbounds=None) - - def set_kpath(self, ndivsm, kptbounds=None, iscf=-2, dtset=0): - """ - Set the variables defining the k-path for the specified dtset. - The list of K-points is taken from the pymatgen database if `kptbounds` is None. - - Args: - ndivsm: Number of divisions for the smallest segment. - kptbounds: k-points defining the path in k-space. - iscf: iscf variable. - dtset: Index of the dataset. 0 for global variables. - """ - for idt in self._dtset2range(dtset): - self[idt].set_kpath(ndivsm, kptbounds=kptbounds, iscf=iscf) - - def set_kptgw(self, kptgw, bdgw, dtset=0): - """Set the variables defining the k point for the GW corrections""" - for idt in self._dtset2range(dtset): - self[idt].set_kptgw(kptgw, bdgw) - - @pmg_serialize - def as_dict(self): - dtsets = [] - for ds in self: - ds_copy = ds.deepcopy() - for key, value in ds_copy.items(): - if isinstance(value, np.ndarray): - ds_copy[key] = value.tolist() - dtsets.append(dict(ds_copy)) - - return {'pseudos': [p.as_dict() for p in self.pseudos], - 'datasets': dtsets, - "decorators": [dec.as_dict() for dec in self._decorators], - } - - @classmethod - def from_dict(cls, d): - pseudos = [] - for p in d['pseudos']: - pseudos.append(Pseudo.from_file(p['filepath'])) - - dtsets = d['datasets'] - abiinput = cls(pseudos, ndtset=dtsets[0]['ndtset'], decorators=d["decorators"]) - - for n, ds in enumerate(dtsets): - abiinput.set_vars(dtset=n, **ds) - - return abiinput - - def new_from_decorators(self, decorators): - """ - This function receives a list of :class:`AbinitInputDecorator` objects or just a single object, - applyes the decorators to the input and returns a new :class:`AbiInput` object. - self is not changed. - """ - if not isinstance(decorators, (list, tuple)): decorators = [decorators] - - # Deepcopy only at the first step to improve performance. - inp = self - for i, dec in enumerate(decorators): - inp = dec(inp, deepcopy=(i == 0)) - - return inp - - def validate(self): - """ - Run ABINIT in dry mode to validate the input file. - - Return: - `namedtuple` with the following attributes: - - retcode: Return code. 0 if OK. - log_file: log file of the Abinit run, use log_file.read() to access its content. - stderr_file: stderr file of the Abinit run. use stderr_file.read() to access its content. - - Raises: - `RuntimeError` if executable is not in $PATH. - """ - task = AbinitTask.temp_shell_task(inp=self) - retcode = task.start_and_wait(autoparal=False, exec_args=["--dry-run"]) - return dict2namedtuple(retcode=retcode, log_file=task.log_file, stderr_file=task.stderr_file) - - - -class MappingMixin(collections.Mapping): - """ - Mixin class implementing the mapping protocol. Useful to avoid boilerplate code if you want - to define a object that behaves as a Mapping but without inheriting from dict or similar classes - because you don't want to expose/support all the methods of dict. - - Client code must initialize a Mapping object either in new or in init and bound it to _mapping_mixin_ - The implemention of the Mapping interface is delegated to _mapping_mixin_ - - .. Example: - - >>> class Foo(MappingMixin): - ... def __init__(self, attr, **kwargs): - ... self._mapping_mixin_ = kwargs - ... self.attr = attr - >>> obj = Foo(attr=1, spam=2) - >>> obj.attr, obj["spam"] - (1, 2) - >>> obj.pop("spam") - 2 - >>> len(obj), "spam" in obj - (0, False) - """ - def __len__(self): - return len(self._mapping_mixin_) - - def __iter__(self): - return self._mapping_mixin_.__iter__() - - def __getitem__(self, key): - return self._mapping_mixin_[key] - - def __setitem__(self, key, value): - self._mapping_mixin_[key] = value - - def __contains__(self, key): - return key in self._mapping_mixin_ - - def keys(self): - return self._mapping_mixin_.keys() - - def items(self): - return self._mapping_mixin_.items() - - def get(self, value, default=None): - try: - return self[value] - except KeyError: - return default - - def pop(self, k, *d): - """ - D.pop(k[,d]) -> v, remove specified key and return the corresponding value. - If key is not found, d is returned if given, otherwise KeyError is raised - """ - if d: - return self._mapping_mixin_.pop(k, d[0]) - else: - return self._mapping_mixin_.pop(k) - - def update(self, e, **f): - """ - D.update([E, ]**F) -> None. Update D from dict/iterable E and F. - If E present and has a .keys() method, does: for k in E: D[k] = E[k] - If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v - In either case, this is followed by: for k in F: D[k] = F[k] - """ - self._mapping_mixin_.update(e, **f) - - - -class Dataset(MappingMixin, Has_Structure): - """ - This object stores the ABINIT variables for a single dataset. - """ - # TODO this should be the "actual" input file - Error = AbinitInputError - - def __init__(self, index, dt0, *args, **kwargs): - self._mapping_mixin_ = collections.OrderedDict(*args, **kwargs) - self._index = index - self._dt0 = dt0 - - def __repr__(self): - return "<%s at %s>" % (self.__class__.__name__, id(self)) - - def __str__(self): - return self.to_string() - - @property - def vars(self): - return self._mapping_mixin_ - - @property - def index(self): - """The index of the dataset within the input.""" - return self._index - - @property - def dt0(self): - return self._dt0 - - @property - def global_vars(self): - """Copy of the dictionary with the global variables.""" - return self.dt0.vars.copy() - - @property - def allvars(self): - """ - Dictionay with the variables of the dataset + the global variables. - Variables local to the dataset overwrite the global values (if any). - """ - all_vars = self.global_vars - all_vars.update(self.vars) - return all_vars.copy() - - def deepcopy(self): - """Deepcopy of the `Dataset`""" - return copy.deepcopy(self) - - #@abc.property - #def runlevel(self): - # """String defining the Runlevel. See _runl2optdriver.""" - # Mapping runlevel --> optdriver variable - #_runl2optdriver = { - # "scf": 0, - # "nscf": 0, - # "relax": 0, - # "dfpt": 1, - # "screening": 3, - # "sigma": 4, - # "bse": 99, - #} - # # Find the value of optdriver (firt in self, then in globals finally use default value. - # optdriver = self.get("optdriver") - # if optdriver is None: optdriver = self.dt0.get("optdriver") - # if optdriver is None: optdriver = 0 - - # # At this point we have to understand the type of calculation. - - def set_mnemonics(self, boolean): - """True if mnemonics should be printed""" - self._mnemonics = bool(boolean) - - @property - def mnemonics(self): - """Return True if mnemonics should be printed""" - try: - return self._mnemonics - except AttributeError: - return False - - def to_string(self, sortmode=None, post=None): - """ - String representation. - - Args: - sortmode: "a" for alphabetical order, None if no sorting is wanted - post: String that will be appended to the name of the variables - Note that post is usually autodetected when we have multiple datatasets - It is mainly used when we have an input file with a single dataset - so that we can prevent the code from adding "1" to the name of the variables - (In this case, indeed, Abinit complains if ndtset=1 is not specified - and we don't want ndtset=1 simply because the code will start to add - _DS1_ to all the input and output files. - """ - lines = [] - app = lines.append - - if self.comment: - app("# " + self.comment.replace("\n", "\n#")) - - if post is None: - post = "" if self.index == 0 else str(self.index) - else: - post = post - - if sortmode is None: - # no sorting. - keys = self.keys() - elif sortmode == "a": - # alphabetical order. - keys = sorted(self.keys()) - else: - raise ValueError("Unsupported value for sortmode %s" % str(sortmode)) - - with_mnemonics = self.mnemonics - if with_mnemonics: - var_database = get_abinit_variables() - - for var in keys: - value = self[var] - # Do not print NO_MULTI variables except for dataset 0. - if self.index != 0 and var in _ABINIT_NO_MULTI: - continue - - # Print ndtset only if we really have datasets. - # This trick prevents abinit from adding DS# to the output files. - # thus complicating the creation of workflows made of separated calculations. - if var == "ndtset" and value == 1: - continue - - if with_mnemonics: - v = var_database[var] - app("# <" + v.definition + ">") - - varname = var + post - variable = InputVariable(varname, value) - app(str(variable)) - - return "\n".join(lines) - - @property - def comment(self): - try: - return self._comment - except AttributeError: - return None - - def set_comment(self, comment): - """Set a comment to be included at the top of the file.""" - self._comment = comment - - def set_var(self, varname, value): - """Set a the value of a variable.""" - # Check if varname is in the internal database. - if not is_abivar(varname): - raise self.Error("varname %s is not a valid ABINIT variable\n." - "Modify abipy/htc/abinit_vars.json" % varname) - - # Debugging section. - if False and varname in self: - try: - iseq = (self[varname] == value) - iseq = np.all(iseq) - except ValueError: - # array like. - iseq = np.allclose(self[varname], value) - except: - iseq = False - - if not iseq: - msg = "%s is already defined with a different value:\nOLD:\n %s,\nNEW\n %s" % ( - varname, str(self[varname]), str(value)) - logger.debug(msg) - - self[varname] = value - - # Handle no_multi variables. - if varname in _ABINIT_NO_MULTI and self.index != 0: - - if varname in self.dt0: - glob_value = np.array(self.dt0[varname]) - isok = np.all(glob_value == np.array(value)) - if not isok: - err_msg = "NO_MULTI variable: dataset 0: %s, dataset %d: %s" % ( - str(glob_value), self.index, str(value)) - raise self.Error(err_msg) - else: - self.dt0.set_var(varname, value) - - # Alias - set_variable = set_var - set_variable = deprecated(replacement=set_var)(set_variable) - - def set_vars(self, *args, **kwargs): - """Set the value of the variables provied in the dictionary **kwargs""" - kwargs.update(dict(*args)) - for varname, varvalue in kwargs.items(): - self.set_var(varname, varvalue) - - # Alias - set_variables = set_vars - set_variables = deprecated(replacement=set_vars)(set_variables) - - def remove_vars(self, keys): - """Remove the variables listed in keys.""" - for key in list_strings(keys): - if key not in self: - raise KeyError("key: %s not in self:\n %s" % (key, list(self.keys()))) - self.pop(key) - - # Alias - remove_variables = remove_vars - remove_variables = deprecated(replacement=remove_vars)(remove_variables) - - @property - def structure(self): - """Returns the :class:`Structure` associated to this dataset.""" - # TODO: Avoid calling Structure.from_abivars, find a way to cache the object and invalidate it. - return Structure.from_abivars(self.allvars) - # Cannot use lazy properties here because we may want to change the structure - - #if hasattr(self, "_structure") and self._structure is None: - # return self._structure - #try: - # return self._structure - #except AttributeError: - # structure = Structure.from_abivars(self.allvars) - # self.set_structure(structure) - # return self._structure - - def set_structure(self, structure): - structure = Structure.as_structure(structure) - - self._structure = structure - if structure is None: return - - self.set_vars(**structure.to_abivars()) - - # Helper functions to facilitate the specification of several variables. - def set_kmesh(self, ngkpt, shiftk, kptopt=1): - """ - Set the variables for the sampling of the BZ. - - Args: - ngkpt: Monkhorst-Pack divisions - shiftk: List of shifts. - kptopt: Option for the generation of the mesh. - """ - shiftk = np.reshape(shiftk, (-1,3)) - - self.set_vars(ngkpt=ngkpt, kptopt=kptopt, nshiftk=len(shiftk), shiftk=shiftk) - - def set_autokmesh(self, nksmall, kptopt=1): - """ - Set the variables (ngkpt, shift, kptopt) for the sampling of the BZ. - - Args: - nksmall: Number of k-points used to sample the smallest lattice vector. - kptopt: Option for the generation of the mesh. - """ - shiftk = self.structure.calc_shiftk() - - self.set_vars(ngkpt=self.structure.calc_ngkpt(nksmall), kptopt=kptopt, - nshiftk=len(shiftk), shiftk=shiftk) - - def set_kpath(self, ndivsm, kptbounds=None, iscf=-2): - """ - Set the variables for the computation of the band structure. - - Args: - ndivsm: Number of divisions for the smallest segment. - kptbounds: k-points defining the path in k-space. - If None, we use the default high-symmetry k-path defined in the pymatgen database. - """ - if kptbounds is None: kptbounds = self.structure.calc_kptbounds() - kptbounds = np.reshape(kptbounds, (-1,3)) - - self.set_vars(kptbounds=kptbounds, kptopt=-(len(kptbounds)-1), ndivsm=ndivsm, iscf=iscf) - - def set_kptgw(self, kptgw, bdgw): - """ - Set the variables (k-points, bands) for the computation of the GW corrections. - - Args - kptgw: List of k-points in reduced coordinates. - bdgw: Specifies the range of bands for the GW corrections. - Accepts iterable that be reshaped to (nkptgw, 2) - or a tuple of two integers if the extrema are the same for each k-point. - """ - kptgw = np.reshape(kptgw, (-1,3)) - nkptgw = len(kptgw) - if len(bdgw) == 2: bdgw = len(kptgw) * bdgw - - self.set_vars(kptgw=kptgw, nkptgw=nkptgw, bdgw=np.reshape(bdgw, (nkptgw, 2))) - - -class LujForSpecie(collections.namedtuple("LdauForSpecie", "l u j unit")): - """ - This object stores the value of l, u, j used for a single atomic specie. - """ - def __new__(cls, l, u, j, unit): - """ - Args: - l: Angular momentum (int or string). - u: U value - j: J Value - unit: Energy unit for u and j. - """ - l = l - u, j = Energy(u, unit), Energy(j, unit) - return super(cls, LujForSpecie).__new__(cls, l, u, j, unit) - - -class LdauParams(object): - """ - This object stores the parameters for LDA+U calculations with the PAW method - It facilitates the specification of the U-J parameters in the Abinit input file. - (see `to_abivars`). The U-J operator will be applied only on the atomic species - that have been selected by calling `lui_for_symbol`. - - To setup the Abinit variables for a LDA+U calculation in NiO with a - U value of 5 eV applied on the nickel atoms: - - .. code-block:: python - - luj_params = LdauParams(usepawu=1, structure=nio_structure) - # Apply U-J on Ni only. - u = 5.0 - luj_params.luj_for_symbol("Ni", l=2, u=u, j=0.1*u, unit="eV") - - print(luj_params.to_abivars()) - """ - def __init__(self, usepawu, structure): - """ - Arg: - usepawu: ABINIT variable `usepawu` defining the LDA+U method. - structure: :class:`Structure` object. - """ - self.usepawu = usepawu - self.structure = structure - self._params = {} - - @property - def symbols_by_typat(self): - return [specie.symbol for specie in self.structure.types_of_specie] - - def luj_for_symbol(self, symbol, l, u, j, unit="eV"): - """ - Args: - symbol: Chemical symbol of the atoms on which LDA+U should be applied. - l: Angular momentum. - u: Value of U. - j: Value of J. - unit: Energy unit of U and J. - """ - if symbol not in self.symbols_by_typat: - raise ValueError("Symbol %s not in symbols_by_typat:\n%s" % (symbol, self.symbols_by_typat)) - - if symbol in self._params: - raise ValueError("Symbol %s is already present in LdauParams! Cannot overwrite:\n" % symbol) - - self._params[symbol] = LujForSpecie(l=l, u=u, j=j, unit=unit) - - def to_abivars(self): - """Returns a dict with the Abinit variables.""" - lpawu, upawu, jpawu = [], [], [] - - for symbol in self.symbols_by_typat: - p = self._params.get(symbol, None) - - if p is not None: - l, u, j = p.l, p.u.to("eV"), p.j.to("eV") - else: - l, u, j = -1, 0, 0 - - lpawu.append(int(l)) - upawu.append(float(u)) - jpawu.append(float(j)) - - # convert upawu and jpaw to string so that we can use - # eV unit in the Abinit input file (much more readable). - return dict( - usepawu=self.usepawu, - lpawu=" ".join(map(str, lpawu)), - upawu=" ".join(map(str, upawu)) + " eV", - jpawu=" ".join(map(str, jpawu)) + " eV") - - -class LexxParams(object): - """ - This object stores the parameters for local exact exchange calculations with the PAW method - It facilitates the specification of the LEXX parameters in the Abinit input file. - (see `to_abivars`). The LEXX operator will be applied only on the atomic species - that have been selected by calling `lexx_for_symbol`. - - To perform a LEXX calculation for NiO in which the LEXX is computed only for the l=2 - channel of the nickel atoms: - - .. code-block:: python - - lexx_params = LexxParams(nio_structure) - lexx_params.lexx_for_symbol("Ni", l=2) - - print(lexc_params.to_abivars()) - """ - def __init__(self, structure): - """ - Arg: - structure: :class:`Structure` object. - """ - self.structure = structure - self._lexx_for_symbol = {} - - @property - def symbols_by_typat(self): - return [specie.symbol for specie in self.structure.types_of_specie] - - def lexx_for_symbol(self, symbol, l): - """ - Enable LEXX for the given chemical symbol and the angular momentum l - - Args: - symbol: Chemical symbol of the atoms on which LEXX should be applied. - l: Angular momentum. - """ - if symbol not in self.symbols_by_typat: - err_msg = "Symbol %s not in symbols_by_typat:\n%s" % (symbol, self.symbols_by_typat) - raise ValueError(err_msg) - - if symbol in self._lexx_for_symbol: - raise ValueError("Symbol %s is already present in LdauParams! Cannot overwrite:" % symbol) - - self._lexx_for_symbol[symbol] = l - - def to_abivars(self): - """Returns a dict with the Abinit variables.""" - lexx_typat = [] - - for symbol in self.symbols_by_typat: - l = self._lexx_for_symbol.get(symbol, -1) - lexx_typat.append(int(l)) - - return dict(useexexch=1, lexexch=" ".join(map(str, lexx_typat))) - - -def input_gen(inp, **kwargs): - """ - This function receives an :class:`AbiInput` and generates - new inputs by replacing the variables specified in kwargs. - - Args: - inp: :class:`AbiInput` file. - kwargs: keyword arguments with the values used for each variable. - - .. code-block:: python - - gs_inp = call_function_to_generate_initial_template() - - # To generate two input files with different values of ecut: - for inp_ecut in input_gen(gs_inp, ecut=[10, 20]): - print("do something with inp_ecut %s" % inp_ecut) - - # To generate four input files with all the possible combinations of ecut and nsppol: - for inp_ecut in input_gen(gs_inp, ecut=[10, 20], nsppol=[1, 2]): - print("do something with inp_ecut %s" % inp_ecut) - """ - for new_vars in product_dict(kwargs): - new_inp = inp.deepcopy() - # Remove the variable names to avoid annoying warnings if the variable is overwritten. - new_inp.remove_vars(new_vars.keys()) - new_inp.set_vars(**new_vars) - - yield new_inp - - -def product_dict(d): - """ - This function receives a dictionary d where each key defines a list of items or a simple scalar. - It constructs the Cartesian product of the values (equivalent to nested for-loops), - and returns a list of dictionaries with the values that would be used inside the loop. - - >>> d = OrderedDict([("foo", [2, 4]), ("bar", 1)]) - >>> product_dict(d) == [OrderedDict([('foo', 2), ('bar', 1)]), OrderedDict([('foo', 4), ('bar', 1)])] - True - >>> d = OrderedDict([("bar", [1,2]), ('foo', [3,4])]) - >>> product_dict(d) == [{'bar': 1, 'foo': 3}, - ... {'bar': 1, 'foo': 4}, - ... {'bar': 2, 'foo': 3}, - ... {'bar': 2, 'foo': 4}] - True - - .. warning: - - Dictionaries are not ordered, therefore one cannot assume that - the order of the keys in the output equals the one used to loop. - If the order is important, one should pass a :class:`OrderedDict` in input. - """ - keys, vals = d.keys(), d.values() - - # Each item in vals must be iterable. - values = [] - - for v in vals: - if not isinstance(v, collections.Iterable): v = [v] - values.append(v) - - # Build list of dictionaries. Use ordered dicts so that - # we preserve the order when d is an OrderedDict. - vars_prod = [] - - for prod_values in itertools.product(*values): - dprod = OrderedDict(zip(keys, prod_values)) - vars_prod.append(dprod) - - return vars_prod diff --git a/abipy/htc/inputfile.py b/abipy/htc/inputfile.py deleted file mode 100644 index 01e42726..00000000 --- a/abipy/htc/inputfile.py +++ /dev/null @@ -1,394 +0,0 @@ -from __future__ import print_function, division #, unicode_literals - -import string -import os.path -import warnings -import numpy as np - -from os import makedirs -from os.path import dirname, abspath, exists -from collections import OrderedDict -from copy import deepcopy - -from .utils import flatten, listify, is_number, is_iter -from .variable import InputVariable, SpecialInputVariable, _UNITS - -__all__ = [ - 'InputFile', - 'VariableBlock', -] - - -_input_variable_blocks = OrderedDict(( -('Datasets', ''' - ndtset jdtset udtset - '''), -('Basis set', ''' - ecut ecutsm - '''), -('Bands', ''' - nband nbdbuf - '''), -('k-point grid', ''' - kptopt nkpt kpt ngkpt kptrlatt - nshiftk shiftk kptbounds kptns - '''), -('Models', ''' - ixc ppmodel ppmfreq usepawu upawu jpawu - '''), -('PAW options', ''' - bxctmindg dmatpawu dmatpuopt dmatudiag iboxcut - jpawu lpawu lexexch mqgriddg ngfftdg - pawcpxocc pawcross pawecutdg pawfatbnd - pawlcutd pawlmix pawmixdg pawnhatxc pawnphi - pawntheta pawnzlm pawoptmix pawovlp - pawprtden pawprtdos pawprtvol pawprtwf - pawspnorb pawstgylm pawsushat pawusecp - pawxcdev prtcs prtefg prtfc prtnabla - ptcharge quadmom spnorbscl usedmatpu upawu - useexexch usepawu usexcnhat - '''), -('SCF procedure', ''' - iscf nstep nline tolvrs tolwfr - toldfe toldff tolimg tolmxf tolrff - '''), -('KSS generation', ''' - kssform nbandkss - '''), -('GW procedure', ''' - optdriver gwcalctyp spmeth nkptgw kptgw - bdgw nqptdm qptdm - '''), -('GW param', ''' - ecuteps ecutsigx ecutwfn nomegasf - nfreqim nfreqre freqremax npweps rhoqpmix - '''), -('GW options', ''' - userre awtr symchi gwpara symsigma gwmem fftgw - '''), -('Structural optimization', ''' - amu bmass delayperm diismemory dilatmx dtion dynimage - ecutsm friction fxcartfactor getcell getxcart getxred - goprecon goprecprm iatcon iatfix iatfixx iatfixy iatfixz - imgmov ionmov istatimg mdtemp mdwall natfix natfixx - natfixy natfixz natcon nconeq nimage nnos noseinert - ntime ntimimage optcell pimass pitransform prtatlist qmass - random_atpos restartxf signperm strfact strprecon strtarget - tolimg tolmxf vel vis wtatcon - '''), -('Response function', ''' - bdeigrf elph2_imagden esmear frzfermi - ieig2rf mkqmem mk1mem prepanl prepgkk - prtbbb rfasr rfatpol rfddk rfdir rfelfd - rfmeth rfphon rfstrs rfuser rf1atpol rf1dir - rf1elfd rf1phon rf2atpol rf2dir rf2elfd - rf2phon rf3atpol rf3dir rf3elfd rf3phon - sciss smdelta td_maxene td_mexcit - '''), -('Wannier 90', ''' - w90iniprj w90prtunk - '''), -('Parallelisation', ''' - gwpara localrdwf ngroup_rf npband npfft - npimage npkpt npspinor paral_kgb - paral_rf use_gpu_cuda - '''), -('Unit cell', ''' - acell angdeg rprim ntypat znucl natom typat xred xcart - '''), -('Printing', ''' - prtvol enunit - '''), -('Files', ''' - irdddk irdden ird1den irdqps irdkss irdscr - irdsuscep irdwfk irdwfq ird1wf getcell - getddk getden getgam_eig2nkq getkss getocc - getqps getscr getsuscep getvel getwfk - getwfq getxcart getxred get1den get1wf - '''), -)) - - -# =========================================================================== # - - -class VariableBlock(list): - """A block of abinit variables.""" - - def __init__(self, title, register=''): - - # The block title - self.title = title - - # A register of all possible input variable. - if isinstance(register, str): - self.register = register.split() - else: - self.register = list(register) - - def clear(self): - del self[:] - - def __str__(self): - lines = ['#== {0} ==#'.format(self.title)] - for variable in sorted(self): - svar = str(variable) - if svar: - lines.append(svar) - return '\n'.join(lines) - - -# =========================================================================== # - - -class InputFile(object): - """ - Abinit input file. - - .. code-block:: python - - >> f = InputFile('myfile.in') - >> f.read('otherfile.in') - >> - >> f.ndtset = 4 # Variables are set with integers, - >> f.jdtset = [1,2,3,4] # or lists. - >> - >> f.ecut = 25. # Here is a floats. - >> f.ecut = '25.' # But using strings is always possible. - >> - >> f.tolwfr = 1e-20 # Scientific notation - >> f.tolwfr = '1d-20' # is translated like this. - >> - >> f.rprim = [[0.0,0.5,0.5], [0.5,0.0,0.5], [0.5,0.5,0.0]] # These three lines - >> f.rprim = [ 0.0,0.5,0.5 , 0.5,0.0,0.5 , 0.5,0.5,0.0] # produce exactly - >> f.rprim ='\\n 0.0 0.5 0.5 \\n 0.5 0.0 0.5 \\n 0.5 0.5 0.0' # the same result. - >> - >> f.nband4 = 300 # Dataset-specific variable. - >> - >> f.udtset = '2 3' # We will have to remember that: - >> f.nband1__s = 100 # ':' <==> '__s' (start) - >> f.nband1__i = 50 # '+' <==> '__i' (increment) - >> f.ecut__a2 = 20.0 # '?' <==> '__a' (any) - >> - >> f.istwfk = '*1' # In some cases, string is the only way! - >> - >> f.tolvrs = None # Unset a variable. It won't appear in the file. - >> - >> f.fuzzy = 10 # But non-existent variables are written anyway! - >> - >> f.ecut = '100 eV' # This is OK but not recommended since variables - >> f.ecut = [100, 'eV'] # are converted to default units when read from file. - >> - >> f.set_comment('''This is a comment. - .. It will be printed at the top of the file.''') - >> - >> f.write() - - See also the function :func:`~abipy.htc.InputFile.set_variables`. - return self.variables - """ - - _blocks = _input_variable_blocks - - def __init__(self, name='abinit.in'): - - self.__dict__['name'] = str(name) - self.__dict__['variables'] = dict() - self.__dict__['_comment'] = str() - self.__dict__['variables_blocks'] = list() - - for (name, register) in _input_variable_blocks.items(): - self.variables_blocks.append(VariableBlock(name, register)) - self.variables_blocks.append(VariableBlock('Other')) - - def __str__(self): - lines = list() - - # Comments - if self.comment: - lines.append(self.comment) - lines.append('') - - # Clear blocks - for block in self.variables_blocks: - block.clear() - - # Sort variables in blocks - for name, value in self.variables.items(): - variable = SpecialInputVariable(name, value) - placed = False - for block in self.variables_blocks: - if variable.basename in block.register: - block.append(variable) - placed = True - break - if not placed: - self.variables_blocks[-1].append(variable) - - # Make the string - for block in self.variables_blocks: - if block: - lines.append(str(block)) - lines.append('') - block.clear() - - return '\n'.join(lines) - - def __setattr__(self, name, value): - """ - F.__setattr__('name', value) <==> F.name = value - - Declare a variable in the internal dictionary. - """ - self.set_variable(name, value) - - def set_name(self, name): - """Set the name of the file.""" - self.__dict__['name'] = name - - @property - def path(self): - """The absolute path.""" - return abspath(self.name) - - @property - def exists(self): - """True if self.path exists.""" - return os.path.exists(self.path) - - @property - def comment(self): - return self._comment - - def set_comment(self, comment): - """Set a comment to be included at the top of the file.""" - lines = [ '# ' + l.lstrip('#').strip() for l in comment.splitlines() ] - self.__dict__['_comment'] = '\n'.join(lines) - - def write(self, name=None): - """Write the inputs to the file.""" - if name is None: - name = self.name - - if not exists(dirname(self.name)): - makedirs(dirname(self.name)) - - with open(name, 'w') as f: - f.write(str(self)) - - def clear(self): - """Clear variables.""" - self.variables.clear() - for block in self.variables_blocks: - block.clear() - - def read_string(self, bigstring): - """Initialize all variables from a string.""" - - # Split the big string into parts - parts = list() - for line in bigstring.splitlines(): - line = line.replace('=', ' ').split('#')[0].strip() - parts.extend(line.split()) - - # Make a list of variable string declaration - var_list, var_string = list(), '' - for part in parts: - if not part: - continue - if part[0].isalpha() and part not in _UNITS: - if var_string: - var_list.append(var_string) - var_string = '' - var_string += ' ' + part - if var_string: - var_list.append(var_string) - - # Initialize all variables. - for var_string in var_list: - variable = SpecialInputVariable.from_str(var_string) - self.variables[variable.name] = variable.get_value() - - @classmethod - def from_str(cls, bigstring): - """Initialize from a string.""" - inputfile = cls() - inputfile.read_string(bigstring) - - def read(self, file): - """ - Reads the content of an input file and store the variables in the - internal dictionary with the proper type. Comments are thrown away. - """ - self.clear() - with open(file, 'r') as f: - self.read_string(f.read()) - - def set_variable(self, name, value): - """Set a single variable.""" - self.variables[name] = value - - def set_variables(self, variables=None, dataset=0, **kwargs): - """ - Sets variables by providing a dictionary, or expanding a dictionary, - and possibly append them by a dataset index. - - .. code-block:: python - - >> kpoint_grid_shifted = { - >> 'kptopt' : 1, - >> 'ngkpt' : 3*[4], - >> 'nshiftk' : 4, - >> 'shiftk' : [[0.5,0.5,0.5], - >> [0.5,0.0,0.0], - >> [0.0,0.5,0.0], - >> [0.0,0.0,0.5]],} - >> - >> kpoint_grid_unshifted = { - >> 'kptopt' : 1, - >> 'ngkpt' : 3*[4], - >> 'nshiftk' : 1, - >> 'shiftk' : [0,0,0],} - >> - >> cell = { - >> 'ntypat' : 1 - >> 'znucl' : 6.0 - >> 'natom' : 2 - >> 'typat' : [1, 1] - >> 'xred' : [[0,0,0],[0.25,0.25,0.25]] - >> 'acell' : 3*[6.9] - >> 'rprim' : [[0.0,0.5,0.5], - >> [0.5,0.0,0.5], - >> [0.5,0.5,0.0]]} - >> - >> f = InputFile() - >> f.set_variables(ndtset=3, ecut=4.0, ecutsm=0.5) - >> - >> f.set_variables(cell) # These two lines - >> f.set_variables(**cell) # are equivalent. - >> - >> # Here we append a dataset index at the end of all variables. - >> f.set_variables(kpoint_grid_shifted, dataset=1) - >> f.set_variables(kpoint_grid_unshifted, dataset=[2, 3]) - >> - >> f.write('myfile.in') # The name was not set at initialization. - """ - if variables is None: - variables = dict() - variables.update(kwargs) - - if not dataset: - dataset = [''] - - for ds in listify(dataset): - for (key, val) in variables.items(): - newkey = key + str(ds) - self.set_variable(newkey, val) - - def get_variables(self): - """Return a dictionary of the variables.""" - return deepcopy(self.variables) - - def get_variable(self, variable): - """Return the value of a variable, or None if it is not set.""" - return self.variables.get(variable) diff --git a/abipy/htc/jobfile.py b/abipy/htc/jobfile.py deleted file mode 100644 index 25bc4197..00000000 --- a/abipy/htc/jobfile.py +++ /dev/null @@ -1,806 +0,0 @@ -from __future__ import print_function, division #, unicode_literals - -from os import makedirs -from os.path import basename, dirname, join, abspath, exists, realpath -from .utils import listify - -__all__ = [ - 'JobFile', - 'PBSJobFile', - 'SGEJobFile', - 'SlurmJobFile', - 'MoabJobFile', -] - -# =========================================================================== # - - -class JobFile(object): - """ - The job file is organized as follow:: - - #!/bin/csh # 1) Shell specification. - # - #PBS -N jobname # 2) Submission commands. - #PBS -l walltime=48:00:00 # Depends on the subclass used. - #PBS -l nodes=4:ppn=12 - - set MPIRUN="mpiexec" # 3) Declarations. - set EXECUTABLE=/path/to/executable # These are also properties - set INPUT=calculation.in # of the jobfile object. - set LOG=calculation.log - - module load intel-compilers # 4) Modules. - module load MPI/Intel/mvapich2 - - cd ${PBS_O_WORKDIR} # 5) Lines before execution. - limit stacksize unlimited - - $MPIRUN $EXECUTABLE < $INPUT > $LOG # 6) Execution line. - - echo "Job done!" # 7) Lines after execution. - date - - .. attributes: - - shell: - The shell binary to be used. E.g. '/bin/csh'. - Default is '/bin/bash'. - mpirun: - The mpi runner. E.g. 'mpiexec -npernode 6'. - Default is none. - executable: - The binary to be executed. Default is abinit. - bindir: - The directory in which to look for binaries. Default is none. - input: - The input file to feed in the executable as the standard input. - Mandatory. - log: - The file into which the standard output is redirected. - Default is 'log'. - stderr: - The file into which the standard error is redirected. - Default is 'stderr'. - modules: - The modules which will be loaded with 'module load'. - Default is none. - lines_before: - Lines before the main execution. - Default is none. - lines_after: - Lines after the main execution. - Default is none. - other_lines: - Other lines your job submission script would like to have. - Must be preceded by the approbriate tag (#!, #PBS). - Default is none. - submission_command: - The command which should be used to launch the job. - E.g. 'qsub', 'bqsub', 'sbatch'. - Default depends on the job type. - """ - _executable = 'abinit' - _mpirun = '' - _modules = list() - _other_lines = list() - _lines_before = list() - _lines_after = list() - - def __init__(self, name='job.sh', **kwargs): - - # Name - self.name = name - self.absdir = realpath(dirname(self.name)) - self.absname = realpath(self.name) - - # Shell - self._shell = '/bin/bash' - - # Execution lines - self.input = '' - self.log = 'log' - self.stderr = 'stderr' - self.executable = 'abinit' - self.bindir = '' - self.mpirun = '' - - # Modules - self.modules = list() - - # Other lines - self.other_lines = list() - self.lines_before = list() - self.lines_after = list() - - # Command used to submit the job - self.submission_command = 'qsub' - - # Set attributes - for (arg, val) in kwargs.items(): - try: - getattr(self, 'set_' + arg)(val) - except: - pass - - def __str__(self): - lines = [] - def app(line): - if '__iter__' in dir(line): - lines.extend(line) - else: - lines.append(line) - - # Shell line - app('#!' + self.shell) - app('') - - # Submission instructions - app(self._get_command_lines()) - - # Other submission inscrutions - app(self.other_lines) - app('') - - # Declarations - for (key, value) in [('MPIRUN', self.mpirun), - ('EXECUTABLE', self.executable), - ('INPUT', self.input), - ('LOG', self.log), - ('STDERR', self.stderr)]: - app(self._declare(key, value)) - app('') - - # Modules - for module in self.modules: - app('module load ' + module) - app('') - - # Lines before execution - app(self.lines_before) - app('') - - # Execution lines - if 'csh' in self.shell: - execline = "($MPIRUN $EXECUTABLE < $INPUT > $LOG) >& $STDERR" - else: - execline = "$MPIRUN $EXECUTABLE < $INPUT > $LOG 2> $STDERR" - app(execline) - app('') - - # Lines after execution - app(self.lines_after) - app('') - - return "\n".join(lines) - - def write(self, name=None): - """Write the file.""" - if name is None: - name = self.name - - if self.dirname and not exists(self.dirname): - makedirs(self.dirname) - - with open(name, 'w') as f: - f.write(str(self)) - - def _get_command_lines(self): - """Return the lines specifying instructions for job submission.""" - return list() - - def _declare(self, key, val): - """Return a lines setting a variable.""" - if 'csh' in self.shell: - declare = 'set ' - else: - declare = '' - return declare + key + '=' + val - - def _set_property(self, name, *args, **kwargs): - """Set a property through the corresponding set_ function.""" - return getattr(self, 'set_' + key)(*args, **kwargs) - - @property - def dirname(self): - """The directory containing the file.""" - return dirname(self.name) - - @property - def path(self): - """The path of the file.""" - return abspath(self.name) - - @property - def basename(self): - """The base name of the file.""" - return basename(self.name) - - @classmethod - def properties(cls): - """Return the list of properties with a set function.""" - funcs = filter(lambda s: s.startswith('set_'), dir(cls)) - return [ f.split('set_', 1)[-1] for f in funcs ] - - @property - def executable(self): - return join(self.bindir, self._executable) - - @executable.setter - def executable(self, executable): - self._executable = basename(executable) - if basename(executable) != executable: - self.set_bindir(dirname(executable)) - - @property - def mpirun(self): - return '"' + self._mpirun.strip('"').strip("'") + '"' - - @mpirun.setter - def mpirun(self, mpirun): - self._mpirun = str(mpirun) - - @property - def shell(self): - return self._shell - - @shell.setter - def shell(self, shell): - if shell == basename(shell): - self._shell = join('/bin', shell) - else: - self._shell = abspath(shell) - - @property - def modules(self): - return self._modules - - @modules.setter - def modules(self, modules): - self._modules = listify(modules) - - @property - def other_lines(self): - return self._other_lines - - @other_lines.setter - def other_lines(self, lines): - self._other_lines = listify(lines) - - @property - def lines_before(self): - return self._lines_before - - @lines_before.setter - def lines_before(self, lines): - self._lines_before = listify(lines) - - @property - def lines_after(self): - return self._lines_after - - @lines_after.setter - def lines_after(self, lines): - self._lines_after = listify(lines) - - def set_shell(self, shell): - """ - Sets the shell type. The argument can either be an absolute path, - or just the shell type e.g. bash, csh, tcsh, in which case - the executable is assumed to be located in /bin/. - The shell also determine how a variable is declared. - """ - self.shell = shell - - def set_mpirun(self, mpirun): - """ - Set the mpi runner to execute the program. - E.g. 'mpiexec -npernode 6', 'mpirun -np 12', ''. - """ - self.mpirun = mpirun - - def set_bindir(self, bindir): - """Set the directory for binaries (abinit, mrgscr...).""" - self.bindir = realpath(bindir) - - def set_executable(self, executable): - """Set the executable to use.""" - self.executable = executable - - def set_input(self, input): - """Set the input file for the main executable.""" - self.input = input - - def set_log(self, log): - """Set the log file to collect standard output of the executable.""" - self.log = log - - def set_stderr(self, stderr): - """Set the log file to collect standard output of the executable.""" - self.stderr = stderr - - def set_modules(self, *modules): - """Set one or many modules to be loaded.""" - self.modules = modules - - def set_other_lines(self, *lines): - """Set other command lines for the batch submission system.""" - self.other_lines = lines - - def set_lines_before(self, *lines): - """Set one or many lines to be executed before the main execution.""" - self.lines_before = lines - - def set_lines_after(self, *lines): - """Set one or many lines to be executed after the main execution.""" - self.lines_after = lines - - def set_submission_command(self, command): - """ - Sets the command used for job submission, - e.g. qsub, bqsub, sbatch, ... - """ - self.submission_command = command - -# =========================================================================== # - - -class PBSJobFile(JobFile): - """ - Portable Batch System. - - .. attributes: - - jobname: - Name of the job. - runtime: - Maximum time for the job. - nodes: - Number of nodes on which to run the job. - ppn: - Number of processors per node. - memory: - Memory per node. E.g. '48G'. - queue: - The queue to which the job is submitted. - mail: - The mail to which a notification will be sent. - mail_options: - The conditions under which a mail will be sent. - E.G. 'abe'. - submission_command: - default is 'qsub'. - - See man qsub for more info. - """ - __doc__ += "\n" + JobFile.__doc__ - - _command = "#PBS " - - def __init__(self, **kwargs): - - kwargs.setdefault('submission_command', 'qsub') - JobFile.__init__(self, **kwargs) - - nodes = None - def set_nodes(self, val): - self.nodes = val - - ppn = None - def set_ppn(self, val): - self.ppn = val - - memory = None - def set_memory(self, val): - self.memory = val - - runtime = None - def set_runtime(self, val): - """Either set the numer of hours, or a triplet for (hours,min,sec).""" - if isinstance(val, int): - val = [val, 0, 0] - self.runtime = val - - jobname = None - def set_jobname(self, val): - self.jobname = val - - queue = None - def set_queue(self, val): - self.queue = val - - mail = None - def set_mail(self, val): - self.mail = val - - mail_options = None - def set_mail_options(self, val): - self.mail_options = val - - def _get_command_lines(self): - """Return the lines specifying instructions for job submission.""" - lines = list() - def add(line): - lines.append(self._command + line) # + '\n') - - if self.jobname: - add('-N ' + str(self.jobname)) - - if self.runtime: - add('-l walltime={0}:{1}:{2}'.format(*self.runtime)) - - if self.nodes and self.ppn: - add('-l nodes=' + str(self.nodes) + ':ppn=' + str(self.ppn)) - - if self.memory: - add('-l mem=' + str(self.memory)) - - if self.queue: - add('-q ' + self.queue) - - if self.mail: - add('-M ' + self.mail) - - if self.mail_options: - add('-m ' + self.mail_options) - - return lines - -# =========================================================================== # - - -class SGEJobFile(JobFile): - """ - Sun Grid Engine. - - .. attributes: - - jobname: - Name of the job. - runtime: - Maximum time for the job. - nproc: - Number of processors. - queue: - The queue to which the job is submitted. - environment: - The parallel environment under which the job is ran. - memory: - The requested memory, in M. - mail: - The mail to which a notification will be sent. - mail_options: - The conditions under which a mail will be sent. - E.G. 'abe'. - submission_command: - default is 'qsub'. - - See man qsub for more info. - """ - __doc__ += "\n" + JobFile.__doc__ - - _command = "#$ " - - def __init__(self, **kwargs): - - kwargs.setdefault('submission_command', 'qsub') - JobFile.__init__(self, **kwargs) - - jobname = None - def set_jobname(self, val): - self.jobname = val - - runtime = None - def set_runtime(self, val): - """Either set the numer of hours, or a triplet for (hours,min,sec).""" - if isinstance(val, int): - val = [val, 0, 0] - self.runtime = val - - nproc = None - def set_nproc(self, val): - self.nproc = val - - queue = None - def set_queue(self, val): - self.queue = val - - environment = None - def set_environment(self, val): - self.environment = val - - memory = None - def set_memory(self, val): - self.memory = val - - mail = None - def set_mail(self, val): - self.mail = val - - mail_options = None - def set_mail_options(self, val): - self.mail_options = val - - def _get_command_lines(self): - """Return the lines specifying instructions for job submission.""" - lines = list() - def add(line): - lines.append(self._command + line) # + '\n') - - if self.jobname: - add('-N ' + str(self.jobname)) - - if self.runtime: - add('-l h_rt={0}:{1}:{2}'.format(*self.runtime)) - - if self.environment and self.nproc: - line = '-pe ' + self.environment + ' ' + str(self.nproc) - if self.memory: - line += ' -l mem=' + str(self.memory) - add(line) - - if self.queue: - add('-q ' + self.queue) - - if self.mail: - add('-M ' + self.mail) - - if self.mail_options: - add('-m ' + self.mail_options) - - return lines - -# =========================================================================== # - - -class SlurmJobFile(JobFile): - """ - Simple Linux Utility for Resource Management. - - .. Attributes: - - jobname: - Name of the job. - time: - Maximum time for the job. - ntasks: - The number of processes. - cpus_per_task: - The number of cpus per process. - mem_per_cpu: - The memory per cpu. - partition: - The partition... - mail_user: - The mail to which a notification is sent. - mail_type: - The conditions unde which to send a mail. - submission_command: - default is 'sbatch'. - """ - __doc__ += "\n" + JobFile.__doc__ - - _command = "#SBATCH " - - def __init__(self, **kwargs): - - kwargs.setdefault('submission_command', 'sbatch') - JobFile.__init__(self, **kwargs) - - jobname = None - def set_jobname(self, val): - self.jobname = val - - time = None - def set_time(self, val): - """Either set the number of hours, or a triplet for (hours,min,sec).""" - if isinstance(val, int): - val = [val, 0, 0] - self.time = val - - def set_runtime(self, val): - self.set_time(val) - - ntasks = None - def set_ntasks(self, val): - self.ntasks = val - - ntasks_per_node = None - def set_ntasks_per_node(self, val): - self.ntasks_per_node = val - - cpus_per_task = None - def set_cpus_per_task(self, val): - self.cpus_per_task = val - - mem_per_cpu = None - def set_mem_per_cpu(self, val): - self.mem_per_cpu = val - - partition = None - def set_partition(self, val): - self.partition = val - - mail_user = None - def set_mail_user(self, val): - self.mail_user = val - - mail_type = None - def set_mail_type(self, val): - self.mail_type = val - - def _get_command_lines(self): - """Return the lines specifying instructions for job submission.""" - lines = list() - def add(line): - lines.append(self._command + line) # + '\n') - - if self.jobname: - add('--job-name=' + str(self.jobname)) - - if self.time: - add('--time={0}:{1}:{2}\n'.format(*self.time)) - - if self.ntasks: - add('--ntasks=' + str(self.ntasks)) - - if self.partition: - add('--partition=' + self.partition) - - if self.ntasks_per_node: - add('--ntasks-per-node=' + str(self.ntasks_per_node)) - - if self.cpus_per_task: - add('--cpus-per-task=' + str(self.cpus_per_task)) - - if self.mem_per_cpu: - add('--mem-per-cpu=' + str(self.mem_per_cpu)) - - if self.mail_user: - add('--mail-user=' + self.mail_user) - - if self.mail_type: - add('--mail-type=' + self.mail_type) - - return lines - -# =========================================================================== # - - -class MoabJobFile(JobFile): - """ - Moab Workload Manager - - .. Attributes: - start_after: - Declares the time after which the job is eligible for execution. - Syntax: (brackets delimit optional items with the default being - current date/time): [CC][YY][MM][DD]hhmm[.SS] - account: - Defines the account associated with the job. - hold: - Put a user hold on the job at submission time. - combine: - Combine stdout and stderr into the same output file. - resources: - Defines the resources that are required by the job. - mail: - Defines the set of conditions (a=abort,b=begin,e=end) when the - server will send a mail message about the job to the user. - jobname: - Gives a user specified name to the job. - priority: - Assigns a user priority value to a job. - queue: - Run the job in the specified queue (pdebug, pbatch, etc.). A host - may also be specified if it is not the local host. - rerun: - Automatically rerun the job is there is a system failure. - env: - Specifically adds a list of environment variables that are exported - to the job. - allenv: - Declares that all environment variables in the msub environment are - exported to the batch job. - """ - __doc__ += "\n" + JobFile.__doc__ - - _command = "#MSUB " - - def __init__(self, **kwargs): - - kwargs.setdefault('submission_command', 'srun') - JobFile.__init__(self, **kwargs) - - start_after = None - def set_start_after(self, val): - self.start_after = val - - account = None - def set_account(self, val): - self.account = val - - hold = None - def set_hold(self, val): - self.hold = val - - combine = None - def set_combine(self, val): - self.combine = val - - resources = dict() - def set_resources(self, val): - self.resources = val - - mail = None - def set_mail(self, val): - self.mail = val - - jobname = None - def set_jobname(self, val): - self.jobname = val - - priority = None - def set_priority(self, val): - self.priority = val - - queue = None - def set_queue(self, val): - self.queue = val - - rerun = None - def set_rerun(self, val): - self.rerun = val - - env = None - def set_env(self, val): - self.env = val - - allenv = None - def set_allenv(self, val): - self.allenv = val - - def _get_command_lines(self): - """Return the lines specifying instructions for job submission.""" - lines = list() - def add(line): - lines.append(self._command + line) # + '\n') - - if self.start_after: - add('-a ' + self.start_after) - - if self.account: - add('-A ' + self.account) - - if self.hold is True: - add('-h ') - - if self.combine is True: - add('-j oe') - - if self.resources: - for (arg, val) in self.resources.items(): - add('-l ' + arg + '=' + val) - - if self.mail: - add('-m ' + self.mail) - - if self.jobname: - add('-N ' + self.jobname) - - if self.priority: - add('-p ' + self.priority) - - if self.queue: - add('-q ' + self.queue) - - if self.rerun is True: - add('-r y') - - if self.env: - add('-v ' + ','.join(self.env)) - - if self.allenv is True: - add('-V') - - return lines diff --git a/abipy/htc/launcher.py b/abipy/htc/launcher.py deleted file mode 100644 index a05cd34a..00000000 --- a/abipy/htc/launcher.py +++ /dev/null @@ -1,914 +0,0 @@ -from __future__ import print_function, division #, unicode_literals - -import sys -import os -import warnings -import subprocess -import numpy as np - -from os.path import basename, dirname, join, relpath, abspath -from argparse import ArgumentParser, RawDescriptionHelpFormatter -from collections import OrderedDict -from copy import deepcopy - -from abipy.core import release, Structure, Density -from .utils import parse_ewc -from .abinitinput import AbinitInput - - -__all__ = [ - 'Launcher', - 'MassLauncher', -] - -# =========================================================================== # - -class LauncherArgParser(ArgumentParser): - """ - Base parser used in Launcher to parse the arguments provided by the user - when the function 'execute' is called. The parser consists of a top level - parser responsible for parsing global options such as verbosity level, - version, etc, and sub-parsers for the different sub-commands. - - Every class that inherits from Launcher, MassLauncher should define - a parser that inherits from this base class, and use register_subparser - to extend or customize the subparsers. The list of commands supported - by the instance is stored in self.commands so that we know whether - a particular sub-parser can handle the argument or if it should delegate - the superclass. - """ - def __init__(self, *args, **kwargs): - - #formatter_class=RawDescriptionHelpFormatter) - ArgumentParser.__init__(self, *args, **kwargs) - - # Create the top-level parse responsible for parsing global options - # such as verbosity level, version .... - # NOTE: except for verbose and version, any other option for the top - # level parser should be stored in a variable named --top-optname - # so that we don't pollute the namespace of the subcommands - - self.add_argument('-v', '--verbose', default=0, action='count', # -vv --> verbose=2 - help='verbose, can be supplied multiple times to increase verbosity') - - self.add_argument('--version', action='version', version="abipy " + release.version) - - # Create the parser for the sub-commands - self.subparsers = self.add_subparsers(dest='command', help='sub-command help') - - # Workaround. see http://stackoverflow.com/questions/8757338/sub-classing-the-argparse-argument-parser - # I know I shouldn't do this, but I don't want to wrap the parser in the object. - self.subparsers._parser_class = ArgumentParser - - p_make = self.subparsers.add_parser('make', help='Make files and directories') - p_make.add_argument('-f', '--force', action='store_true', default=False, help='Force file creation') - self.register_subparser("make", p_make) - - p_submit = self.subparsers.add_parser('submit', help='Submit the calculation to a batch server.') - #p_submit.add_argument('bar', type=int, help='bar help') - self.register_subparser("submit", p_submit) - - p_run = self.subparsers.add_parser('run', help='Run the calculation from the shell.') - #p_run.add_argument('-n', '--py-nthreads', metavar='NUM', type=int, default=1, help='Number of jobs.') - self.register_subparser("run", p_run) - - p_report = self.subparsers.add_parser('report', help='Tell if the calculation completed or not.') - #p_report.add_argument('bar', type=int, help='bar help') - self.register_subparser("report", p_report) - - p_inspect = self.subparsers.add_parser('inspect', help='Inspect files using EDITOR.') - p_inspect.add_argument('what_inspect', metavar='character(s)', default = "o", - help='Files to inspect: i for the input, o for the output, l for log, j for the job file. f for files file\n' + - 'Characters can be concatenated. Use "ol", for example, to inspect both the output and the log file.') - self.register_subparser("inspect", p_inspect) - - p_clean = self.subparsers.add_parser('clean', help='Remove log, data files and temporary files.') - #p_clean.add_argument('-f', '--force', action='store_true', help='Force') - self.register_subparser("clean", p_clean) - - p_destroy = self.subparsers.add_parser('destroy', help='Remove all files, including input and outputs.') - p_destroy.add_argument('-f', '--force', action='store_true', help='Force') - self.register_subparser("destroy", p_destroy) - - p_show = self.subparsers.add_parser('show', help='Print the calculation name and return True.') - #p_show.add_argument('-f', '--force', action='store_true', help='Force') - self.register_subparser("show", p_show) - - p_visu = self.subparsers.add_parser('visualize', #aliases=['visu'], - help='Visualize data with an external program e.g. Xcrysden.') - p_visu.add_argument('what_visualize', metavar='STRING', default = "crystal", help=' Type of data to visualize') - - self.register_subparser("visualize", p_visu) - - def myparse_args(self, args=None, namespace=None): - """ - Wrap the parse_args method of ArgumentParsers - :return: options, args, kwargs - - where options is the default output of parse_args and kwargs is a dictionary option_name -> value - """ - if args is None: - self.parse_args(args=["--help"], namespace=namespace) - - if '__iter__' not in dir(args): args = [args] - - # Call the "true" parse_args - options = self.parse_args(args=args, namespace=namespace) - - args = list() - kwargs = deepcopy(vars(options)) - - return options, args, kwargs - - @property - def commands(self): - "The commands registered in the parser" - return self._cmd2subparser.keys() - - def can_handle(self, command): - "True if the parser can handle command" - return command in self.commands - - def iter_cmdsubparser(self): - "Iterate over (command_string, subparser)" - for tup in self._cmd2subparser.items(): yield tup - - def register_subparser(self, command, subparser, solve_conflict=False): - """ - Register the subparser associate to a given command. - - :arg solve_conflict: By defaut it's not possible to override an existent subparser associated - to the same command. Use solve_conflict if subparser should replace the old one. - """ - if not hasattr(self, "_cmd2subparser"): self._cmd2subparser = OrderedDict() - if command in self._cmd2subparser and not solve_conflict: - raise ValueError("Cannot overwrite subparser for command %s. Use solve_conflict=True" % command) - self._cmd2subparser[command] = subparser - - def unregister_subparser(self, command): - """Unregister the subparser associated to the given command. Return the subparser removed""" - return self._cmd2subparser.pop(command) - - -# =========================================================================== # - -class LauncherError(Exception): - """base class for the exceptions raised by Launcher.""" - -class Launcher(AbinitInput): - """ - A more powerful version of :class:`~abipy.htc.AbinitInput`. - Allows to run a calculation, either from a script or from command line. - - .. code-block:: python - - >> calc = Launcher('Mycalc/root', - .. jobtype=None, - .. pseudodir='/path/to/pseudos/', - .. pseudos=['14si.pspnc'], - .. bindir='/path/to/binaries/') - >> - >> calc.read('myinput.in') - >> - >> # Write the files. - >> calc.make() - >> - >> # Run the calculation with abinit. - >> calc.run() - >> - >> # Inquire about the calculation status. - >> status = calc.report() - >> - >> if status == 'Completed': - .. # Remove log and data files. - .. calc.clean(force=True) - - You can perform all these actions from the command line, using the function 'execute'. - """ - Error = LauncherError - - # Parser class and instance are stored as class attributes. - ArgParser = LauncherArgParser - argparser = LauncherArgParser() - - def output_files(self): - """Return all output files produced, in alphabetical order.""" - base = self.output_name - files = [base] + [ base + a for a in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ] - return filter(os.path.exists, files) - - def last_output(self): - """Return the last output file produced.""" - files = self.output_files() - if not files: - return None - return files[-1] - - def idat_files(self): - """Return all the input data files.""" - files = list() - for file in os.listdir(dirname(self.idat_root)): - if file.startswith(basename(self.idat_root)): - files.append(join(dirname(self.idat_root), file)) - return files - - def odat_files(self): - """Return all the output data files produced.""" - files = list() - for file in os.listdir(dirname(self.odat_root)): - if file.startswith(basename(self.odat_root)): - files.append(join(dirname(self.odat_root), file)) - return files - - def tmp_files(self): - """Return all the input data files produced.""" - files = list() - for file in os.listdir(dirname(self.tmp_root)): - if file.startswith(basename(self.tmp_root)): - files.append(join(dirname(self.tmp_root), file)) - return files - - def junk_files(self): - """Return all the junk files produced.""" - files = list() - for file in os.listdir(self.jobfile.dirname): - - if (file.startswith('fort.') or - file.endswith('.dat') or - file in ('_GWDIAG',)): - - files.append(join(self.jobfile.dirname, file)) - - return files - - def read_mainlog_ewc(self): - """ - Read errors, warnings and comments from the main output and the log file. - - :return: Two namedtuple instances: main, log. - The lists of strings with the corresponding messages are - available in main.errors, main.warnings, main.comments, log.errors etc. - """ - from pymatgen.io.abinit.events import EventsParser - parser = EventsParser() - main_events = parser.parse(self.last_output()) - log_events = parser.parse(self.log_name) - - return main_events, log_events - - def make(self, *args, **kwargs): - """ - Write the files. - - Keyword arguments: - verbose: (0) - Print message if verbose is not zero. - """ - if kwargs.get('verbose'): - print('Writing ' + self.name) - - self.write(*args, **kwargs) - - def run(self, *args, **kwargs): - """ - Run the calculation by executing the job file from the shell. - - Keyword arguments: - verbose: (0) - Print message if verbose is not zero. - - .. warning:: - This method must be thread safe since we may want to run several indipendent - calculations with different python threads. - """ - if kwargs.get('verbose'): - print('Running ' + self.name + '\n') - - subprocess.call((self.jobfile.shell, self.jobfile.absname)) - - def submit(self, *args, **kwargs): - """ - Submit the calculation to a batch server. - - Keyword arguments: - verbose: (0) - Print message if verbose is not zero. - - """ - if kwargs.get('verbose'): - print('Submitting ' + self.name) - - curdir = abspath(os.curdir) - os.chdir(self.jobfile.absdir) - subprocess.call((self.jobfile.submission_command, self.jobfile.basename)) - os.chdir(curdir) - - def clean(self, *args, **kwargs): - """ - Remove log file, data files and tmp files. - - Keyword arguments: - force: (False) - Do not ask confirmation. - verbose: (0) - Print message if verbose is not zero. - """ - if kwargs.get('verbose'): - print('Cleaning ' + self.name) - - destroy = [self.log_name] - for files in (self.odat_files(), self.tmp_files(), self.junk_files()): - destroy.extend(files) - - if destroy: - self._remove_files(destroy, kwargs.get('force', False)) - - def destroy(self, *args, **kwargs): - """ - Remove all calculation files and directories, if empty. - - Keyword arguments: - force: (False) - Do not ask confirmation. - verbose: (0) - Print message if verbose is not zero. - - """ - if kwargs.get('verbose'): - print('Destroying ' + self.name) - - destroy = [self.input_name, self.log_name, self.job_name, self.files_name] - - for files in (self.output_files(), self.idat_files(), - self.odat_files(), self.tmp_files(), self.junk_files()): - destroy.extend(files) - - if destroy: - self._remove_files(destroy, kwargs.get('force', False)) - self._remove_directory_tree(self.dirname) - - def show(self, form=str, *args, **kwargs): - """Print the calculation name and return True.""" - print(form(self.name)) - return True - - def inspect(self, *args, **kwargs): - """ - Inspect the input/(last) output/ log produced by the run. - - :arg what_inspect: - "i" for the input, "o" for the output, "l" for log, "j" for the job file. "f" for the files file - characters can be concatenated. what="ol", for example, will inspect both the output and the log file. - - The environment variable EDITOR defines the application to use (default vi). - """ - from ..tools import Editor - editor = Editor() - - what_inspect = kwargs.get("what_inspect", "o") - - filenames = [] - if "i" in what_inspect: filenames.append(self.input_name) - if "o" in what_inspect: filenames.append(self.last_output()) - if "l" in what_inspect: filenames.append(self.log_name) - if "j" in what_inspect: filenames.append(self.job_name) - if "f" in what_inspect: filenames.append(self.files_name) - editor.edit_files(filenames, ask_for_exit=True) - - def visualize(self, *args, **kwargs): - # TODO Here I have to decide if this method should be defined - # in a subclass of Launcher e.g GSLauncher or in the base class - from .utils import find_file - what_visualize = kwargs.get("what_visualize", "crystal") - - visualizer = "xcrysden" - #visualizer = abipy_env.get_uservar("visualizer", kwargs) - - # Find the correct output file - out_files = self.odat_files() - gsfname = find_file(out_files, "GSR") - - if gsfname is None: - raise RuntimeError("Cannot find GSR file among %s" % out_files) - - if what_visualize == "crystal": - - structure = Structure.from_file(gsfname) - structure.visualize(visualizer)() - - elif what_visualize == "density": - raise NotImplementedError("den_fname?") - - density = Density.from_file(den_fname) - density.visualize(visualizer)() - - elif what_visualize in ["electrons", "fermisurface",]: - from ..electrons import ElectronBands - energies = ElectronBands.from_file(gsfname) - - if what_visualize == "electrons": energies.plot() - if what_visualize == "fermisurface": - raise RuntimeError("No hanlder found for fermisurface") - #visu = energies.visualize(self, visualizer, structure) - #visu() - else: - raise RuntimeError("No handler found for %s" % what_visualize) - - # TODO - #def __str__(self): - # string = "" - # return string - - def report(self, *args, **kwargs): - """ - Print information on the calculation status and return a status. - - Keyword arguments: - verbose: (0) - 0 : do not print anything - > 0 : print status - > 1 : print number of errors, warnings and comments - - """ - output = self.last_output() - - from ..tools import StringColorizer - str_colorizer = StringColorizer(sys.stdout) - - status2txtcolor = { - "Completed" : lambda string : str_colorizer(string, "green"), - "Unfinished" : lambda string : str_colorizer(string, "blue"), - "Unstarted" : lambda string : str_colorizer(string, "cyan"), - } - - def color(status): return status2txtcolor[status](status) - - verbose = kwargs.get('verbose', 0) - - if output and self._iscomplete(output): - status = 'Completed' - msg = relpath(output) + ' : ' + color(status) - - if verbose: - - # Does not work! - pass - - ## Read the number of errors, warnings and comments - ##for the (last) main output and the log file. - #main, log = self.read_mainlog_ewc() - - #main_info = main.tostream(sys.stdout) - #log_info = log.tostream(sys.stdout) - - #msg += "\n " + "\n ".join([main_info, log_info]) - - elif os.path.exists(self.log_name): - status = 'Unfinished' - msg = self.name + ' : ' + color(status) - - else: - status = 'Unstarted' - msg = self.name + ' : ' + color(status) - - if verbose: - print(msg) - if status == 'Completed': - pass - # Does not work! - #for w in main.warnings: print(w) - #if verbose > 1: - # for w in log.warnings: print(w) - - return status - - def execute(self, *args, **kwargs): - """ - Execute an action from the command line. - - * make -- Write the files. - * submit -- Submit the calculation to a batch server. - * run -- Run the calculation from the shell. - * report -- Tell if the calculation completed or not. - * inspect -- Open files in EDITOR - * clean -- Remove log, data files and temporary files. - * show -- Signify that the calculation exists. - * destroy -- Remove all files, including input and outputs. - * visualize -- Visualize data. - - Suppose this is the content of 'myscript.py': - - .. code-block:: python - - > # myscript.py - calc = Launcher('Mycalc/root', jobtype=None, - pseudodir='Data', pseudos=['14si.pspnc'], - executable='abinit') - calc.read('myinput.in') - calc.execute() - - Then, from the shell, one can perform the following:: - - .. code-block:: python - - > # bash - > python myscript.py make - Writing Mycalc/root - > - > python myscript.py show - Mycalc/root - > - > python myscript.py run - Running Mycalc/root - > - > python myscript.py report - Mycalc/root.out : Completed - > - > python myscript.py clean - Cleaning Mycalc/root - - Typically, one would use the command 'submit' instead of 'run'. - """ - if not args: - args = sys.argv[1:] - - options, args, kwargs = self.argparser.myparse_args(args) - - if self.argparser.can_handle(options.command): - getattr(self, options.command)(*args, **kwargs) - - else: - raise RuntimeError("Don't know how to handle command %s. This should not happen!" % options.command) - - @staticmethod - def _remove_files(files, force=False): - """Remove a list of file, asking confirmation.""" - - files = filter(os.path.exists, files) - - if files and not force: - - print("About to remove the following files:") - for file in files: - print(file) - - proceed = raw_input("Do you want to proceed? (y/n) ") - if not proceed.lower().startswith('y'): - return - - for file in files: - try: - os.remove(file) - except Exception as exc: - warnings.warn(str(exc)) - - @staticmethod - def _remove_directory_tree(topdir): - """Remove a directory hierarchy, if it contains no files.""" - dirs = [topdir] - for d in dirs: - for f in os.listdir(d): - sub = os.path.join(d, f) - if os.path.isdir(sub): - dirs.append(sub) - else: - return - - for d in reversed(dirs): - try: - os.rmdir(d) - except OSError: - warnings.warn("Directory tree partially removed: " + topdir) - - @staticmethod - def _iscomplete(output_file): - "Return True if an abinit output file is complete." - with open(output_file, 'r') as f: - lines = f.readlines() - lines.reverse() - - for i in range(10): - try: - line = lines[i] - except: - return False - - if 'Calculation completed.' in line: - return True - return False - - # Does not work ! - #from pymatgen.io.abinit.utils import abinit_output_iscomplete - #return abinit_output_iscomplete(output_file) - -# =========================================================================== # - -class MassLauncherArgParser(LauncherArgParser): - """ - top level parser and subparsers used by MassLauncher. - Handle all the options of Launcher and add the option -c to select the calculations. - """ - def __init__(self, *args, **kwargs): - - LauncherArgParser.__init__(self, *args, **kwargs) - - for (cmd, subparser) in self.iter_cmdsubparser(): - # Add new option - subparser.add_argument('-c', '--calc', dest='only', nargs='*', type=str, - help="Execute command only for the selected calculations.") - - # Add py_nthreads arg to the commands that support threads. - if cmd in ["run",]: - subparser.add_argument('-n', '--py_nthreads', nargs='?', type=int, default=1, - help="The number of threads (to run simultaneously).") - - - # Register the curried subparser so that MassLauncher will take over in execute. - self.register_subparser(cmd, subparser, solve_conflict=True) - -class MassLauncher(object): - """ - To launch several nearly-identical launchers. - Acts like a list of launcher. - - .. code-block:: python - - >> # Let's create four calculations # The rootnames are - >> calcs = MassLauncher(4, 'Mycalcs/calc', # Mycalcs/calc1 - >> jobtype='PBS', # Mycalcs/calc2 - >> pseudodir='Data', # Mycalcs/calc3 - >> executable='abinit') # Mycalcs/calc4 - >> - >> # == Common properties == - >> calcs.read('common.in') - >> - >> calcs.ecut = 10. - >> calcs.tolwfr = 1e-8 - >> calcs.nstep = 0 - >> calcs.iscf = 7 - >> - >> unit_cell = {'ntypat' : 1, 'znucl' : [14], 'natom' : 2, 'typat' : [1, 1], - >> 'rprim' : [[.0, .5, .5], [.5, .0, .5], [.5, .5, .0]], - >> 'acell' : 3*[10.261], 'xred' : [[.0, .0, .0], [.25,.25,.25]]} - >> calcs.set_variables(unit_cell) - >> - >> calcs.set_pseudos('14si.pspnc') - >> - >> calcs.set_jobname('MyTest') - >> calcs.set_nodes(1) - >> calcs.set_ppn(12) - >> calcs.set_memory('1gb') - >> calcs.set_runtime(48) - >> - >> # == Specific properties == - >> ecut = 10. - >> for calc in calcs: - >> calc.ecut = ecut - >> ecut += 5. - >> - >> # Write them all. - >> calcs.make() - """ - - ArgParser = MassLauncherArgParser - argparser = MassLauncherArgParser() - - def __init__(self, n=0, name='Calc', *args, **kwargs): - - self._setattr(launchers = list()) - self._setattr(ids = list()) - - if n > 0: - index_format = '0=' + str(int(np.log10(n)) + 1) - for i in range(1, n+1): - index = '{i:{f}}'.format(i=i, f=index_format) - calc_name = name + index - launcher = Launcher(calc_name, *args, **kwargs) - self.add_launcher(launcher, index=index) - - def __getitem__(self, i): return self.launchers[i] - - def __setitem__(self, i, calc): self.launchers[i] = calc - - def __delitem__(self, i): del self.launchers[i] - - def __iter__(self): return iter(self.launchers) - - def __len__(self): return len(self.launchers) - - def _setattr(self, **kwargs): - self.__dict__.update(kwargs) - - #def __getattr__(self, name): - # """Return a function that passes the arguments to all launchers.""" - # def f(*args, **kwargs): - # return [ getattr(c, name)(*args, **kwargs) for c in self ] - # return f - - def _distributed(self, func_name): - """Return a function that passes the arguments to all launchers.""" - def f(*args, **kwargs): - return [ getattr(c, func_name)(*args, **kwargs) for c in self ] - f.__doc__ = getattr(self[0], func_name).__doc__ - return f - - def _make_distributed(f): - """Make a function distributed to all launchers.""" - def g(self, *args, **kwargs): - return self._distributed(f.__name__)(*args, **kwargs) - g.__doc__ = getattr(Launcher, f.__name__).__doc__ - return g - - def __setattr__(self, name, value): - """Set an attribute to all launchers.""" - return [ setattr(calc, name, value) for calc in self ] - - def add_launcher(self, launcher, index=None): - """Add a Launcher instance (or derivatives) to the list.""" - if index is None: - index = str(len(self.launchers) + 1) - # maybe needs an OrderedDict here - self.launchers.append(launcher) - self.ids.append(index) - - # Create missing property setter - if len(self.launchers) == 1: - for prop in self.launchers[0].properties(): - setter = 'set_' + prop - if not setter in dir(self): - self.__dict__[setter] = self._distributed(setter) - - def properties(self): - """Return the list of properties with a `set_` function.""" - funcs = filter(lambda s: s.startswith('set_'), dir(self)) - return [ f.split('set_', 1)[-1] for f in funcs ] - - def only(self, only=None): - """Return a list of indexes and a list of calc which are included in 'only'.""" - if only: - filtered = list() - for i, calc in zip(self.ids, self): - if str(i) in map(str, only): - filtered.append((i, calc)) - else: - filtered= zip(self.ids, self) - return filtered - - @_make_distributed - def set_pseudodir(self): return - - @_make_distributed - def set_pseudos(self): return - - @_make_distributed - def read(self): return - - @_make_distributed - def set_variables(self): return - - @_make_distributed - def set_comment(self): return - - @_make_distributed - def link_idat(self): return - - @_make_distributed - def link_odat(self): return - - @_make_distributed - def link_io(self): return - - def execute(self, *args, **kwargs): - """ - Execute an action given from the command line. - - * make -- Write the files. - * submit -- Submit the calculation to a batch server. - * run -- Run the calculation from the shell. - * report -- Tell if the calculation completed or not. - * clean -- Remove log, data files and temporary files. - * show -- Signify that the calculation exists. - * destroy -- Remove all files, including input and outputs. - - Command line optional arguments: - -c [id1 [,id2 [id3, ... ]]] : - - Select a subset of calculations. - - With the previous example, one could issue:: - - > python myscript.py make - Writing Mycalc/calc1 - Writing Mycalc/calc2 - Writing Mycalc/calc3 - Writing Mycalc/calc4 - > - > python myscript.py show -c 1 2 - Mycalc/calc1 - Mycalc/calc2 - > - > python myscript.py run -c 2 3 - Running Mycalc/calc2 - Running Mycalc/calc3 - > - > python myscript.py report - Mycalc/calc1 : Unstarted - Mycalc/calc2.out : Completed - Mycalc/calc3.out : Completed - Mycalc/calc4 : Unstarted - """ - if not args: - args = sys.argv[1:] - - options, args, kwargs = self.argparser.myparse_args(args) - kwargs.update(vars(options)) - - if self.argparser.can_handle(options.command): - - try: - nthreads = options.py_nthreads - except AttributeError: - nthreads = 1 - - print("About to run command", options.command," with nthreads", nthreads) - - if nthreads == 1: - - if options.command in dir(self): - getattr(self, options.command)(*args, **kwargs) - else: - for index, calc in self.only(options.only): - #for i, calc in zip(self.ids, self): - # if options.only and str(i) not in map(str, options.only): - # continue - getattr(calc, options.command)(*args, **kwargs) - - else: - # Threaded version. - from threading import Thread - from Queue import Queue - - def worker(): - while True: - func, args, kwargs = q.get() - func(*args, **kwargs) - q.task_done() - - q = Queue() - for i in range(nthreads): - t = Thread(target=worker) - t.setDaemon(True) - t.start() - - #for (i, calc) in zip(self.ids, self): - # if options.only and i not in options.only: continue - for index, calc in self.only(options.only): - func = getattr(calc, options.command) - q.put((func, args, kwargs)) - - # Block until all tasks are done. - q.join() - - else: - raise RuntimeError("Don't know how to handle command %s. This should not happen!" % options.command) - - @_make_distributed - def report(self): return - - @_make_distributed - def odat_files(self): return - - @_make_distributed - def last_output(self): return - - #@_make_distributed - def show(self, form=None, *args, **kwargs): - only = kwargs.get('only') - if form is None: - form = str - - #@form - def tmpform(s): - return str(index) + ' ' + s - - newform = lambda s: form(tmpform(s)) - - for index, calc in self.only(only): - calc.show(form=newform, *args, **kwargs) - - return - - @_make_distributed - def make(self): return - - @_make_distributed - def run(self): return - - @_make_distributed - def submit(self): return - - @_make_distributed - def clean(self): return - - @_make_distributed - def destroy(self): return diff --git a/abipy/htc/tests/__init__.py b/abipy/htc/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/abipy/htc/tests/test_filesfile.py b/abipy/htc/tests/test_filesfile.py deleted file mode 100644 index c17e2f8a..00000000 --- a/abipy/htc/tests/test_filesfile.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Tests for htc.FilesFile.""" -from __future__ import print_function, division - -import warnings - -from abipy.htc.filesfile import FilesFile -from abipy.core.testing import * - -# =========================================================================== # - -class TestFilesFile(AbipyFileTest): - """Unit tests for FilesFile.""" - - def setUp(self): - self.file = FilesFile('MyDir/MyName.files', - input='mycalc.in', - output='mycalc.out', - idat_root='i_mycalc', - odat_root='o_mycalc', - tmp_root='t_mycalc') - - self.file.pseudos = ['ps1', 'ps2'] - self.file.pseudodir = '/path/to/my/pseudodir' - - def test_check_pseudos(self): - """Test the user is warned of pseudopotential not found.""" - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - self.file.check_pseudos() - self.assertEqual(len(w), 2) - msg = str(w[-1].message) - self.assertRegexpMatches(msg, "file not found") - - def test_str(self): - """Test the FilesFile is printed correctly.""" - self.assertContains(""" - mycalc.in - mycalc.out - i_mycalc - o_mycalc - t_mycalc - /path/to/my/pseudodir/ps1 - /path/to/my/pseudodir/ps2 - """) - - -if __name__ == "__main__": - import unittest - unittest.main() diff --git a/abipy/htc/tests/test_input.py b/abipy/htc/tests/test_input.py deleted file mode 100644 index f5fabd1e..00000000 --- a/abipy/htc/tests/test_input.py +++ /dev/null @@ -1,211 +0,0 @@ -"""Tests for htc.FilesFile.""" -from __future__ import print_function, division, unicode_literals - -import numpy as np -import abipy.data as abidata - -from abipy.core.testing import AbipyTest -from abipy.htc.input import * - - -class AbiInputTest(AbipyTest): - - def test_si_input(self): - """Testing Silicon input with NC pseudo.""" - aequal, atrue = self.assertEqual, self.assertTrue - - # Create an ABINIT input file with 1 dataset. - inp = AbiInput(pseudos="14si.pspnc", pseudo_dir=abidata.pseudo_dir, ndtset=1) - inp.set_comment("Input file with 1 dataset") - assert inp.isnc - - inp.set_mnemonics(True) - assert inp.mnemonics - - # One can set the value of the variables directly with the syntax. - inp.ecut = 10. - inp.tolwfr = 1e-8 - - # It's possible to use strings but use them only for special cases such as: - inp.istwfk = '*1' - - # One can create a dictionary mapping keywords to values - unit_cell = { - "acell": 3*[10.217], - 'rprim': [[.0, .5, .5], - [.5, .0, .5], - [.5, .5, .0]], - 'ntypat': 1, - 'znucl': [14,], - 'natom': 2, - 'typat': [1, 1], - 'xred': [[.0, .0, .0], - [.25,.25,.25]] - } - - # and set the variables in the input file with the call: - inp.set_vars(**unit_cell) - # Now we have a structure - assert len(inp.structure) == 2 - assert inp.num_valence_electrons == 8 - - # Alternatively, it's possible to create a dictionary on the fly with the syntax. - inp.set_vars(kptopt=1, ngkpt=[2, 2, 2], nshiftk=1, - shiftk=np.reshape([0.0, 0.0, 0.0], (-1,3))) - - inp.nshiftk = len(inp.shiftk) - assert inp.nshiftk == 1 - - inp.remove_vars("nshiftk") - with self.assertRaises(AttributeError): print(inp.nshiftk) - - # To print the input to stdout use: - print(inp) - - # Test set_structure - new_structure = inp.structure.copy() - new_structure.perturb(distance=0.1) - inp.set_structure(new_structure) - assert inp.structure == new_structure - - # To create a new input with a different variable. - new = inp.new_with_vars(kptopt=3) - assert new.kptopt == 3 and inp.kptopt == 1 - - # Compatible with deepcopy, Pickle and MSONable? - inp.deepcopy() - self.serialize_with_pickle(inp, test_eq=False) - self.assertMSONable(inp) - - # A slightly more complicated example: input file with two datasets - inp = AbiInput(pseudos="14si.pspnc", pseudo_dir=abidata.pseudo_dir, ndtset=2) - - # Global variable common to all datasets. - inp.tolwfr = 1e-8 - - # To specify values for the different datasets, one can use the syntax - inp.ecut1 = 10 - inp.ecut2 = 20 - - assert inp[1]["ecut"] == inp.ecut1 and inp[2]["ecut"] == inp.ecut2 - assert inp[1].get("ecut") == inp.ecut1 and inp[2].get("foobar") is None - - with self.assertRaises(AttributeError): print(inp.ecut) - inp.remove_vars("ecut", dtset=2) - assert inp.ecut1 == 10 - with self.assertRaises(AttributeError): print(inp.ecut2) - - # or by passing the index of the dataset to set_vars via the dtset argument. - inp.set_vars(ngkpt=[2,2,2], tsmear=0.004, dtset=1) - inp.set_vars(kptopt=[4,4,4], tsmear=0.008, dtset=2) - print(inp) - - # Compatible with deepcopy, Pickle and MSONable? - inp.deepcopy() - self.serialize_with_pickle(inp, test_eq=False) - self.assertMSONable(inp) - - # pseudo file must exist. - with self.assertRaises(inp.Error): - AbiInput(pseudos="foobar.pspnc", pseudo_dir=abidata.pseudo_dir, ndtset=2) - - tsmear_list = [0.005, 0.01] - ngkpt_list = [[4,4,4], [8,8,8]] - occopt_list = [3, 4] - inp = AbiInput(pseudos=abidata.pseudos("14si.pspnc"), ndtset=len(tsmear_list)) - - inp.linspace("tsmear", start=tsmear_list[0], stop=tsmear_list[-1]) - print(inp) - - inp = AbiInput(pseudos=abidata.pseudos("14si.pspnc"), ndtset=len(tsmear_list) * len(ngkpt_list)) - - inp.product("tsmear", "ngkpt", tsmear_list, ngkpt_list) - print(inp) - - # If you don't want to use multiple datasets in your calculation, - # you can split the initial input into ndtset different inputs. - separated_inps = inp.split_datasets() - - for inp in separated_inps: - print(inp) - atrue(isinstance(inp, AbiInput)) - - # product accepts an arbitrary number of variables. - inp = AbiInput(pseudos=abidata.pseudos("14si.pspnc"), ndtset=len(tsmear_list) * len(ngkpt_list) * len(occopt_list)) - - inp.product("tsmear", "ngkpt", "occopt", tsmear_list, ngkpt_list, occopt_list) - print(inp) - - # Split datasets. - inp.split_datasets() - - # Cannot split datasets when we have get* or ird* variables. - inp[2].set_vars(getwfk=-1) - with self.assertRaises(inp.Error): inp.split_datasets() - - def test_niopaw_input(self): - """Testing AbiInput for NiO with PAW.""" - aequal = self.assertEqual - - inp = AbiInput(pseudos=abidata.pseudos("28ni.paw", "8o.2.paw"), ndtset=2, comment="NiO calculation") - inp.set_structure(abidata.structure_from_ucell("NiO")) - print(inp) - - aequal(inp.ndtset, 2) - aequal(inp.ispaw, True) - - # Set global variables. - inp.set_vars(ecut=10) - - # Compatible with deepcopy, Pickle and MSONable? - inp.deepcopy() - self.serialize_with_pickle(inp, test_eq=False) - self.assertMSONable(inp) - - # Setting an unknown variable should raise an error. - with self.assertRaises(inp.Error): inp.set_vars(foobar=10) - - -class LdauLexxTest(AbipyTest): - - def test_nio(self): - """Test LdauParams and LexxParams.""" - aequal, atrue = self.assertEqual, self.assertTrue - - structure = abidata.structure_from_ucell("NiO") - pseudos = abidata.pseudos("28ni.paw", "8o.2.paw") - - u = 8.0 - luj_params = LdauParams(usepawu=1, structure=structure) - luj_params.luj_for_symbol("Ni", l=2, u=u, j=0.1*u, unit="eV") - vars = luj_params.to_abivars() - - self.serialize_with_pickle(luj_params, test_eq=False) - - atrue(vars["usepawu"] == 1), - aequal(vars["lpawu"], "2 -1"), - aequal(vars["upawu"], "8.0 0.0 eV"), - aequal(vars["jpawu"], "0.8 0.0 eV"), - - # Cannot add UJ for non-existent species. - with self.assertRaises(ValueError): - luj_params.luj_for_symbol("Foo", l=2, u=u, j=0.1*u, unit="eV") - - # Cannot overwrite UJ. - with self.assertRaises(ValueError): - luj_params.luj_for_symbol("Ni", l=1, u=u, j=0.1*u, unit="eV") - - lexx_params = LexxParams(structure) - lexx_params.lexx_for_symbol("Ni", l=2) - vars = lexx_params.to_abivars() - - self.serialize_with_pickle(lexx_params, test_eq=False) - - aequal(vars["useexexch"], 1), - aequal(vars["lexexch"], "2 -1"), - - # Cannot add LEXX for non-existent species. - with self.assertRaises(ValueError): lexx_params.lexx_for_symbol("Foo", l=2) - - # Cannot overwrite LEXX. - with self.assertRaises(ValueError): lexx_params.lexx_for_symbol("Ni", l=1) diff --git a/abipy/htc/tests/test_inputfile.py b/abipy/htc/tests/test_inputfile.py deleted file mode 100644 index dd6081f9..00000000 --- a/abipy/htc/tests/test_inputfile.py +++ /dev/null @@ -1,83 +0,0 @@ -"""Tests for htc.InputFile.""" -import warnings - -from abipy.core.testing import AbipyFileTest -from abipy.htc.jobfile import JobFile -from abipy.htc.inputfile import InputFile -from abipy.htc.variable import SpecialInputVariable - - -class TestInputVariable(AbipyFileTest): - """Unit tests for AbinitVariable.""" - - def setUp(self): - self.file = SpecialInputVariable('ecut', 10.0) - - def test_varnames(self): - """Test printing of variables name.""" - self.file.name = 'ecut1' - self.assertContains('ecut1') - - self.file.name = 'ecut__s' - self.assertContains('ecut:') - - self.file.name = 'ecut__i' - self.assertContains('ecut+') - - self.file.name = 'ecut__a' - self.assertContains('ecut?') - - self.file.name = 'ecut__s2' - self.assertContains('ecut:2') - - self.file.name = 'ecut3__a' - self.assertContains('ecut3?') - - self.file.name = 'ecut__s__a' - self.assertContains('ecut:?') - - def test_scalar_values(self): - """Test printing of scalar variables.""" - self.file.value = 11.5 - self.assertContains('ecut 11.5') - - self.file.value = 10 - self.assertContains('ecut 10') - - self.file.value = '*1' - self.assertContains('ecut *1') - - self.file.value = None - self.assertEmpty() - - self.file.value = '' - self.assertEmpty() - - -class TestInputFile(AbipyFileTest): - """Unit tests for InputFile.""" - - def setUp(self): - self.file = InputFile('MyDir/MyName.in') - - def test_set_variable_attribute(self): - """Test setting variables by attribute.""" - self.file.ecut = 10. - self.assertContains('ecut 10.') - - def test_set_variable_function(self): - """Test setting variables with set_variable.""" - self.file.set_variable('ecut', 10.) - self.assertContains('ecut 10.') - - def test_set_variables_function(self): - """Test setting variables with set_variables.""" - - self.file.set_variables({'ecut':10., 'nstep':100}) - self.assertContains('nstep 100') - self.assertContains('ecut 10.') - - self.file.set_variables({'ecut':10., 'nstep':100}, 1) - self.assertContains('nstep1 100') - self.assertContains('ecut1 10.') - diff --git a/abipy/htc/tests/test_jobfile.py b/abipy/htc/tests/test_jobfile.py deleted file mode 100644 index 2303ca3d..00000000 --- a/abipy/htc/tests/test_jobfile.py +++ /dev/null @@ -1,165 +0,0 @@ -"""Tests for htc.JobFile.""" -from __future__ import print_function, division - -import warnings - -from abipy.core.testing import AbipyFileTest -from abipy.htc.jobfile import JobFile - - -class TestJobFile(AbipyFileTest): - """Unit tests for JobFile.""" - - def setUp(self): - self.file = JobFile('MyJob.sh', input='myinput') - - def test_shell_line(self): - """Check the shell line is printed correctly.""" - self.assertContains("#!/bin/bash") - self.file.shell = 'csh' - self.assertContains("#!/bin/csh") - - def test_declaration_lines(self): - """Check the declaration are printed correctly in bash.""" - self.assertContains(""" - MPIRUN="" - EXECUTABLE=abinit - INPUT=myinput - LOG=log - STDERR=stderr - """) - - def test_execution_line(self): - """Check the execution line is printed correctly in bash.""" - self.assertContains(""" - $MPIRUN $EXECUTABLE < $INPUT > $LOG 2> $STDERR - """) - - def test_mpirun(self): - """Check mpirun is set correctly.""" - self.file.mpirun = 'openmpirun -n 2' - self.assertContains(""" - MPIRUN="openmpirun -n 2" - """) - - def test_modules(self): - """Check the modules are loaded correctly.""" - - self.file.modules = "single" - self.assertContains("module load single") - - m1, m2 = 'mod1', 'mod2/version/1.4-b' - lookfor = """ - module load {0} - module load {1} - """.format(m1, m2) - self.file.modules = m1, m2 - self.assertContains(lookfor) - - self.file.modules = [m1, m2] - self.assertContains(lookfor) - - self.file.set_modules(m1, m2) - self.assertContains(lookfor) - - self.file.set_modules([m1, m2]) - self.assertContains(lookfor) - - def test_lines_before(self): - """Check lines_before are printed correctly.""" - - single_line = "A single line." - self.file.lines_before = single_line - self.assertContains(single_line) - - l1 = "This is my first line!" - l2 = "And that is my ${SECOND_LINE}" - lookfor = '\n'.join([l1, l2]) - - self.file.lines_before = l1, l2 - self.assertContains(lookfor) - - self.file.lines_before = [l1, l2] - self.assertContains(lookfor) - - self.file.set_lines_before(l1, l2) - self.assertContains(lookfor) - - self.file.set_lines_before([l1, l2]) - self.assertContains(lookfor) - - def test_lines_after(self): - """Check lines_after are printed correctly.""" - - single_line = "A single line." - self.file.lines_after = single_line - self.assertContains(single_line) - - l1 = "This is my first line!" - l2 = "And that is my ${SECOND_LINE}" - lookfor = '\n'.join([l1, l2]) - - self.file.lines_after = l1, l2 - self.assertContains(lookfor) - - self.file.lines_after = [l1, l2] - self.assertContains(lookfor) - - self.file.set_lines_after(l1, l2) - self.assertContains(lookfor) - - self.file.set_lines_after([l1, l2]) - self.assertContains(lookfor) - - def test_other_lines(self): - """Check other_lines are printed correctly.""" - - single_line = "A single line." - self.file.other_lines = single_line - self.assertContains(single_line) - - l1 = "This is my first line!" - l2 = "And that is my ${SECOND_LINE}" - lookfor = '\n'.join([l1, l2]) - - self.file.other_lines = l1, l2 - self.assertContains(lookfor) - - self.file.other_lines = [l1, l2] - self.assertContains(lookfor) - - self.file.set_other_lines(l1, l2) - self.assertContains(lookfor) - - self.file.set_other_lines([l1, l2]) - self.assertContains(lookfor) - - -class TestJobFileCSH(AbipyFileTest): - """Unit tests for JobFile with csh shell.""" - - def setUp(self): - self.file = JobFile('MyJob.sh', input='myinput') - self.file.shell='csh' - - def test_declaration_lines(self): - """Check the declaration are printed correctly in csh.""" - self.assertContains(""" - set MPIRUN="" - set EXECUTABLE=abinit - set INPUT=myinput - set LOG=log - set STDERR=stderr - """) - - def test_execution_line(self): - """Check the execution line is printed correctly in csh.""" - self.assertContains(""" - ($MPIRUN $EXECUTABLE < $INPUT > $LOG) >& $STDERR - """) - - -if __name__ == "__main__": - import unittest - unittest.main() - diff --git a/abipy/htc/utils.py b/abipy/htc/utils.py deleted file mode 100644 index 1d0e29ed..00000000 --- a/abipy/htc/utils.py +++ /dev/null @@ -1,199 +0,0 @@ -"""Tools and helper functions for abinit calculations""" -from __future__ import print_function, division #, unicode_literals - -import os.path -import collections - -from copy import deepcopy -from itertools import chain -from monty.string import list_strings - -########################################################################################## - -class _ewc_tuple(collections.namedtuple("ewc_tuple", "errors, warnings, comments")): - - def tostream(self, stream): - "Return a string that can be visualized on stream (with colors if stream support them)." - str_colorizer = StringColorizer(stream) - - red = lambda num : str_colorizer(str(num), "red") - blue = lambda num : str_colorizer(str(num), "blue") - - nums = map(len, [self.errors, self.warnings, self.comments]) - - colors = (red, blue, str) - - for (i, n) in enumerate(nums): - color = colors[i] - nums[i] = color(n) if n else str(n) - - return "%s errors, %s warnings, %s comments in main output file" % tuple(nums) - -########################################################################################## - -def parse_ewc(filename, nafter=5): - """ - Extract errors, warnings and comments from file filename. - - :arg nafter: Save nafter lines of trailing context after matching lines. - :return: namedtuple instance. The lists of strings with the corresponding messages are - available in tuple.errors, tuple.warnings, tuple.comments. - """ - # TODO - # we have to standardize the abinit WARNING, COMMENT and ERROR so that we can parse them easily - # without having to use nafter. - - errors, warnings, comments = [], [], [] - # Note the space after the name. - exc_cases = ["ERROR ", "BUG ", "WARNING ", "COMMENT "] - - handlers = { - "ERROR " : errors.append, - "BUG " : errors.append, - "WARNING " : warnings.append, - "COMMENT " : comments.append, - } - - def exc_case(line): - for e in exc_cases: - if e in line: return e - else: - return None - - with open(filename, "r") as fh: - lines = fh.readlines() - nlines = len(lines) - for (lineno, line) in enumerate(lines): - handle = handlers.get(exc_case(line)) - if handle is None: continue - context = lines[lineno: min(lineno+nafter, nlines)] - handle( "".join([c for c in context]) ) - - return _ewc_tuple(errors, warnings, comments) - -########################################################################################## - -def find_file(files, ext, prefix=None, dataset=None, image=None): - """ - Given a list of file names, return the file with extension "_" + ext, None if not found. - - The prefix, the dataset index and the image index can be specified - - .. warning:: - - There are some border cases that will confuse the algorithm - since the order of dataset and image is not tested. - Solving this problem requires the knowledge of ndtset and nimages - This code, however should work in 99.9% of the cases. - """ - separator = "_" - - for filename in list_strings(files): - # Remove Netcdf extension (if any) - f = filename[:-3] if filename.endswith(".nc") else filename - if separator not in f: continue - tokens = f.split(separator) - if tokens[-1] == ext: - found = True - if prefix is not None: found = found and filename.startswith(prefix) - if dataset is not None: found = found and "DS" + str(dataset) in tokens - if image is not None: found = found and "IMG" + str(image) in tokens - if found: return filename - else: - return None - -########################################################################################## - -def abinit_output_iscomplete(output_file): - "Returns True if the abinit output file is complete." - if not os.path.exists(output_file): - return False - - chunk = 5 * 1024 # Read only the last 5Kb of data. - nlines = 10 # Check only in the last 10 lines. - - MAGIC = "Calculation completed." - - with open(output_file, 'r') as f: - size = f.tell() - f.seek(max(size - chunk, 0)) - try: - for line in f.read().splitlines()[-nlines:]: - if MAGIC in line: - return True - except: - pass - - return False - - -# =========================================================================== # - -def is_number(s): - """Returns True if the argument can be made a float.""" - try: - float(s) - return True - except: - return False - -def is_iter(obj): - """Return True if the argument is list-like.""" - return hasattr(obj, '__iter__') - -def is_scalar(obj): - """Return True if the argument is not list-like.""" - return not is_iter - -def flatten(iterable): - """Make an iterable flat, i.e. a 1d iterable object.""" - iterator = iter(iterable) - array, stack = collections.deque(), collections.deque() - while True: - try: - value = next(iterator) - except StopIteration: - if not stack: - return tuple(array) - iterator = stack.pop() - else: - if not isinstance(value, str) \ - and isinstance(value, collections.Iterable): - stack.append(iterator) - iterator = iter(value) - else: - array.append(value) - -def listify(obj): - """Return a flat list out of the argument.""" - if not obj: - obj = list() - elif is_iter(obj): - obj = list(flatten(obj)) - else: - obj = [obj] - return deepcopy(obj) - - -class StringColorizer(object): - COLORS = {"default": "", - "blue": "\x1b[01;34m", - "cyan": "\x1b[01;36m", - "green": "\x1b[01;32m", - "red": "\x1b[01;31m", - # lighting colors. - #"lred": "\x1b[01;05;37;41m" - } - - def __init__(self, stream): - self.has_colours = stream_has_colours(stream) - - def __call__(self, string, colour): - if self.has_colours: - code = self.COLORS.get(colour, "") - if code: - return code + string + "\x1b[00m" - else: - return string - else: - return string diff --git a/abipy/integration_tests/TODO.md b/abipy/integration_tests/TODO.md index d350a84b..e3a1077e 100644 --- a/abipy/integration_tests/TODO.md +++ b/abipy/integration_tests/TODO.md @@ -141,3 +141,5 @@ TODO list: it and can report this value in the final band structure. * DONE ecut is not reported in the GSR file. Similar problem for the k-sampling (see SIGRES.nc) + +* FFTProf (use file extension and interface it with abiopen) diff --git a/abipy/htc/fftbench.py b/abipy/tools/fftprof.py similarity index 88% rename from abipy/htc/fftbench.py rename to abipy/tools/fftprof.py index 47bbb1d0..741fa50e 100644 --- a/abipy/htc/fftbench.py +++ b/abipy/tools/fftprof.py @@ -2,7 +2,7 @@ Python interface to fftprof. Provides objects to benchmark the FFT libraries used by ABINIT and plot the results with matplotlib. """ -from __future__ import print_function, division, unicode_literals +from __future__ import print_function, division, unicode_literals, absolute_import import sys import os @@ -12,9 +12,10 @@ import numpy as np from subprocess import Popen, PIPE from monty.os.path import which from monty.fnmatch import WildCard +from abipy.tools.plotting import add_fig_kwargs, get_ax_fig_plt __all__ = [ - "FFT_Benchmark", + "FFTBenchmark", ] _color_fftalg = { @@ -58,14 +59,10 @@ class FFT_Test(object): def __init__(self, ecut, ngfft, wall_time, info): """ Args: - ecut: - List of cutoff energies in Hartree. - ngfft: - List with FFT divisions. - wall_time: - List of wall_time for the different ecut. - info: - Dictionary with extra information. + ecut: List of cutoff energies in Hartree. + ngfft: List with FFT divisions. + wall_time: List of wall_time for the different ecut. + info: Dictionary with extra information. """ self.ecut = np.asarray(ecut) self.necut = len(ecut) @@ -110,11 +107,11 @@ class FFT_Test(object): return line -class FFT_Benchmark(object): +class FFTBenchmark(object): """ Container class storing the results of the FFT benchmark. - Use the class method `from_file` to generate a new instance. + Use the class method ``from_file`` to generate a new instance. """ @classmethod def from_file(cls, fileobj): @@ -128,8 +125,8 @@ class FFT_Benchmark(object): alg = test.fftalg if alg not in self._fftalgs: self._fftalgs.append(alg) - self._fftalgs.sort() + self._fftalgs.sort() self.tests = [t for t in FFT_tests] def iter_fftalgs(self): @@ -143,26 +140,13 @@ class FFT_Benchmark(object): if t.fftalg == fftalg: lst.append(t) return lst + @add_fig_kwargs def plot(self, exclude_algs=None, exclude_threads=None, **kwargs): """ Plot the wall-time and the speed-up. - - Args: - - ============== ============================================================== - kwargs Meaning - ============== ============================================================== - show True to show the figure (Default) - - savefig 'abc.png' or 'abc.eps'* to save the figure to a file. - ============== =============================================================== """ - show = kwargs.pop("show", True) - savefig = kwargs.pop("savefig", None) - import matplotlib.pyplot as plt fig = plt.figure() - ax1 = fig.add_subplot(2, 1, 1) exc_algs = [] @@ -206,7 +190,6 @@ class FFT_Benchmark(object): ax2.set_xticklabels(labels, fontdict=None, minor=False, rotation=35) for fftalg in self.iter_fftalgs(): - if fftalg in exc_algs: continue tests = self.tests_with_fftalg(fftalg) for t in tests: @@ -247,12 +230,6 @@ class FFT_Benchmark(object): ideal = [1.0 for i in range(t0.necut)] ax2.plot(t0.ecut, ideal, "b-", linewidth=3.0) - if show: - plt.show() - - if savefig is not None: - fig.savefig(os.path.abspath(savefig)) - return fig @@ -260,12 +237,9 @@ def parse_prof_file(fileobj): """ Parse the PROF file generated by fftprof.F90. - Args: - fileobj: - String or file-like object. + Args: fileobj: String or file-like object. - Returns: - Instance of FFT_Benchmark. + Returns: Instance of FFTBenchmark. """ # The file contains # 1) An initial header with info on the routine. @@ -321,14 +295,14 @@ def parse_prof_file(fileobj): except IndexError: line = None - # Instantiate FFT_Benchmark. + # Instantiate FFTBenchmark. fft_tests = [] - for (idx, wall_time) in enumerate(data): + for idx, wall_time in enumerate(data): info = info_of_test[idx] Test = FFT_Test(ecut, ngfft, wall_time, info) fft_tests.append(Test) - return FFT_Benchmark(title, fft_tests) + return FFTBenchmark(title, fft_tests) class FFTProfError(Exception): @@ -397,5 +371,5 @@ class FFTProf(object): if self.verbose: print("About to plot prof_file: ", prof_file) - bench = FFT_Benchmark.from_file(prof_file) + bench = FFTBenchmark.from_file(prof_file) bench.plot() diff --git a/abipy/tools/tests/test_duck.py b/abipy/tools/tests/test_duck.py index ba773135..82b392eb 100644 --- a/abipy/tools/tests/test_duck.py +++ b/abipy/tools/tests/test_duck.py @@ -2,7 +2,6 @@ """Tests for duck module.""" from __future__ import division, print_function, absolute_import, unicode_literals - import numpy as np from abipy.core.testing import AbipyTest diff --git a/abipy/tools/tests/test_fftprof.py b/abipy/tools/tests/test_fftprof.py new file mode 100644 index 00000000..40ab268b --- /dev/null +++ b/abipy/tools/tests/test_fftprof.py @@ -0,0 +1,34 @@ +# coding: utf-8 +"""Tests for fftprof module.""" +from __future__ import division, print_function, absolute_import, unicode_literals + +import os +import abipy.data as abidata + +from abipy.core.testing import AbipyTest +from abipy.tools.fftprof import FFTBenchmark + + +class FftProfTest(AbipyTest): + + def test_fft_benchmark(self): + """Testing FFt benchmark.""" + + # Plot the benchmark results saved in the files + path = os.path.join(abidata.dirpath, "PROF_fourwf_cplex0_option3_istwfk1") + bench = FFTBenchmark.from_file(path) + + assert bench.title.strip() == " Benchmark: routine = fourwf, cplex = 0, option= 3, istwfk= 1".strip() + assert len(bench.tests) == 4 + assert len(bench.tests_with_fftalg(112)) == 2 + assert len(bench.tests_with_fftalg(512)) == 2 + + test0 = bench.tests_with_fftalg(112)[0] + test1 = bench.tests_with_fftalg(112)[1] + assert len(test0.ecut) == len(test0.ngfft) + assert str(test0) + test0.speedup_wrt(test1) + + if self.has_matplotlib(): + #test0.plot_ax() + assert bench.plot(show=False) diff --git a/docs/api/abio_api.rst b/docs/api/abio_api.rst index 051f9f11..9f8d7a89 100644 --- a/docs/api/abio_api.rst +++ b/docs/api/abio_api.rst @@ -84,3 +84,11 @@ abio Package :members: :undoc-members: :show-inheritance: + +:mod:`variable` Module +---------------------- + +.. automodule:: abipy.abio.variable + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/tools_api.rst b/docs/api/tools_api.rst index ba10ea1b..b3c1f976 100644 --- a/docs/api/tools_api.rst +++ b/docs/api/tools_api.rst @@ -53,6 +53,14 @@ tools Package :undoc-members: :show-inheritance: +:mod:`fftprof` Module +--------------------- + +.. automodule:: abipy.tools.fftprof + :members: + :undoc-members: + :show-inheritance: + :mod:`iotools` Module ---------------------