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' )
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
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
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
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
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)
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
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