예제 #1
0
    def test_bandsexport_single_kp(self):
        """
        Plot band for single k-point (issue #2462).
        """
        kpnts = KpointsData()
        kpnts.set_kpoints([[0., 0., 0.]])

        bands = BandsData()
        bands.set_kpointsdata(kpnts)
        bands.set_bands([[1.0, 2.0]])
        bands.store()

        # matplotlib
        options = [str(bands.id), '--format', 'mpl_singlefile']
        res = self.cli_runner.invoke(cmd_bands.bands_export,
                                     options,
                                     catch_exceptions=False)
        self.assertIn(
            b'p.scatter', res.stdout_bytes,
            'The string p.scatter was not found in the bands mpl export')

        # gnuplot
        with self.cli_runner.isolated_filesystem():
            options = [str(bands.id), '--format', 'gnuplot', '-o', 'bands.gnu']
            self.cli_runner.invoke(cmd_bands.bands_export,
                                   options,
                                   catch_exceptions=False)
            with open('bands.gnu', 'r') as gnu_file:
                res = gnu_file.read()
                self.assertIn(
                    'vectors nohead', res,
                    'The string "vectors nohead" was not found in the gnuplot script'
                )
예제 #2
0
def create_aiida_bands_data(fleurinp, retrieved):
    """
    Creates :py:class:`aiida.orm.BandsData` object containing the kpoints and eigenvalues
    from the `banddos.hdf` file of the calculation

    :param fleurinp: :py:class:`~aiida_fleur.data.fleurinp.FleurinpData` for the calculation
    :param retrieved: :py:class:`aiida.orm.FolderData` for the bandstructure calculation

    :returns: :py:class:`aiida.orm.BandsData` for the bandstructure calculation

    :raises: ExitCode 300, banddos.hdf file is missing
    :raises: ExitCode 310, banddos.hdf reading failed
    :raises: ExitCode 320, reading kpointsdata from Fleurinp failed
    """
    from masci_tools.io.parsers.hdf5 import HDF5Reader, HDF5TransformationError
    from masci_tools.io.parsers.hdf5.recipes import FleurSimpleBands  #no projections only eigenvalues for now
    from aiida.engine import ExitCode

    try:
        kpoints = fleurinp.get_kpointsdata_ncf(only_used=True)
    except ValueError as exc:
        return ExitCode(
            320,
            message=f'Retrieving kpoints data from fleurinp failed with: {exc}'
        )

    if 'banddos.hdf' in retrieved.list_object_names():
        try:
            with retrieved.open('banddos.hdf', 'rb') as f:
                with HDF5Reader(f) as reader:
                    data, attributes = reader.read(recipe=FleurSimpleBands)
        except (HDF5TransformationError, ValueError) as exc:
            return ExitCode(310,
                            message=f'banddos.hdf reading failed with: {exc}')
    else:
        return ExitCode(300,
                        message='banddos.hdf file not in the retrieved files')

    bands = BandsData()
    bands.set_kpointsdata(kpoints)

    nkpts, nbands = attributes['nkpts'], attributes['nbands']
    eigenvalues = data['eigenvalues_up'].reshape((nkpts, nbands))
    if 'eigenvalues_down' in data:
        eigenvalues_dn = data['eigenvalues_down'].reshape((nkpts, nbands))
        eigenvalues = [eigenvalues, eigenvalues_dn]

    bands.set_bands(eigenvalues, units='eV')

    bands.label = 'output_banddos_wc_bands'
    bands.description = (
        'Contains BandsData for the bandstructure calculation')

    return bands
예제 #3
0
        def connect_structure_bands(strct):  # pylint: disable=unused-argument
            alat = 4.
            cell = np.array([
                [alat, 0., 0.],
                [0., alat, 0.],
                [0., 0., alat],
            ])

            kpnts = KpointsData()
            kpnts.set_cell(cell)
            kpnts.set_kpoints([[0., 0., 0.], [0.1, 0.1, 0.1]])

            bands = BandsData()
            bands.set_kpointsdata(kpnts)
            bands.set_bands([[1.0, 2.0], [3.0, 4.0]])
            return bands
예제 #4
0
        def connect_structure_bands(structure):
            alat = 4.
            cell = np.array([
                [alat, 0., 0.],
                [0., alat, 0.],
                [0., 0., alat],
            ])

            k = KpointsData()
            k.set_cell(cell)
            k.set_kpoints([[0., 0., 0.], [0.1, 0.1, 0.1]])

            b = BandsData()
            b.set_kpointsdata(k)
            b.set_bands([[1.0, 2.0], [3.0, 4.0]])

            return b
