def test_xml_delete_att_occurrences_multiple(load_inpxml):

    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_basic import xml_delete_att

    xmltree, schema_dict = load_inpxml(TEST_INPXML_PATH)
    root = xmltree.getroot()

    assert eval_xpath(root,
                      '/fleurInput/atomSpecies/species/mtSphere/@radius') == [
                          '2.20000000', '2.20000000'
                      ]

    xmltree = xml_delete_att(xmltree,
                             '/fleurInput/atomSpecies/species/mtSphere',
                             'radius',
                             occurrences=[0])

    assert eval_xpath(
        root,
        '/fleurInput/atomSpecies/species/mtSphere/@radius') == '2.20000000'
    assert eval_xpath(
        root, "/fleurInput/atomSpecies/species[@name='Pt-1']/mtSphere/@radius"
    ) == '2.20000000'
Example #2
0
def test_xml_set_complex_tag_recursive_complex_multiple(load_inpxml):

    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_xpaths import xml_set_complex_tag

    xmltree, schema_dict = load_inpxml(TEST_INPXML_PATH)
    root = xmltree.getroot()

    changes = {'ldaHIA': [{'addArg': {'key': 'TEST'}}, {'addArg': {'key': 'TEST2'}}]}

    xml_set_complex_tag(xmltree, schema_dict, "/fleurInput/atomSpecies/species[@name='Fe-1']",
                        '/fleurInput/atomSpecies/species', changes)

    nodes = eval_xpath(root, '/fleurInput/atomSpecies/species/ldaHIA/addArg/@key')
    assert [str(node) for node in nodes] == ['TEST', 'TEST2']
def test_xml_create_tag_string_append(load_inpxml):

    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_basic import xml_create_tag

    xmltree, schema_dict = load_inpxml(TEST_INPXML_PATH)
    root = xmltree.getroot()

    tags = [
        'cutoffs', 'scfLoop', 'coreElectrons', 'xcFunctional', 'magnetism',
        'soc', 'prodBasis', 'expertModes', 'geometryOptimization', 'ldaU'
    ]

    node = eval_xpath(root, '/fleurInput/calculationSetup')

    assert [child.tag for child in node.iterchildren()] == tags

    xmltree = xml_create_tag(xmltree, '/fleurInput/calculationSetup',
                             'test_tag')

    node = eval_xpath(root, '/fleurInput/calculationSetup')

    tags.append('test_tag')
    assert [child.tag for child in node.iterchildren()] == tags
Example #4
0
def test_eval_xpath_create_non_existing(load_inpxml):

    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_xpaths import eval_xpath_create

    xmltree, schema_dict = load_inpxml(TEST_INPXML_PATH)
    root = xmltree.getroot()

    assert len(eval_xpath(root, '/fleurInput/atomSpecies/species/ldaU')) == 0

    nodes = eval_xpath_create(xmltree, schema_dict, '/fleurInput/atomSpecies/species/ldaU',
                              '/fleurInput/atomSpecies/species/ldaU')

    assert len(nodes) == 2
    assert [node.getparent().attrib['name'] for node in nodes] == ['Fe-1', 'Pt-1']
Example #5
0
def test_xml_set_complex_tag_differing_xpaths_recursive_complex_single(load_inpxml):

    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_xpaths import xml_set_complex_tag

    xmltree, schema_dict = load_inpxml(TEST_INPXML_PATH)
    root = xmltree.getroot()

    changes = {'electronConfig': {'valenceConfig': 'TEST'}}

    xml_set_complex_tag(xmltree, schema_dict, "/fleurInput/atomSpecies/species[@name='Fe-1']",
                        '/fleurInput/atomSpecies/species', changes)

    nodes = eval_xpath(root, '/fleurInput/atomSpecies/species/electronConfig/valenceConfig/text()')
    assert [str(node) for node in nodes] == ['TEST', '(5p1/2) (5p3/2) (6s1/2) (5d3/2) (5d5/2)']
Example #6
0
def test_xml_set_simple_tag_multiple_dict(load_inpxml):

    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_xpaths import xml_set_simple_tag

    xmltree, schema_dict = load_inpxml(TEST_INPXML_PATH)
    root = xmltree.getroot()

    changes = {'type': 'SCLO', 'n': 21, 'l': 3, 'eDeriv': 0}

    xml_set_simple_tag(xmltree, schema_dict, '/fleurInput/atomSpecies/species', '/fleurInput/atomSpecies/species', 'lo',
                       changes)

    nodes = eval_xpath(root, '/fleurInput/atomSpecies/species/lo')

    assert [node.attrib.items() for node in nodes] == [[(key, str(val)) for key, val in changes.items()]] * 2
Example #7
0
def test_xml_set_simple_tag_single(load_inpxml):

    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_xpaths import xml_set_simple_tag

    xmltree, schema_dict = load_inpxml(TEST_INPXML_PATH)
    root = xmltree.getroot()

    changes = {'s': 2, 'p': 3, 'd': 4, 'f': 5}

    xml_set_simple_tag(xmltree, schema_dict, '/fleurInput/atomSpecies/species', '/fleurInput/atomSpecies/species',
                       'energyParameters', changes)

    nodes = eval_xpath(root, '/fleurInput/atomSpecies/species/energyParameters')

    assert [node.attrib.items() for node in nodes] == [[(key, str(val)) for key, val in changes.items()]] * 2
Example #8
0
def test_xml_set_simple_tag_differing_xpaths(load_inpxml):

    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_xpaths import xml_set_simple_tag

    xmltree, schema_dict = load_inpxml(TEST_INPXML_PATH)
    root = xmltree.getroot()

    changes = [{'type': 'SCLO', 'n': 21, 'l': 3, 'eDeriv': 0}, {'type': 'SCLO', 'n': 22, 'l': 4, 'eDeriv': 0}]

    xml_set_simple_tag(xmltree, schema_dict, "/fleurInput/atomSpecies/species[@name='Fe-1']",
                       '/fleurInput/atomSpecies/species', 'lo', changes)

    nodes = eval_xpath(root, '/fleurInput/atomSpecies/species/lo')

    assert [node.attrib.items() for node in nodes] == [[('type', 'SCLO'), ('n', '21'), ('l', '3'), ('eDeriv', '0')],
                                                       [('type', 'SCLO'), ('n', '22'), ('l', '4'), ('eDeriv', '0')],
                                                       [('type', 'SCLO'), ('l', '1'), ('n', '5'), ('eDeriv', '0')]]
Example #9
0
def test_xml_add_number_to_attrib_differing_int(load_inpxml):

    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_xpaths import xml_add_number_to_attrib

    xmltree, schema_dict = load_inpxml(TEST_INPXML_PATH)
    root = xmltree.getroot()

    xml_add_number_to_attrib(xmltree, schema_dict, '/fleurInput/calculationSetup/cutoffs',
                             '/fleurInput/calculationSetup/cutoffs', 'numbands', 100)

    numbands = eval_xpath(root, '/fleurInput/calculationSetup/cutoffs/@numbands')

    assert numbands == '100'

    with pytest.raises(ValueError, match='You are trying to write a float to an integer attribute'):
        xml_add_number_to_attrib(xmltree, schema_dict, '/fleurInput/calculationSetup/cutoffs',
                                 '/fleurInput/calculationSetup/cutoffs', 'numbands', 55.5)
