From 9ab6116b289325a3b8f8ae006e9da69ff52435c7 Mon Sep 17 00:00:00 2001 From: Oleg Peil Date: Sun, 15 Feb 2015 16:42:48 +0100 Subject: [PATCH] * Completed group parser * Added a consistency check for groups and shells * Added scenario descriptions to the test suite --- python/converters/vasp/python/inpconf.py | 134 +++++++++++++++--- .../vasp/test/inpconf/test_inpconf.py | 35 ++++- 2 files changed, 149 insertions(+), 20 deletions(-) diff --git a/python/converters/vasp/python/inpconf.py b/python/converters/vasp/python/inpconf.py index 00e1f709..1856e8b3 100644 --- a/python/converters/vasp/python/inpconf.py +++ b/python/converters/vasp/python/inpconf.py @@ -241,10 +241,12 @@ class ConfigParameters: issue_warning("Shell indices are not uniform or not starting from 1. " "This might be an indication of a incorrect setup.") -# Parse shell parameters - self.shells = {} +# Parse shell parameters and put them into a list sorted according to the original indices + self.shells = [] for ind in sh_inds: - self.shells[ind] = {} + shell = {} +# Store the original user-defined index + shell['user_index'] = ind section = self.sh_sections[ind] # Shell required parameters @@ -252,14 +254,14 @@ class ConfigParameters: print print " Required shell parameters:" parsed = self.parse_parameter_set(section, self.sh_required, exception=True) - self.shells[ind].update(parsed) + shell.update(parsed) # Shell optional parameters if self.verbosity > 0: print print " Optional shell parameters:" parsed = self.parse_parameter_set(section, self.sh_optional, exception=False) - self.shells[ind].update(parsed) + shell.update(parsed) # Group required parameters # Must be given if no group is explicitly specified @@ -268,14 +270,16 @@ class ConfigParameters: print print " Required group parameters:" parsed = self.parse_parameter_set(section, self.gr_required, exception=False) - self.shells[ind].update(parsed) + shell.update(parsed) # Group optional parameters if self.verbosity > 0: print print " Optional group parameters:" parsed = self.parse_parameter_set(section, self.gr_optional, exception=False) - self.shells[ind].update(parsed) + shell.update(parsed) + + self.shells.append(shell) ################################################################################ # @@ -294,20 +298,66 @@ class ConfigParameters: self.ngroups = len(sec_groups) + self.groups = [] +# Parse group parameters + for section in sec_groups: + group = {} + +# Extract group index (FIXME: do we really need it?) + gr_patt2 = re.compile('group +([0-9]*)$', re.IGNORECASE) + try: + gr_ind = int(gr_patt2.match(section).groups()[0]) + except (ValueError, AttributeError): + raise ValueError("Failed to extract group index from a group name: %s"%(section)) + group['index'] = gr_ind + +# Group required parameters + if self.verbosity > 0: + print + print " Required group parameters:" + parsed = self.parse_parameter_set(section, self.gr_required, exception=True) + group.update(parsed) + +# Group optional parameters + if self.verbosity > 0: + print + print " Optional group parameters:" + parsed = self.parse_parameter_set(section, self.gr_optional, exception=False) + group.update(parsed) + + self.groups.append(group) + +# Sort groups according to indices defined in the config-file + if self.ngroups > 0: + self.groups.sort(key=lambda g: g['index']) + +################################################################################ +# +# groups_shells_consistency() +# +################################################################################ + def groups_shells_consistency(self): + """ + Ensures consistency between groups and shells. + In particular: + - if no groups are explicitly defined and only shell is defined create + a group automatically + - check the existance of all shells referenced in the groups + - check that all shells are referenced in the groups + """ # Special case: no groups is defined if self.ngroups == 0: # Check that 'nshells = 1' assert self.nshells == 1, "At least one group must be defined if there are more than one shells." # Otherwise create a single group taking group information from [Shell] section - self.groups = [{}] + self.groups.append({}) # Check that the single '[Shell]' section contains enough information # and move it to the `groups` dictionary - ind = self.sh_sections.keys()[0] try: for par in self.gr_required.keys(): key = self.gr_required[par][0] - value = self.shells[ind].pop(key) + value = self.shells[0].pop(key) self.groups[0][key] = value except KeyError: message = "One [Shell] section is specified but no explicit [Group] section is provided." @@ -316,16 +366,67 @@ class ConfigParameters: raise KeyError(message) # Do the same for optional group parameters, but do not raise an exception - for par in self.gr_required.keys(): + for par in self.gr_optional.keys(): try: - key = self.gr_required[par][0] + key = self.gr_optional[par][0] value = self.shells[ind].pop(key) self.groups[0][key] = value except KeyError: continue - - self.groups.update({'shells': [self.shells[ind]]}) - +# Add the index of the single shell into the group + self.groups.update({'shells': 0}) + +# +# Consistency checks +# +# Check the existance of shells referenced in the groups + def find_shell_by_user_index(uindex): + for ind, shell in enumerate(self.shells): + if shell['user_index'] == uindex: + return shell + raise KeyError + + sh_inds = [] + for group in self.groups: + gr_shells = group['shells'] + for user_ind in gr_shells: + try: + ind, shell = find_shell_by_user_index(user_ind) + except KeyError: + raise Exception("Shell %i reference in group '%s' does not exist"%(user_ind, group['index']) + sh_inds.append(ind) + +# If [Shell] section contains (potentiall conflicting) group parameters +# remove them and issue a warning +# First, required group parameters + for par in self.gr_required.keys(): + try: + key = self.gr_required[par][0] + value = shell.pop(key) + mess = (" Redundant group parameter '%s' in [Shell] section" + " %i is discarded"%(par, user_ind)) + issue_warning(mess) + except KeyError: + continue + +# Second, optional group parameters + for par in self.gr_optional.keys(): + try: + key = self.gr_optional[par][0] + value = shell.pop(key) + mess = (" Redundant group parameter '%s' in [Shell] section" + " %i is discarded"%(par, user_ind)) + issue_warning(mess) + except KeyError: + continue + + sh_refs_used = list(set(sh_inds)) + sh_refs_used.sort() + +# Check that all shells are referenced in the groups + assert sh_refs_used == range(self.nshells), "Some shells are not inside any of the groups" + + ################################################################################ # @@ -340,8 +441,9 @@ class ConfigParameters: self.parse_shells() self.parse_groups() + self.groups_shells_consistency() -# Output list of dictionaries +# Return a output_pars = [{} for isec in xrange(nsections)] for isec, section in enumerate(sections): print "Section: %s"%(section) diff --git a/python/converters/vasp/test/inpconf/test_inpconf.py b/python/converters/vasp/test/inpconf/test_inpconf.py index c5f270c5..fec83dd6 100644 --- a/python/converters/vasp/test/inpconf/test_inpconf.py +++ b/python/converters/vasp/test/inpconf/test_inpconf.py @@ -228,6 +228,7 @@ class TestSpecialParsers(unittest.TestCase): expected = {1: {'lshell': 2, 'ion_list': np.array([4, 5, 6, 7])}, 2: {'lshell': 1, 'ion_list': np.array([0, 1, 2, 3]), 'tmatrix': np.array([[ 0., 1., 0.], [ 1., 0., 0.], [ 0., 0., 1.]])}} +# ...lousy way to test equality of two dictionaries containing numpy arrays self.assertSetEqual(set(res.keys()), set(expected.keys())) arr = res[1].pop('ion_list') @@ -257,12 +258,37 @@ class TestSpecialParsers(unittest.TestCase): Scenarios: - - **if** no [Group] section exists and more than one [Shell] section - is given **raise** AssertionError - **if** a [Group] section does not contain all required parameters **raise** Exception - - **if** two correct [Shell] sections are defined - **return** a dictionary of shell parameters + - **if** a correct group section is defined **return** a list of dictionaries + """ +# Scenario 1 +# conf_pars = ConfigParameters('test6.cfg') +# err_mess = "At least one group" +# with self.assertRaisesRegexp(AssertionError, err_mess): +# conf_pars.parse_shells() + +################################################################################ +# +# test_groups_shells_consistency() +# +################################################################################ + def test_groups_shells_consistency(self): + """ + Function: + + def groups_shells_consistency(self) + + Scenarios: + + - **if** no [Group] section exists and more than one [Shell] section + is given **raise** AssertionError + - **if** no [Group] section exists but the single [Shell] section + does not contain required group information **raise** KeyError + - **if** a shell referenced in a group does not exist + **raise** Exception + - **if** not all defined shells are referenced in the groups + **raise** Exception """ # Scenario 1 conf_pars = ConfigParameters('test6.cfg') @@ -271,6 +297,7 @@ class TestSpecialParsers(unittest.TestCase): conf_pars.parse_shells() + if __name__ == '__main__': suite = unittest.TestLoader().loadTestsFromTestCase(TestSpecialParsers) # unittest.TextTestRunner(verbosity=2, buffer=False).run(suite)