예제 #5
0
def spin_dependent_subparser(out_info_dict):
    """This find the projection and bands arrays from the out_file and out_info_dict. Used to handle the different
    possible spin-cases in a convenient manner.

    :param out_info_dict: contains various technical internals useful in parsing
    :return: ProjectionData, BandsData parsed from out_file
    """

    out_file = out_info_dict['out_file']
    spin_down = out_info_dict['spin_down']
    od = out_info_dict  #using a shorter name for convenience
    #   regular expressions needed for later parsing
    WaveFraction1_re = re.compile(r'\=(.*?)\*')  # state composition 1
    WaveFractionremain_re = re.compile(r'\+(.*?)\*')  # state comp 2
    FunctionId_re = re.compile(r'\#(.*?)\]')  # state identity
    # primes arrays for the later parsing
    num_wfc = len(od['wfc_lines'])
    bands = np.zeros([od['k_states'], od['num_bands']])
    projection_arrays = np.zeros([od['k_states'], od['num_bands'], num_wfc])

    try:
        for i in range(od['k_states']):
            if spin_down:
                i += od['k_states']
            # grabs band energy
            for j in range(i * od['num_bands'], (i + 1) * od['num_bands'], 1):
                out_ind = od['e_lines'][j]
                try:
                    # post ~6.3 output format "e ="
                    val = out_file[out_ind].split()[2]
                    float(val)
                except ValueError:
                    # pre ~6.3 output format? "==== e("
                    val = out_file[out_ind].split()[4]
                bands[i % od['k_states']][j % od['num_bands']] = val
                #subloop grabs pdos
                wave_fraction = []
                wave_id = []
                for k in range(od['e_lines'][j] + 1, od['psi_lines'][j], 1):
                    out_line = out_file[k]
                    wave_fraction += WaveFraction1_re.findall(out_line)
                    wave_fraction += WaveFractionremain_re.findall(out_line)
                    wave_id += FunctionId_re.findall(out_line)
                if len(wave_id) != len(wave_fraction):
                    raise IndexError
                for l in range(len(wave_id)):
                    wave_id[l] = int(wave_id[l])
                    wave_fraction[l] = float(wave_fraction[l])
                    #sets relevant values in pdos_array
                    projection_arrays[i % od['k_states']][j % od['num_bands']][
                        wave_id[l] - 1] = wave_fraction[l]
    except IndexError:
        raise QEOutputParsingError(
            'the standard out file does not comply with the official documentation.'
        )

    bands_data = BandsData()
    # Attempts to retrieve the kpoints from the parent calc
    parent_calc = out_info_dict['parent_calc']
    try:
        parent_kpoints = parent_calc.get_incoming(
            link_label_filter='kpoints').one().node
    except ValueError:
        raise QEOutputParsingError(
            'The parent had no input kpoints! Cannot parse from this!')
    try:
        if len(od['k_vect']) != len(parent_kpoints.get_kpoints()):
            raise AttributeError
        bands_data.set_kpointsdata(parent_kpoints)
    except AttributeError:
        bands_data.set_kpoints(od['k_vect'].astype(float))

    bands_data.set_bands(bands, units='eV')

    orbitals = out_info_dict['orbitals']
    if len(orbitals) != np.shape(projection_arrays[0, 0, :])[0]:
        raise QEOutputParsingError('There was an internal parsing error, '
                                   ' the projection array shape does not agree'
                                   ' with the number of orbitals')
    projection_data = ProjectionData()
    projection_data.set_reference_bandsdata(bands_data)
    projections = [projection_arrays[:, :, i] for i in range(len(orbitals))]

    # Do the bands_check manually here
    for projection in projections:
        if np.shape(projection) != np.shape(bands):
            raise AttributeError('Projections not the same shape as the bands')

    #insert here some logic to assign pdos to the orbitals
    pdos_arrays = spin_dependent_pdos_subparser(out_info_dict)
    energy_arrays = [out_info_dict['energy']] * len(orbitals)
    projection_data.set_projectiondata(orbitals,
                                       list_of_projections=projections,
                                       list_of_energy=energy_arrays,
                                       list_of_pdos=pdos_arrays,
                                       bands_check=False)
    # pdos=pdos_arrays
    return bands_data, projection_data