def test_clear_xml():
    """
    Test of the clear_xml function
    """
    from lxml import etree
    from masci_tools.util.xml.common_functions import eval_xpath, clear_xml
    parser = etree.XMLParser(attribute_defaults=True, encoding='utf-8')
    xmltree = etree.parse(CLEAR_XML_TEST_FILE, parser)

    #Check that the file contains comments and includes
    root = xmltree.getroot()
    comments = eval_xpath(root, '//comment()', list_return=True)
    assert len(comments) == 3

    include_tags = eval_xpath(
        root,
        '//xi:include',
        namespaces={'xi': 'http://www.w3.org/2001/XInclude'},
        list_return=True)
    assert len(include_tags) == 2

    symmetry_tags = eval_xpath(root, '//symOp', list_return=True)
    assert len(symmetry_tags) == 0

    cleared_tree, all_include_tags = clear_xml(xmltree)
    cleared_root = cleared_tree.getroot()
    old_root = xmltree.getroot()

    assert all_include_tags == {'symmetryOperations'}
    #Make sure that the original tree was not modified
    comments = eval_xpath(old_root, '//comment()', list_return=True)
    assert len(comments) == 3

    #Check that the cleared tree is correct
    comments = eval_xpath(cleared_root, '//comment()', list_return=True)
    assert len(comments) == 0

    include_tags = eval_xpath(
        cleared_root,
        '//xi:include',
        namespaces={'xi': 'http://www.w3.org/2001/XInclude'},
        list_return=True)
    assert len(include_tags) == 0

    symmetry_tags = eval_xpath(cleared_root, '//symOp', list_return=True)
    assert len(symmetry_tags) == 16
Example #11
0
def test_xml_add_number_to_attrib_all_abs(load_inpxml, shift_value, result, occurrences):

    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_xpaths import xml_add_number_to_attrib

    xmltree, schema_dict = load_inpxml(TEST_INPXML_PATH)
    root = xmltree.getroot()

    xml_add_number_to_attrib(xmltree,
                             schema_dict,
                             '/fleurInput/atomSpecies/species/mtSphere',
                             '/fleurInput/atomSpecies/species/mtSphere',
                             'radius',
                             shift_value,
                             occurrences=occurrences)

    radius = eval_xpath(root, '/fleurInput/atomSpecies/species/mtSphere/@radius')

    assert [str(val) for val in radius] == result
Example #12
0
def xml_set_attrib_value_no_create(xmltree, xpath, attributename, attribv, occurrences=None):
    """
    Sets an attribute in a xmltree to a given value. By default the attribute will be set
    on all nodes returned for the specified xpath.

    :param xmltree: an xmltree that represents inp.xml
    :param xpath: a path where to set the attributes
    :param attributename: the attribute name to set
    :param attribv: value or list of values to set (if not str they will be converted with `str(value)`)
    :param occurrences: int or list of int. Which occurence of the node to set. By default all are set.

    :raises ValueError: If the lengths of attribv or occurrences do not match number of nodes

    :returns: xmltree with set attribute
    """
    from masci_tools.io.common_functions import is_sequence

    root = xmltree.getroot()
    nodes = eval_xpath(root, xpath, list_return=True)

    if len(nodes) == 0:
        return xmltree

    if occurrences is not None:
        if not is_sequence(occurrences):
            occurrences = [occurrences]
        try:
            nodes = [nodes[occ] for occ in occurrences]
        except IndexError as exc:
            raise ValueError('Wrong value for occurrences') from exc

    if is_sequence(attribv):
        if len(attribv) != len(nodes):
            raise ValueError(f'Wrong length for attribute values. Expected {len(nodes)} items. Got: {len(attribv)}')
    else:
        attribv = [attribv] * len(nodes)

    attribv = [val if isinstance(val, str) else str(val) for val in attribv]

    for node, value in zip(nodes, attribv):
        node.set(attributename, value)

    return xmltree
Example #13
0
    def get_tag(self, xpath):
        """
        Tries to evaluate an xpath expression for ``inp.xml`` file. If it fails it logs it.

        :param xpath: an xpath expression
        :returns: A node list retrived using given xpath
        """
        from masci_tools.util.xml.common_functions import eval_xpath

        warnings.warn(
            'The get_tag method is deprecated. Instead you can use the load_inpxml method to access '
            'the xmltree and schema of the stored inp.xml. Then the required information can be accessed '
            'via the XML functions in masci-tools or directly',
            DeprecationWarning)

        xmltree, _ = self.load_inpxml()
        root = xmltree.getroot()

        return eval_xpath(root, xpath)
Example #14
0
def test_xml_set_text_create(load_inpxml):

    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_xpaths import xml_set_text

    xmltree, schema_dict = load_inpxml(TEST_INPXML_PATH)
    root = xmltree.getroot()

    with pytest.raises(ValueError, match='Could not set text on path'):
        xml_set_text(xmltree, schema_dict,
                     "/fleurInput/atomSpecies/species[@name='Fe-1']/torgueCalculation/greensfElements/s",
                     '/fleurInput/atomSpecies/species/torgueCalculation/greensfElements/s', [False, False, True, False])

    xml_set_text(xmltree,
                 schema_dict,
                 "/fleurInput/atomSpecies/species[@name='Fe-1']/torgueCalculation/greensfElements/s",
                 '/fleurInput/atomSpecies/species/torgueCalculation/greensfElements/s', [False, False, True, False],
                 create=True)

    assert eval_xpath(root, '/fleurInput/atomSpecies/species/torgueCalculation/greensfElements/s/text()') == 'F F T F'
Example #15
0
def test_xml_set_first_attrib_value_create(load_inpxml):
    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_xpaths import xml_set_first_attrib_value

    xmltree, schema_dict = load_inpxml(TEST_INPXML_PATH)
    root = xmltree.getroot()

    with pytest.raises(ValueError, match="Could not set attribute 'key' on path "):
        xml_set_first_attrib_value(xmltree, schema_dict, "/fleurInput/atomSpecies/species[@name='Fe-1']/ldaHIA/addArg",
                                   '/fleurInput/atomSpecies/species/ldaHIA/addArg', 'key', 'TEST')

    xml_set_first_attrib_value(xmltree,
                               schema_dict,
                               "/fleurInput/atomSpecies/species[@name='Fe-1']/ldaHIA/addArg",
                               '/fleurInput/atomSpecies/species/ldaHIA/addArg',
                               'key',
                               'TEST',
                               create=True)

    assert eval_xpath(root, '/fleurInput/atomSpecies/species/ldaHIA/addArg/@key') == 'TEST'
Example #16
0
def test_xml_create_tag_schema_dict_create_parents(load_inpxml):

    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_xpaths import xml_create_tag_schema_dict

    xmltree, schema_dict = load_inpxml(TEST_INPXML_PATH)
    root = xmltree.getroot()

    with pytest.raises(ValueError, match="Could not create tag 'addArg' because atleast one subtag is missing."):
        xml_create_tag_schema_dict(xmltree, schema_dict, "/fleurInput/atomSpecies/species[@name='Fe-1']/ldaHIA",
                                   '/fleurInput/atomSpecies/species/ldaHIA', 'addArg')

    xml_create_tag_schema_dict(xmltree,
                               schema_dict,
                               "/fleurInput/atomSpecies/species[@name='Fe-1']/ldaHIA",
                               '/fleurInput/atomSpecies/species/ldaHIA',
                               'addArg',
                               create_parents=True)

    assert len(eval_xpath(root, '/fleurInput/atomSpecies/species/ldaHIA/addArg', list_return=True)) == 1
def test_eval_xpath(caplog):
    """
    Test of the eval_xpath function
    """
    from lxml import etree
    from masci_tools.util.xml.common_functions import eval_xpath

    parser = etree.XMLParser(attribute_defaults=True, encoding='utf-8')
    xmltree = etree.parse(CLEAR_XML_TEST_FILE, parser)
    root = xmltree.getroot()

    scfLoop = eval_xpath(root, '//scfLoop')
    assert isinstance(scfLoop, etree._Element)

    scfLoop = eval_xpath(root, '//scfLoop', list_return=True)
    assert len(scfLoop) == 1
    assert isinstance(scfLoop[0], etree._Element)

    include_tags = eval_xpath(
        root,
        '//xi:include',
        namespaces={'xi': 'http://www.w3.org/2001/XInclude'},
        list_return=True)
    assert len(include_tags) == 2
    assert isinstance(include_tags[0], etree._Element)

    species_z = eval_xpath(root, "//species[@name='Cu-1']/@atomicNumber")
    assert species_z == '29'

    ldau_tags = eval_xpath(root, "//species[@name='Cu-1']/ldaU")
    assert ldau_tags == []

    with pytest.raises(ValueError,
                       match='There was a XpathEvalError on the xpath:'):
        ldau_tags = eval_xpath(root, "//species/[@name='Cu-1']/ldaU")

    with caplog.at_level(logging.WARNING):
        with pytest.raises(ValueError,
                           match='There was a XpathEvalError on the xpath:'):
            ldau_tags = eval_xpath(root,
                                   "//species/[@name='Cu-1']/ldaU",
                                   logger=LOGGER)

    assert 'There was a XpathEvalError on the xpath:' in caplog.text
Example #18
0
def xml_set_text_no_create(xmltree, xpath, text, occurrences=None):
    """
    Sets the text of a tag in a xmltree to a given value.
    By default the text will be set on all nodes returned for the specified xpath.

    :param xmltree: an xmltree that represents inp.xml
    :param xpath: a path where to set the text
    :param text: value or list of values to set (if not str they will be converted with `str(value)`)
    :param occurrences: int or list of int. Which occurrence of the node to set. By default all are set.

    :raises ValueError: If the lengths of text or occurrences do not match number of nodes

    :returns: xmltree with set text
    """
    from masci_tools.io.common_functions import is_sequence

    root = xmltree.getroot()
    nodes = eval_xpath(root, xpath, list_return=True)

    if len(nodes) == 0:
        return xmltree

    if occurrences is not None:
        if not is_sequence(occurrences):
            occurrences = [occurrences]
        try:
            nodes = [nodes[occ] for occ in occurrences]
        except IndexError as exc:
            raise ValueError('Wrong value for occurrences') from exc

    if is_sequence(text):
        if len(text) != len(nodes):
            raise ValueError(f'Wrong length for text values. Expected {len(nodes)} items. Got: {text}')
    else:
        text = [text] * len(nodes)

    for node, text_val in zip(nodes, text):
        node.text = text_val

    return xmltree
def test_xml_set_text_no_create_errors(load_inpxml):

    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_basic import xml_set_text_no_create

    xmltree, schema_dict = load_inpxml(TEST_INPXML_PATH)
    root = xmltree.getroot()

    kpoints_xpath = '/fleurInput/cell/bzIntegration/kPointLists/kPointList/kPoint'

    with pytest.raises(ValueError, match='Wrong value for occurrences'):
        xml_set_text_no_create(xmltree, kpoints_xpath, 'test', occurrences=5)

    with pytest.raises(ValueError, match='Wrong length for text values'):
        xml_set_text_no_create(xmltree,
                               kpoints_xpath, ['test', 'too_much'],
                               occurrences=[1])

    assert eval_xpath(root, f'{kpoints_xpath}/text()') == [
        '   -0.250000     0.250000     0.000000',
        '    0.250000     0.250000     0.000000'
    ]
Example #20
0
def test_xml_set_simple_tag_create_single(load_inpxml):

    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_xpaths import xml_set_simple_tag

    xmltree, schema_dict = load_inpxml(TEST_INPXML_PATH)
    root = xmltree.getroot()

    changes = {'ne': 6}

    with pytest.raises(ValueError, match="Could not create tag 'realAxis' because atleast one subtag is missing"):
        xml_set_simple_tag(xmltree, schema_dict, '/fleurInput/calculationSetup/greensFunction',
                           '/fleurInput/calculationSetup/greensFunction', 'realAxis', changes)

    xml_set_simple_tag(xmltree,
                       schema_dict,
                       '/fleurInput/calculationSetup/greensFunction',
                       '/fleurInput/calculationSetup/greensFunction',
                       'realAxis',
                       changes,
                       create_parents=True)
    node = eval_xpath(root, '/fleurInput/calculationSetup/greensFunction/realAxis')

    assert node.attrib.items() == [('ne', '6')]