예제 #6
0
    def parse(self, **kwargs):  # noqa: MC0001  - is mccabe too complex funct -
        """
        Receives in input a dictionary of retrieved nodes. Does all the logic here.
        """
        from aiida.engine import ExitCode

        parser_info = {}
        parser_info['parser_info'] = 'AiiDA Siesta Parser V. {}'.format(
            self._version)

        try:
            output_folder = self.retrieved
        except exceptions.NotExistent:
            raise OutputParsingError("Folder not retrieved")

        output_path, messages_path, xml_path, json_path, bands_path, basis_enthalpy_path = \
            self._fetch_output_files(output_folder)

        if xml_path is None:
            raise OutputParsingError("Xml file not retrieved")
        xmldoc = get_parsed_xml_doc(xml_path)
        result_dict = get_dict_from_xml_doc(xmldoc)

        if output_path is None:
            raise OutputParsingError("output file not retrieved")

        output_dict = dict(
            list(result_dict.items()) + list(parser_info.items()))

        warnings_list = []

        if json_path is not None:
            from .json_time import get_timing_info
            global_time, timing_decomp = get_timing_info(json_path)
            if global_time is None:
                warnings_list.append(["Cannot fully parse the time.json file"])
            else:
                output_dict["global_time"] = global_time
                output_dict["timing_decomposition"] = timing_decomp

        if basis_enthalpy_path is not None:
            the_file = open(basis_enthalpy_path)
            bas_enthalpy = float(the_file.read().split()[0])
            the_file.close()
            output_dict["basis_enthalpy"] = bas_enthalpy
            output_dict["basis_enthalpy_units"] = "eV"
        else:
            warnings_list.append(["BASIS_ENTHALPY file not retrieved"])

        have_errors_to_analyse = False
        if messages_path is None:
            # Perhaps using an old version of Siesta
            warnings_list.append([
                'WARNING: No MESSAGES file, could not check if calculation terminated correctly'
            ])
        else:
            have_errors_to_analyse = True
            #succesful when "INFO: Job completed" is present in message files
            succesful, from_message = self._get_warnings_from_file(
                messages_path)
            warnings_list.append(from_message)
        output_dict["warnings"] = warnings_list

        # An output_parametrs port is always return, even if only parser's info are present
        output_data = Dict(dict=output_dict)
        self.out('output_parameters', output_data)

        #
        # When using floating sites, Siesta associates 'atomic positions' to them, and
        # the structure and forces in the XML file include these fake atoms.
        # In order to return physical structures and forces, we need to remove them.
        # Recall that the input structure is the physical one, and the floating sites
        # are specified in the 'basis' input
        #
        physical_structure = self.node.inputs.structure
        number_of_real_atoms = len(physical_structure.sites)

        # If the structure has changed, save it
        #
        if output_dict['variable_geometry']:
            in_struc = self.node.inputs.structure
            # The next function never fails. If problems arise, the initial structure is
            # returned. The input structure is also necessary because the CML file
            # traditionally contains only the atomic symbols and not the site names.
            # The returned structure does not have any floating atoms, they are removed
            # in the `get_last_structure` call.
            success, out_struc = get_last_structure(xmldoc, in_struc)
            if not success:
                self.logger.warning(
                    "Problem in parsing final structure, returning inp structure in output_structure"
                )

            self.out('output_structure', out_struc)

        # Attempt to parse forces and stresses. In case of failure "None" is returned.
        # Therefore the function never crashes
        forces, stress = get_final_forces_and_stress(xmldoc)
        if forces is not None and stress is not None:
            from aiida.orm import ArrayData
            arraydata = ArrayData()
            arraydata.set_array('forces',
                                np.array(forces[0:number_of_real_atoms]))
            arraydata.set_array('stress', np.array(stress))
            self.out('forces_and_stress', arraydata)

        #Attempt to parse the ion files. Files ".ion.xml" are not produced by siesta if ions file are used
        #in input (`user-basis = T`). This explains the first "if" statement. The SiestaCal input is called
        #`ions__El` (El is the element label) therefore we look for the str "ions" in any of the inputs name.
        if not any(["ions" in inp for inp in self.node.inputs]):  #pylint: disable=too-many-nested-blocks
            from aiida_siesta.data.ion import IonData
            ions = {}
            #Ions from the structure
            in_struc = self.node.inputs.structure
            for kind in in_struc.get_kind_names():
                ion_file_name = kind + ".ion.xml"
                if ion_file_name in output_folder._repository.list_object_names(
                ):
                    ion_file_path = os.path.join(
                        output_folder._repository._get_base_folder().abspath,
                        ion_file_name)
                    ions[kind] = IonData(ion_file_path)
                else:
                    self.logger.warning(f"no ion file retrieved for {kind}")
            #Ions from floating_sites
            if "basis" in self.node.inputs:
                basis_dict = self.node.inputs.basis.get_dict()
                if "floating_sites" in basis_dict:
                    floating_kinds = []
                    for orb in basis_dict["floating_sites"]:
                        if orb["name"] not in floating_kinds:
                            floating_kinds.append(orb["name"])
                            ion_file_name = orb["name"] + ".ion.xml"
                            if ion_file_name in output_folder._repository.list_object_names(
                            ):
                                ion_file_path = os.path.join(
                                    output_folder._repository._get_base_folder(
                                    ).abspath, ion_file_name)
                                ions[orb["name"]] = IonData(ion_file_path)
                            else:
                                self.logger.warning(
                                    f"no ion file retrieved for {orb['name']}")
            #Return the outputs
            if ions:
                self.out('ion_files', ions)

        # Error analysis
        if have_errors_to_analyse:
            # No metter if "INFO: Job completed" is present (succesfull) or not, we check for known
            # errors. They might apprear as WARNING (therefore with succesful True) or FATAL
            # (succesful False)
            for line in from_message:
                if u'split options' in line:
                    min_split = get_min_split(output_path)
                    if min_split:
                        self.logger.error(
                            "Error in split_norm option. Minimum value is {}".
                            format(min_split))
                        return self.exit_codes.SPLIT_NORM
                if u'sys::die' in line:
                    #This is the situation when siesta dies with no specified error
                    #to be reported in "MESSAGES", unfortunately some interesting cases
                    #are treated in this way, we explore the .out file for more insights.
                    if is_polarization_problem(output_path):
                        return self.exit_codes.BASIS_POLARIZ
                if u'SCF_NOT_CONV' in line:
                    return self.exit_codes.SCF_NOT_CONV
                if u'GEOM_NOT_CONV' in line:
                    return self.exit_codes.GEOM_NOT_CONV

        #Because no known error has been found, attempt to parse bands if requested
        if bands_path is None:
            if "bandskpoints" in self.node.inputs:
                return self.exit_codes.BANDS_FILE_NOT_PRODUCED
        else:
            #bands, coords = self._get_bands(bands_path)
            try:
                bands = self._get_bands(bands_path)
            except (ValueError, IndexError):
                return self.exit_codes.BANDS_PARSE_FAIL
            from aiida.orm import BandsData
            arraybands = BandsData()
            #Reset the cell for KpointsData of bands, necessary
            #for bandskpoints without cell and if structure changed
            bkp = self.node.inputs.bandskpoints.clone()
            if output_dict['variable_geometry']:
                bkp.set_cell_from_structure(out_struc)
            else:
                bkp.set_cell_from_structure(self.node.inputs.structure)
            arraybands.set_kpointsdata(bkp)
            arraybands.set_bands(bands, units="eV")
            self.out('bands', arraybands)
            #bandsparameters = Dict(dict={"kp_coordinates": coords})
            #self.out('bands_parameters', bandsparameters)

        #At the very end, return a particular exit code if "INFO: Job completed"
        #was not present in the MESSAGES file, but no known error is detected.
        if have_errors_to_analyse:
            if not succesful:
                self.logger.error(
                    'The calculation finished without "INFO: Job completed", but no '
                    'error could be processed. Might be that the calculation was killed externally'
                )
                return self.exit_codes.UNEXPECTED_TERMINATION

        return ExitCode(0)