def inpxml_parser(inpxmlfile,
                  version=None,
                  parser_info_out=None,
                  strict=False,
                  debug=False):
    """
    Parses the given inp.xml file to a python dictionary utilizing the schema
    defined by the version number to validate and corretly convert to the dictionary

    :param inpxmlfile: either path to the inp.xml file, opened file handle or a xml etree to be parsed
    :param version: version string to enforce that a given schema is used
    :param parser_info_out: dict, with warnings, info, errors, ...
    :param strict: bool if True  and no parser_info_out is provided any encountered error will immediately be raised

    :return: python dictionary with the parsed inp.xml

    :raises ValueError: If the validation against the schema failed, or an irrecoverable error
                        occured during parsing
    :raises FileNotFoundError: If no Schema file for the given version was found

    """

    __parser_version__ = '0.3.0'
    logger = logging.getLogger(__name__)

    parser_log_handler = None
    if parser_info_out is not None or not strict:
        if parser_info_out is None:
            parser_info_out = {}

        logging_level = logging.INFO
        if debug:
            logging_level = logging.DEBUG
        logger.setLevel(logging_level)

        parser_log_handler = DictHandler(parser_info_out,
                                         WARNING='parser_warnings',
                                         ERROR='parser_errors',
                                         INFO='parser_info',
                                         DEBUG='parser_debug',
                                         CRITICAL='parser_critical',
                                         ignore_unknown_levels=True,
                                         level=logging_level)

        logger.addHandler(parser_log_handler)

    if strict:
        logger = None

    if logger is not None:
        logger.info('Masci-Tools Fleur inp.xml Parser v%s', __parser_version__)

    if isinstance(inpxmlfile, etree._ElementTree):
        xmltree = inpxmlfile
    else:
        parser = etree.XMLParser(attribute_defaults=True, encoding='utf-8')
        try:
            xmltree = etree.parse(inpxmlfile, parser)
        except etree.XMLSyntaxError as msg:
            if logger is not None:
                logger.exception('Failed to parse input file')
            raise ValueError(f'Failed to parse input file: {msg}') from msg

    if version is None:
        version = eval_xpath(xmltree, '//@fleurInputVersion', logger=logger)
        version = str(version)
        if version is None:
            if logger is not None:
                logger.error('Failed to extract inputVersion')
            raise ValueError('Failed to extract inputVersion')

    if logger is not None:
        logger.info('Got Fleur input file with file version %s', version)
    schema_dict = InputSchemaDict.fromVersion(version, logger=logger)

    ignore_validation = schema_dict['inp_version'] != version

    xmltree, _ = clear_xml(xmltree)
    root = xmltree.getroot()

    constants = read_constants(root, schema_dict, logger=logger)

    try:
        validate_xml(
            xmltree,
            schema_dict.xmlschema,
            error_header='Input file does not validate against the schema')
    except etree.DocumentInvalid as err:
        errmsg = str(err)
        logger.warning(errmsg)
        if not ignore_validation:
            if logger is not None:
                logger.exception(errmsg)
            raise ValueError(errmsg) from err

    if schema_dict.xmlschema.validate(xmltree) or ignore_validation:
        inp_dict = inpxml_todict(root, schema_dict, constants, logger=logger)
    else:
        msg = 'Input file does not validate against the schema: Reason is unknown'
        if logger is not None:
            logger.warning(msg)
        if not ignore_validation:
            if logger is not None:
                logger.exception(msg)
            raise ValueError(msg)

    if parser_log_handler is not None:
        if logger is not None:
            logger.removeHandler(parser_log_handler)

    return inp_dict
def xml_set_complex_tag(xmltree,
                        schema_dict,
                        xpath,
                        base_xpath,
                        attributedict,
                        create=False):
    """
    Recursive Function to correctly set tags/attributes for a given tag.
    Goes through the attributedict and decides based on the schema_dict, how the corresponding
    key has to be handled.

    Supports:

        - attributes
        - tags with text only
        - simple tags, i.e. only attributes (can be optional single/multiple)
        - complex tags, will recursively create/modify them

    :param xmltree: an xmltree that represents inp.xml
    :param schema_dict: InputSchemaDict containing all information about the structure of the input
    :param xpath: a path where to set the attributes
    :param base_xpath: path where to place a new tag without complex syntax ([] conditions and so on)
    :param tag_name: name of the tag to set
    :param attributedict: Keys in the dictionary correspond to names of tags and the values are the modifications
                          to do on this tag (attributename, subdict with changes to the subtag, ...)
    :param create: bool optional (default False), if True and the path, where the complex tag is
                   set does not exist it is created

    :returns: xmltree with changes to the complex tag
    """
    from masci_tools.util.xml.xml_setters_basic import xml_delete_tag
    from masci_tools.util.xml.common_functions import check_complex_xpath, split_off_tag

    check_complex_xpath(xmltree, base_xpath, xpath)

    tag_info = schema_dict['tag_info'][base_xpath]
    _, tag_name = split_off_tag(base_xpath)

    if create:
        #eval complex tag and ggf create
        eval_xpath_create(xmltree,
                          schema_dict,
                          xpath,
                          base_xpath,
                          create_parents=True)

    for key, val in attributedict.items():

        if key not in tag_info['complex'] | tag_info['simple'] | tag_info[
                'attribs']:
            raise ValueError(
                f"The key '{key}' is not expected for this version of the input for the '{tag_name}' tag. "
                f"Allowed tags are: {sorted((tag_info['complex']|tag_info['simple']).original_case.values())}"
                f"Allowed attributes are: {sorted(tag_info['attribs'].original_case.values())}"
            )

        key = (tag_info['complex'] | tag_info['simple']
               | tag_info['attribs']).original_case[key]

        sub_xpath = f'{xpath}/{key}'
        sub_base_xpath = f'{base_xpath}/{key}'
        if key in tag_info['attribs']:
            xml_set_attrib_value(xmltree,
                                 schema_dict,
                                 xpath,
                                 base_xpath,
                                 key,
                                 val,
                                 create=create)

        elif key in tag_info['text']:
            xml_set_text(xmltree,
                         schema_dict,
                         sub_xpath,
                         sub_base_xpath,
                         val,
                         create=create)

        elif key in tag_info['simple']:
            xml_set_simple_tag(xmltree,
                               schema_dict,
                               xpath,
                               base_xpath,
                               key,
                               val,
                               create_parents=create)

        elif key not in tag_info[
                'several']:  #Complex tag but only one (electronConfig)

            # eval and ggf create tag at right place.
            eval_xpath_create(xmltree,
                              schema_dict,
                              sub_xpath,
                              sub_base_xpath,
                              create_parents=create)

            xmltree = xml_set_complex_tag(xmltree,
                                          schema_dict,
                                          sub_xpath,
                                          sub_base_xpath,
                                          val,
                                          create=create)

        else:
            # policy: we DELETE all existing tags, and create new ones from the given parameters.
            xml_delete_tag(xmltree, sub_xpath)

            if isinstance(val, dict):
                val = [val]

            for indx in range(0, len(val)):
                xml_create_tag_schema_dict(xmltree,
                                           schema_dict,
                                           xpath,
                                           base_xpath,
                                           key,
                                           create_parents=create)

            for indx, tagdict in enumerate(val):
                for k in range(
                        len(eval_xpath(xmltree, sub_xpath, list_return=True))
                        // len(val)):
                    current_elem_xpath = f'{sub_xpath}[{k*len(val)+indx+1}]'
                    xmltree = xml_set_complex_tag(xmltree,
                                                  schema_dict,
                                                  current_elem_xpath,
                                                  sub_base_xpath,
                                                  tagdict,
                                                  create=create)

    return xmltree
def xml_add_number_to_attrib(xmltree,
                             schema_dict,
                             xpath,
                             base_xpath,
                             attributename,
                             add_number,
                             mode='abs',
                             occurrences=None):
    """
    Adds a given number to the attribute value in a xmltree. By default the attribute will be shifted
    on all nodes returned for the specified xpath.
    If there are no nodes under the specified xpath an error is raised

    :param xmltree: an xmltree that represents inp.xml
    :param schema_dict: InputSchemaDict containing all information about the structure of the input
    :param xpath: a path where to set the attributes
    :param base_xpath: path where to place a new tag without complex syntax ([] conditions and so on)
    :param attributename: the attribute name to change
    :param add_number: number to add/multiply with the old attribute value
    :param mode: str (either `rel` or `abs`).
                 `rel` multiplies the old value with `add_number`
                 `abs` adds the old value and `add_number`
    :param occurrences: int or list of int. Which occurence of the node to set. By default all are set.

    :raises ValueError: If the attribute is unknown or cannot be float or int
    :raises ValueError: If the evaluation of the old values failed
    :raises ValueError: If a float result is written to a integer attribute

    :returns: xmltree with shifted attribute
    """
    from masci_tools.util.schema_dict_util import read_constants
    from masci_tools.util.xml.converters import convert_xml_attribute
    from masci_tools.io.common_functions import is_sequence
    from masci_tools.util.xml.common_functions import check_complex_xpath, split_off_attrib, split_off_tag

    check_complex_xpath(xmltree, base_xpath, xpath)

    if attributename not in schema_dict['attrib_types']:
        raise ValueError(
            f"You try to shift the attribute:'{attributename}' , but the key is unknown to the fleur plug-in"
        )

    possible_types = schema_dict['attrib_types'][attributename]

    if not etree.iselement(xmltree):
        constants = read_constants(xmltree.getroot(), schema_dict)
    else:
        constants = read_constants(xmltree, schema_dict)

    if 'float' not in possible_types and \
       'float_expression' not in possible_types and \
       'int' not in possible_types:
        raise ValueError(
            f"Given attribute name '{attributename}' is not float or int")

    attribs = schema_dict['tag_info'][base_xpath]['attribs']
    _, tag_name = split_off_tag(base_xpath)
    if attributename not in attribs:
        raise ValueError(
            f"The key '{attributename}' is not expected for this version of the input for the '{tag_name}' tag. "
            f'Allowed attributes are: {attribs.original_case.values()}')
    attributename = attribs.original_case[attributename]

    if not xpath.endswith(f'/@{attributename}'):
        xpath = '/@'.join([xpath, attributename])

    stringattribute = eval_xpath(xmltree, xpath, list_return=True)

    tag_xpath, attributename = split_off_attrib(xpath)

    if len(stringattribute) == 0:
        raise ValueError(
            f"No attribute values found for '{attributename}'. Cannot add number"
        )

    attribvalues, _ = convert_xml_attribute(stringattribute,
                                            possible_types,
                                            constants=constants,
                                            list_return=True)

    if occurrences is not None:
        if not is_sequence(occurrences):
            occurrences = [occurrences]
        try:
            attribvalues = [attribvalues[occ] for occ in occurrences]
        except IndexError as exc:
            raise ValueError('Wrong value for occurrences') from exc

    if mode == 'abs':
        attribvalues = [value + float(add_number) for value in attribvalues]
    elif mode == 'rel':
        attribvalues = [value * float(add_number) for value in attribvalues]

    if 'float' in possible_types or 'float_expression' in possible_types:
        pass
    elif 'int' in possible_types:
        if any(not value.is_integer() for value in attribvalues):
            raise ValueError(
                'You are trying to write a float to an integer attribute')
        attribvalues = [int(value) for value in attribvalues]

    xmltree = xml_set_attrib_value(xmltree,
                                   schema_dict,
                                   tag_xpath,
                                   base_xpath,
                                   attributename,
                                   attribvalues,
                                   occurrences=occurrences)

    return xmltree
def xml_create_tag_schema_dict(xmltree,
                               schema_dict,
                               xpath,
                               base_xpath,
                               element,
                               create_parents=False,
                               occurrences=None):
    """
    This method evaluates an xpath expression and creates a tag in a xmltree under the
    returned nodes.
    If there are no nodes evaluated the subtags can be created with `create_parents=True`

    The tag is always inserted in the correct place if a order is enforced by the schema

    :param xmltree: an xmltree that represents inp.xml
    :param schema_dict: InputSchemaDict containing all information about the structure of the input
    :param xpath: a path where to place a new tag
    :param base_xpath: path where to place a new tag without complex syntax ([] conditions and so on)
    :param element: a tag name or etree Element to be created
    :param create_parents: bool optional (default False), if True and the given xpath has no results the
                           the parent tags are created recursively
    :param occurrences: int or list of int. Which occurence of the parent nodes to create a tag.
                        By default all nodes are used.

    :raises ValueError: If the nodes are missing and `create_parents=False`

    :returns: xmltree with created tags
    """
    from masci_tools.util.xml.xml_setters_basic import xml_create_tag
    from masci_tools.util.xml.common_functions import check_complex_xpath, split_off_tag

    check_complex_xpath(xmltree, base_xpath, xpath)

    tag_info = schema_dict['tag_info'][base_xpath]

    if not etree.iselement(element):
        #Get original case of the tag
        element_name = (tag_info['simple']
                        | tag_info['complex']).original_case[element]
        try:
            element = etree.Element(element_name)
        except ValueError as exc:
            raise ValueError(
                f"Failed to construct etree Element from '{element_name}'"
            ) from exc
    else:
        element_name = element.tag

    if len(tag_info['order']) == 0:
        tag_order = None
    else:
        tag_order = tag_info['order']

    several_tags = element_name in tag_info['several']

    parent_nodes = eval_xpath(xmltree, xpath, list_return=True)

    if len(parent_nodes) == 0:
        if create_parents:
            parent_xpath, parent_name = split_off_tag(base_xpath)
            complex_parent_xpath, _ = split_off_tag(xpath)
            xmltree = xml_create_tag_schema_dict(xmltree,
                                                 schema_dict,
                                                 complex_parent_xpath,
                                                 parent_xpath,
                                                 parent_name,
                                                 create_parents=create_parents)
        else:
            raise ValueError(
                f"Could not create tag '{element_name}' because atleast one subtag is missing. "
                'Use create=True to create the subtags')

    return xml_create_tag(xmltree,
                          xpath,
                          element,
                          tag_order=tag_order,
                          occurrences=occurrences,
                          several=several_tags)
def xml_set_attrib_value(xmltree,
                         schema_dict,
                         xpath,
                         base_xpath,
                         attributename,
                         attribv,
                         occurrences=None,
                         create=False):
    """
    Sets an attribute in a xmltree to a given value. By default the attribute will be set
    on all nodes returned for the specified xpath.
    If there are no nodes under the specified xpath a tag can be created with `create=True`.
    The attribute values are converted automatically according to the types of the attribute
    with :py:func:`~masci_tools.util.xml.converters.convert_attribute_to_xml()` if they
    are not `str` already.

    :param xmltree: an xmltree that represents inp.xml
    :param schema_dict: InputSchemaDict containing all information about the structure of the input
    :param xpath: a path where to set the attributes
    :param base_xpath: path where to place a new tag without complex syntax ([] conditions and so on)
    :param attributename: the attribute name to set
    :param attribv: value or list of values to set
    :param occurrences: int or list of int. Which occurence of the node to set. By default all are set.
    :param create: bool optional (default False), if True the tag is created if is missing

    :raises ValueError: If the conversion to string failed
    :raises ValueError: If the tag is missing and `create=False`
    :raises ValueError: If the attributename is not allowed on the base_xpath

    :returns: xmltree with set attribute
    """

    from masci_tools.util.xml.xml_setters_basic import xml_set_attrib_value_no_create
    from masci_tools.util.xml.converters import convert_attribute_to_xml
    from masci_tools.util.xml.common_functions import check_complex_xpath, split_off_tag

    check_complex_xpath(xmltree, base_xpath, xpath)

    if create:
        nodes = eval_xpath_create(xmltree,
                                  schema_dict,
                                  xpath,
                                  base_xpath,
                                  create_parents=True,
                                  occurrences=occurrences,
                                  list_return=True)
    else:
        nodes = eval_xpath(xmltree, xpath, list_return=True)

    if len(nodes) == 0:
        raise ValueError(
            f"Could not set attribute '{attributename}' on path '{xpath}' "
            'because atleast one subtag is missing. '
            'Use create=True to create the subtags')

    _, tag_name = split_off_tag(base_xpath)

    attribs = schema_dict['tag_info'][base_xpath]['attribs']
    if attributename not in attribs:
        raise ValueError(
            f"The key '{attributename}' is not expected for this version of the input for the '{tag_name}' tag. "
            f'Allowed attributes are: {attribs.original_case.values()}')
    attributename = attribs.original_case[attributename]

    converted_attribv, suc = convert_attribute_to_xml(
        attribv, schema_dict['attrib_types'][attributename])

    if '/fleurInput/forceTheorem/' in base_xpath and attributename in (
            'theta', 'phi', 'ef_shift'):
        #Special case for theta and phi attributes on forceTheorem tags
        #In Max5/5.1 They are entered as FleurDouble but can be a list. Since
        #the attribute setting so far does not support this we convert the values explicitely
        #here
        if isinstance(converted_attribv, list):
            converted_attribv = ' '.join(converted_attribv)

    return xml_set_attrib_value_no_create(xmltree,
                                          xpath,
                                          attributename,
                                          converted_attribv,
                                          occurrences=occurrences)
Example #26
0
def extract_corelevels(outxmlfile, options=None):
    """
    Extracts corelevels out of out.xml files

    :params outxmlfile: path to out.xml file

    :param options: A dict: 'iteration' : X/'all'
    :returns corelevels: A list of the form:

    .. code-block:: python

            [atomtypes][spin][dict={atomtype : '', corestates : list_of_corestates}]
            [atomtypeNumber][spin]['corestates'][corestate number][attribute]
            get corelevel energy of first atomtype, spin1, corelevels[0][0]['corestates'][i]['energy']

    :example of output:

    .. code-block:: python

                        [[{'atomtype': '     1',
                        'corestates': [{'energy': -3.6489930627,
                                        'j': ' 0.5',
                                        'l': ' 0',
                                        'n': ' 1',
                                        'weight': 2.0}],
                        'eigenvalue_sum': '     -7.2979861254',
                        'kin_energy': '     13.4757066163',
                        'spin': '1'}],
                        [{'atomtype': '     2',
                        'corestates': [{'energy': -3.6489930627,
                                        'j': ' 0.5',
                                        'l': ' 0',
                                        'n': ' 1',
                                        'weight': 2.0}],
                        'eigenvalue_sum': '     -7.2979861254',
                        'kin_energy': '     13.4757066163',
                        'spin': '1'}]]

    """
    ##########################################
    #1. read out.xml in etree

    #2. get all species
    #3. get number of atom types and their species

    #4 get corelevel dimension from atoms types.

    #5 init saving arrays:
    #list length number of atom types, which contains dictionaries:
    # in the form { 'species' : species, 'coresetup' : '', 'atom' : W , 'corelevels' : []} lists of corelevels from last iteration (Do i want all iterations, optional?) Or do I even want a dictionaries of corelevels? (but coresetup is in atom type info

    #6 parse corelevels:
    # get last iteration
    # fill corelevel list
    #######################################
    ########################
    #XPATHS to maintain

    species_xpath = '/fleurOutput/inputData/atomSpecies'
    iteration_xpath = '/fleurOutput/scfLoop/iteration'
    atomgroup_xpath = '/fleurOutput/inputData/atomGroups'
    coreconfig_xpath = 'electronConfig/coreConfig/text()'
    valenceconfig_xpath = 'electronConfig/valenceConfig/text()'
    state_occ_xpath = 'electronConfig/stateOccupation'

    relcoreStates_xpath = 'coreStates'
    relpos_xpath = 'relPos'
    abspos_xpath = 'absPos'
    filmpos_xpath = 'filmPos'
    #TODO all the attribute names...
    ######################

    #1. read out.xml in etree
    # TODO this should be common, moved somewhere else and importet
    parsed_data = {}
    outfile_broken = False
    parse_xml = True
    parser = etree.XMLParser(recover=False)  #, remove_blank_text=True)
    parser_info = {'parser_warnings': [], 'unparsed': []}

    try:
        tree = etree.parse(outxmlfile, parser)
    except etree.XMLSyntaxError:
        outfile_broken = True
        #print 'broken xml'
        parser_info['parser_warnings'].append(
            'The out.xml file is broken I try to repair it.')

    if outfile_broken:
        #repair xmlfile and try to parse what is possible.
        parser = etree.XMLParser(recover=True)  #, remove_blank_text=True)
        try:
            tree = etree.parse(outxmlfile, parser)
        except etree.XMLSyntaxError:
            parser_info['parser_warnings'].append(
                'Skipping the parsing of the xml file. Repairing was not possible.'
            )
            parse_xml = False

    #if parse_xml:
    root = tree.getroot()

    # 2. get all species from input
    # get element, name, coreStates
    # TODO why can this not be eval_xpath2?
    species_nodes = eval_xpath(root, species_xpath)
    species_atts = {}
    species_names = []
    for species in species_nodes:
        species_name = species.get('name')
        species_corestates = species.get('coreStates')
        species_element = species.get('element')
        species_atomicnumber = species.get('atomicNumber')
        species_magMom = species.get('magMom')
        #TODO sometimes not in inp.xml... what if it is not there
        coreconfig = eval_xpath(species, coreconfig_xpath)
        valenceconfig = eval_xpath(species, valenceconfig_xpath)
        state_occ = eval_xpath(species, state_occ_xpath, list_return=True)

        #parse state occ
        state_results = []
        for tag in state_occ:  #always a list?
            state = tag.get('state')
            spinUp = tag.get('spinUp')
            spinDown = tag.get('spinDown')
            state_results.append({state: [spinUp, spinDown]})

        species_atts[species_name] = {
            'name': species_name,
            'corestates': species_corestates,
            'element': species_element,
            'atomgroups': [],
            'mag_mom': species_magMom,
            'atomic_number': species_atomicnumber,
            'coreconfig': coreconfig,
            'valenceconfig': valenceconfig,
            'stateOccupation': state_results
        }
        species_names.append(species_name)

    #3. get number of atom types and their species from input
    atomtypes = []
    atomgroup_nodes = eval_xpath(root, atomgroup_xpath)  #/fleurinp/
    # always a list?
    for atomgroup in atomgroup_nodes:
        types_dict = {}
        group_species = atomgroup.get('species')
        if group_species in species_names:
            species_atts[group_species]['atomgroups'].append(atomgroup)
            element = species_atts[group_species]['element']
            atomicnumber = int(species_atts[group_species]['atomic_number'])
            coreconf = species_atts[group_species]['coreconfig']
            valenceconf = species_atts[group_species]['valenceconfig']
            stateocc = species_atts[group_species]['stateOccupation']
            a =   eval_xpath(atomgroup, relpos_xpath, list_return=True) \
                + eval_xpath(atomgroup, abspos_xpath, list_return=True) \
                + eval_xpath(atomgroup, filmpos_xpath, list_return=True)  # always list
            natoms = len(a)
            types_dict = {
                'species': group_species,
                'element': element,
                'atomic_number': atomicnumber,
                'coreconfig': coreconf,
                'valenceconfig': valenceconf,
                'stateOccupation': stateocc,
                'natoms': natoms
            }
        atomtypes.append(types_dict)

    #natomgroup = len(atomgroup_nodes)
    #print(natomgroup)
    corelevels = []

    #4 get corelevel dimension from atoms types.
    #5 init saving arrays:
    #6 parse corelevels:

    iteration_nodes = eval_xpath(root, iteration_xpath, list_return=True)
    nIteration = len(iteration_nodes)
    if nIteration >= 1:
        iteration_to_parse = iteration_nodes[-1]  #TODO:Optional all or other
        #print iteration_to_parse
        corestatescards = eval_xpath(iteration_to_parse,
                                     relcoreStates_xpath,
                                     list_return=True)
        # maybe does not return a list...
        for atype in atomtypes:  # spin=2 is already in there
            corelevels.append([])

        for corestatescard in corestatescards:
            corelv = parse_state_card(corestatescard, iteration_to_parse,
                                      parser_info)
            corelevels[int(corelv['atomtype']) - 1].append(
                corelv)  # is corelv['atomtype'] always an integer?

    #print parser_info
    #pprint(corelevels[0][1]['corestates'][2]['energy'])
    #corelevels[atomtypeNumber][spin]['corestates'][corestate number][attribute]
    return corelevels, atomtypes
Example #27
0
def parse_state_card(corestateNode, iteration_node, parser_info=None):
    """
    Parses the ONE core state card

    :params corestateNode: an etree element (node), of a fleur output corestate card
    :params iteration_node: an etree element, iteration node
    :params jspin: integer 1 or 2

    :returns: a pythondict of type:

    .. code-block:: python

            {'eigenvalue_sum' : eigenvalueSum,
             'corestates': states,
             'spin' : spin,
             'kin_energy' : kinEnergy,
             'atomtype' : atomtype}

    """
    ##### all xpath of density convergence card (maintain) ########
    coreStates_xpath = 'coreStates'
    state_xpath = 'state'

    units_name = 'units'
    value_name = 'value'
    distance_name = 'distance'

    n_name = 'n'
    j_name = 'j'
    l_name = 'l'
    energy_name = 'energy'
    weight_name = 'weight'
    spin_name = 'spin'
    kinEnergy_name = 'kinEnergy'
    eigenvalueSum_name = 'eigValSum'
    lostElectrons_name = 'lostElectrons'
    atomtype_name = 'atomType'
    #######
    if parser_info is None:
        parser_info = {'parser_warnings': []}

    atomtype = get_xml_attribute(corestateNode, atomtype_name)

    kinEnergy = get_xml_attribute(corestateNode, kinEnergy_name)
    vE2, suc = convert_to_float(kinEnergy)
    eigenvalueSum = get_xml_attribute(corestateNode, eigenvalueSum_name)
    vE2, suc = convert_to_float(eigenvalueSum)

    spin = get_xml_attribute(corestateNode, spin_name)
    #print('spin {}'.format(spin))
    #states = corestateNode.xpath(
    #for state in states:

    # get all corestate tags, (atomtypes * spin)
    #corestateNodes = eval_xpath(iteration_node, coreStates_xpath, parser_info)
    # for every corestate tag parse the attributes

    # some only the first interation, then get all state tags of the corestate tag (atom depended)
    # parse each core state #Attention to spin
    states = []
    corestates = eval_xpath(corestateNode, state_xpath,
                            list_return=True)  #, parser_info)

    for corestate in corestates:  # be careful that corestates is a list
        state_dict = {}
        n_state = get_xml_attribute(corestate, n_name)
        l_state = get_xml_attribute(corestate, l_name)
        j_state = get_xml_attribute(corestate, j_name)
        energy, suc = convert_to_float(
            get_xml_attribute(corestate, energy_name))
        weight, suc = convert_to_float(
            get_xml_attribute(corestate, weight_name))
        state_dict = {
            'n': n_state,
            'l': l_state,
            'j': j_state,
            'energy': energy,
            'weight': weight
        }
        states.append(state_dict)

    core_states = {
        'eigenvalue_sum': eigenvalueSum,
        'corestates': states,
        'spin': spin,
        'kin_energy': kinEnergy,
        'atomtype': atomtype
    }
    return core_states
def test_single_value_tag(caplog):
    """
    Test of the evaluate_single_value_tag function
    """
    from lxml import etree
    from masci_tools.util.schema_dict_util import evaluate_single_value_tag, get_tag_xpath
    from masci_tools.util.xml.common_functions import eval_xpath

    schema_dict = outschema_dict_34

    parser = etree.XMLParser(attribute_defaults=True,
                             recover=False,
                             encoding='utf-8')
    xmltree = etree.parse(TEST_OUTXML_PATH, parser)
    root = xmltree.getroot()

    iteration_xpath = get_tag_xpath(schema_dict, 'iteration')
    iteration = eval_xpath(root, iteration_xpath, list_return=True)[0]

    expected = {'comment': None, 'units': 'Htr', 'value': -4204.714048254}
    totalEnergy = evaluate_single_value_tag(iteration, schema_dict,
                                            'totalEnergy',
                                            FLEUR_DEFINED_CONSTANTS)
    assert totalEnergy == expected

    expected = {'value': -4204.714048254}
    totalEnergy = evaluate_single_value_tag(iteration,
                                            schema_dict,
                                            'totalEnergy',
                                            FLEUR_DEFINED_CONSTANTS,
                                            only_required=True)
    assert totalEnergy == expected

    with pytest.raises(
            ValueError,
            match=
            'The tag total_energy has no possible paths with the current specification.'
    ):
        evaluate_single_value_tag(root, schema_dict, 'total_energy',
                                  FLEUR_DEFINED_CONSTANTS)
    with pytest.raises(
            ValueError,
            match=
            'The tag totalCharge has multiple possible paths with the current specification.'
    ):
        evaluate_single_value_tag(root, schema_dict, 'totalCharge',
                                  FLEUR_DEFINED_CONSTANTS)

    expected = {'units': None, 'value': 63.9999999893}
    with pytest.raises(
            ValueError,
            match=
            "Failed to evaluate singleValue from tag totalCharge: Has no 'units' attribute"
    ):
        evaluate_single_value_tag(iteration,
                                  schema_dict,
                                  'totalCharge',
                                  FLEUR_DEFINED_CONSTANTS,
                                  contains='allElectronCharges',
                                  not_contains='fixed')

    with caplog.at_level(logging.WARNING):
        totalCharge = evaluate_single_value_tag(iteration,
                                                schema_dict,
                                                'totalCharge',
                                                FLEUR_DEFINED_CONSTANTS,
                                                contains='allElectronCharges',
                                                not_contains='fixed',
                                                logger=LOGGER)
    assert totalCharge == expected
    assert "Failed to evaluate singleValue from tag totalCharge: Has no 'units' attribute" in caplog.text

    expected = {'value': 63.9999999893}
    totalCharge = evaluate_single_value_tag(iteration,
                                            schema_dict,
                                            'totalCharge',
                                            FLEUR_DEFINED_CONSTANTS,
                                            contains='allElectronCharges',
                                            not_contains='fixed',
                                            ignore=['units'])
    assert totalCharge == expected
Example #29
0
def compress_fleuroutxml(outxmlfilepath,
                         dest_file_path=None,
                         delete_eig=True,
                         iterations_to_keep=None):
    """
    Compresses a fleur out.xml file by deleting certain things
    like eigenvalues tags and/or iterations from it

    :param outxmlfilepath: (absolut) file path
    :type outxmlfilepath: str
    :param dest_file_path: (absolut) for the compressed file to be saved, if no desitination file path is given the file is overriden in place (default)!
    :type dest_file_path: str, optional
    :param delete_eig:  if eigenvalues are deleted from file default is True
    :type delete_eig: boolean, optional
    :param iterations_to_keep: index of 'till' whihc iterations to be keep, i.e '-2' means only last two, '15' default (None) is keep all
    :type iterations_to_keep: int

     ###
     usage example:
     outxmldes = '/Users/broeder/test/FePt_out_test.xml'
     outxmlsrc = '/Users/broeder/test/FePt_out.xml'
     compress_fleuroutxml(outxmlsrc, dest_file_path=outxmldes, iterations_to_keep=14)
     compress_fleuroutxml(outxmlsrc, dest_file_path=outxmldes, iterations_to_keep=-1)


    """
    from masci_tools.util.xml.common_functions import eval_xpath
    from masci_tools.util.xml.xml_setters_basic import xml_delete_tag
    from lxml import etree

    xpath_eig = '/fleurOutput/scfLoop/iteration/eigenvalues'
    xpath_iter = '/fleurOutput/scfLoop/iteration'
    tree = None
    parser = etree.XMLParser(recover=False)
    outfile_broken = False
    try:
        tree = etree.parse(outxmlfilepath, parser)
    except etree.XMLSyntaxError:
        outfile_broken = True
        print('broken')

    if outfile_broken:
        # repair xmlfile and try to parse what is possible.
        parser = etree.XMLParser(recover=True)
        try:
            tree = etree.parse(outxmlfilepath, parser)
        except etree.XMLSyntaxError:
            parse_xml = False
            successful = False
            print('failed to parse broken file, I abort.')
            return

    if tree is None:
        print('xml tree is None, should not happen, ...')
        return

    # delete eigenvalues (all)
    if delete_eig:
        new_etree = xml_delete_tag(tree, xpath_eig)

    # delete certain iterations
    if iterations_to_keep is not None:
        root = new_etree.getroot()
        iteration_nodes = eval_xpath(root, xpath_iter, list_return=True)
        n_iters = len(iteration_nodes)
        print(n_iters)
        if iterations_to_keep < 0:
            # the first element has 1 (not 0) in xpath expresions
            position_keep = n_iters + iterations_to_keep + 1
            delete_xpath = xpath_iter + '[position()<{}]'.format(
                int(position_keep))
        else:
            delete_xpath = xpath_iter + '[position()>{}]'.format(
                int(iterations_to_keep))

        if abs(iterations_to_keep) > n_iters:
            print(
                'Warning: iterations_to_keep is larger then the number of iterations'
                ' in the given out.xml file, I keep all.')
        else:
            print(delete_xpath)
            new_etree = xml_delete_tag(new_etree, delete_xpath)

    if dest_file_path is None:
        dest_file_path = outxmlfilepath  # overwrite file
    if new_etree.getroot() is not None:  #otherwise write fails
        new_etree.write(dest_file_path)
    else:
        print(
            'new_etree has no root..., I cannot write to proper xml, skipping this now'
        )
    return
def xml_set_simple_tag(xmltree,
                       schema_dict,
                       xpath,
                       base_xpath,
                       tag_name,
                       changes,
                       create_parents=False):
    """
    Sets one or multiple `simple` tag(s) in an xmltree. A simple tag can only hold attributes and has no
    subtags.
    If the tag can occur multiple times all existing tags are DELETED and new ones are written.
    If the tag only occurs once it will automatically be created if its missing.

    :param xmltree: an xmltree that represents inp.xml
    :param schema_dict: InputSchemaDict containing all information about the structure of the input
    :param xpath: a path where to set the attributes
    :param base_xpath: path where to place a new tag without complex syntax ([] conditions and so on)
    :param tag_name: name of the tag to set
    :param changes: list of dicts or dict with the changes. Elements in list describe multiple tags.
                    Keys in the dictionary correspond to {'attributename': attributevalue}
    :param create_parents: bool optional (default False), if True and the path, where the simple tags are
                           set does not exist it is created

    :returns: xmltree with set simple tags
    """
    from masci_tools.util.xml.xml_setters_basic import xml_delete_tag
    from masci_tools.util.xml.common_functions import check_complex_xpath

    check_complex_xpath(xmltree, base_xpath, xpath)

    tag_info = schema_dict['tag_info'][base_xpath]

    tag_xpath = f'{xpath}/{tag_name}'
    tag_base_xpath = f'{base_xpath}/{tag_name}'

    if tag_name in tag_info['several']:
        #change_dict can either be a list or a dict
        if isinstance(changes, dict):
            changes = [changes]

        # policy: we DELETE all existing tags, and create new ones from the given parameters.
        xml_delete_tag(xmltree, tag_xpath)

        for indx in range(0, len(changes)):
            xml_create_tag_schema_dict(xmltree,
                                       schema_dict,
                                       xpath,
                                       base_xpath,
                                       tag_name,
                                       create_parents=create_parents)

        for indx, change in enumerate(changes):
            for attrib, value in change.items():
                occurrences = [
                    k * len(changes) + indx for k in range(
                        len(eval_xpath(xmltree, tag_xpath, list_return=True))
                        // len(changes))
                ]
                xml_set_attrib_value(xmltree,
                                     schema_dict,
                                     tag_xpath,
                                     tag_base_xpath,
                                     attrib,
                                     value,
                                     occurrences=occurrences)
    else:
        if not isinstance(changes, dict):
            raise ValueError(
                f"Tag '{tag_name}' can only occur once. But 'set_simple_tag' got a list"
            )

        #eval and ggf. create tag
        eval_xpath_create(xmltree,
                          schema_dict,
                          tag_xpath,
                          tag_base_xpath,
                          create_parents=create_parents)

        for attrib, value in changes.items():
            xml_set_attrib_value(xmltree, schema_dict, tag_xpath,
                                 tag_base_xpath, attrib, value)

    return xmltree