예제 #7
0
def band_parser_legacy(band_dat, band_kpt, special_points, structure):  # pylint: disable=too-many-locals
    """
    Parsers the bands output data, along with the special points retrieved
    from the input kpoints to construct a BandsData object which is then
    returned. Cannot handle discontinuities in the kpath, if two points are
    assigned to same spot only one will be passed. Used for wannier90 < 3.0
    :param band_dat: list of str with each str stores one line of aiida_band.dat file
    :param band_kpt: list of str with each str stores one line of aiida_band.kpt file
    :param special_points: special points to add labels to the bands a dictionary in
        the form expected in the input as described in the wannier90 documentation
    :return: BandsData object constructed from the input params,
        and a list contains warnings.
    """
    import numpy as np

    from aiida.orm import BandsData
    from aiida.orm import KpointsData

    warnings = []
    warnings.append((
        "Note: no file named SEEDNAME_band.labelinfo.dat found. "
        "You are probably using a version of Wannier90 before 3.0. "
        "There, the labels associated with each k-points were not printed in output "
        "and there were also cases in which points were not calculated "
        "(see issue #195 on the Wannier90 GitHub page). "
        "I will anyway try to do my best to assign labels, "
        "but the assignment might be wrong "
        "(especially if there are path discontinuities)."))

    # imports the data
    out_kpt = np.genfromtxt(band_kpt, skip_header=1, usecols=(0, 1, 2))
    out_dat = np.genfromtxt(band_dat, usecols=1)

    # reshaps the output bands
    out_dat = out_dat.reshape(len(out_kpt), (len(out_dat) // len(out_kpt)),
                              order="F")

    # finds expected points of discontinuity
    kpath = special_points['path']
    cont_break = [(i, (kpath[i - 1][1], kpath[i][0]))
                  for i in range(1, len(kpath))
                  if kpath[i - 1][1] != kpath[i][0]]

    # finds the special points
    special_points_dict = special_points['point_coords']
    # We set atol to 1e-5 because in the kpt file the coords are printed with fixed precision
    labels = [
        (i, k) for k in special_points_dict for i in range(len(out_kpt))
        if all(
            np.isclose(special_points_dict[k], out_kpt[i], rtol=0, atol=1.e-5))
    ]
    labels.sort()

    # Checks and appends labels if discontinuity
    appends = []
    for x in cont_break:
        # two cases the break is before or the break is after
        # if the break is before
        if labels[x[0]][1] != x[1][0]:
            # checks to see if the discontinuity was already there
            if labels[x[0] - 1] == x[1][0]:
                continue
            insert_point = x[0]
            new_label = x[1][0]
            kpoint = labels[x[0]][0] - 1
            appends += [[insert_point, new_label, kpoint]]
        # if the break is after
        if labels[x[0]][1] != x[1][1]:
            # checks to see if the discontinuity was already there
            if labels[x[0] + 1] == x[1][1]:
                continue
            insert_point = x[0] + 1
            new_label = x[1][1]
            kpoint = labels[x[0]][0] + 1
            appends += [[insert_point, new_label, kpoint]]
    appends.sort()

    for i, append in enumerate(appends):
        labels.insert(append[0] + i, (append[2], six.text_type(append[1])))
    bands = BandsData()
    k = KpointsData()
    k.set_cell_from_structure(structure)
    k.set_kpoints(out_kpt, cartesian=False)
    bands.set_kpointsdata(k)
    bands.set_bands(out_dat, units='eV')
    bands.labels = labels
    return bands, warnings
예제 #8
0
def band_parser(band_dat, band_kpt, band_labelinfo, structure):  # pylint: disable=too-many-locals
    """
    Parsers the bands output data to construct a BandsData object which is then
    returned. Used for wannier90 >= 3.0

    :param band_dat: list of str with each str stores one line of aiida_band.dat file
    :param band_kpt: list of str with each str stores one line of aiida_band.kpt file
    :param band_labelinfo: list of str with each str stores one line in aiida_band.labelinfo.dat file
    :return: BandsData object constructed from the input params
    """
    import numpy as np

    from aiida.orm import BandsData
    from aiida.orm import KpointsData

    warnings = []

    # imports the data
    out_kpt = np.genfromtxt(band_kpt, skip_header=1, usecols=(0, 1, 2))
    out_dat = np.genfromtxt(band_dat, usecols=1)

    # reshaps the output bands
    out_dat = out_dat.reshape(len(out_kpt), (len(out_dat) // len(out_kpt)),
                              order="F")

    labels_dict = {}
    for line_idx, line in enumerate(band_labelinfo, start=1):
        if not line.strip():
            continue
        try:
            # label, idx, xval, kx, ky, kz = line.split()
            label, idx, _, _, _, _ = line.split()
        except ValueError:
            warnings.append(
                ('Wrong number of items in line {} of the labelinfo file - '
                 'I will not assign that label')).format(line_idx)
            continue
        try:
            idx = int(idx)
        except ValueError:
            warnings.append((
                "Invalid value for the index in line {} of the labelinfo file, "
                "it's not an integer - I will not assign that label"
            )).format(line_idx)
            continue

        # I use a dictionary because there are cases in which there are
        # two lines for the same point (e.g. when I do a zero-length path,
        # from a point to the same point, just to have that value)
        # Note the -1 because in fortran indices are 1-based, in Python are
        # 0-based
        labels_dict[idx - 1] = label
    labels = [(key, labels_dict[key]) for key in sorted(labels_dict)]

    bands = BandsData()
    k = KpointsData()
    k.set_cell_from_structure(structure)
    k.set_kpoints(out_kpt, cartesian=False)
    bands.set_kpointsdata(k)
    bands.set_bands(out_dat, units='eV')
    bands.labels = labels
    return bands, warnings