def parse_bands_file(bands_lines): ''' Parses the returned bands.1 and bands.2 file and returns a complete bandsData object. bands.1 has the form: k value, energy :param bands_lines: string of the read in bands file ''' # TODO: not finished # read bands out of file: nrows = 0 # get number of rows (known form number of atom types bands_values = [] # init an array of arrays nkpoint * ... bands_labels = [] # label for each row. # fill and correct fermi energy. bands_values = [] # TODO: we need to get the cell from StructureData node # and KpointsData node from inpxml fleur_bands = BandsData() # fleur_bands.set_cell(cell) #fleur_bands.set_kpoints(kpoints, cartesian=True) fleur_bands.set_bands(bands=bands_values, units='eV', labels=bands_labels) for line in bands_lines: pass return fleur_bands
def bands_to_bandsdata(bands_info, kpoints, bands): """ Convert the result of parser_dot_bands into a BandsData object :param bands_info: A dictionary of the informations of the bands file. contains field such as eferemi, units, cell :param kpoints: An array of the kpoints of the bands, rows are (kindex, kx, ky, kz, weight) :param bands: The actual bands array :return: A BandsData object :rtype: ``aiida.orm.bands.data.array.bands.BandsData`` """ bands_node = BandsData() # Extract the index of the kpoints kpn_array = np.asarray(kpoints) k_index = kpn_array[:, 0] # We need to restore the order of the kpoints k_sort = np.argsort(k_index) # Sort the kpn_array kpn_array = kpn_array[k_sort] _weights = kpn_array[:, -1] kpts = kpn_array[:, 1:-1] bands_node.set_kpoints(kpts, weights=_weights) # Sort the bands to match the order of the kpoints bands_array = np.asarray(bands)[k_sort] # We need to swap the axes from kpt,spin,engs to spin,kpt,engs bands_array = bands_array.swapaxes(0, 1) # Squeeze the first dimension e.g when there is a single spin if bands_array.shape[0] == 1: bands_array = bands_array[0] bands_array = bands_array * units['Eh'] bands_info = dict(bands_info) # Create a copy # Convert the units for the fermi energies if isinstance(bands_info['efermi'], list): bands_info['efermi'] = [x * units['Eh'] for x in bands_info['efermi']] else: bands_info['efermi'] = bands_info['efermi'] * units['Eh'] bands_node.set_bands(bands_array, units="eV") # PBC is always true as this is PW DFT.... bands_node.set_cell(bands_info['cell'], pbc=(True, True, True)) # Store information from *.bands in the attributes # This is needs as we need to know the number of electrons # and the fermi energy for key, value in bands_info.items(): bands_node.set_attribute(key, value) return bands_node
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 _has_empty_bands(self, bands_data: BandsData, thresh=0.005): """ Check for the occupation of the BandsData There should be some empty bands if the calculation is a not a fixed occupation one. Otherwise, the final energy and forces are not reliable. """ # Check if occupation is allowed to vary param = self.node.inputs.parameters.get_dict()['PARAM'] # If it is a fixed occupation calculation we do not need to do anything about it.... fix_occ = (param.get('fix_occupancy', False) or param.get('metals_method', 'dm').lower() == 'none' or param.get('elec_method', 'dm').lower() == 'none') if fix_occ: return True _, occ = bands_data.get_bands(also_occupations=True) nspin, nkppts, _ = occ.shape problems = [] for ispin in range(nspin): for ikpts in range(nkppts): if occ[ispin, ikpts, -1] >= thresh: problems.append((ispin, ikpts, occ[ispin, ikpts, -1])) if problems: for ispin, ikpts, val in problems: self.logger.warning( "No empty bands for spin %d, kpoint %d - occ: %.5f", ispin, ikpts, val) return False return True
def _parse_stdout(self, out_folder): """CP2K output parser""" from aiida.orm import BandsData, Dict # pylint: disable=protected-access fname = self.node.process_class._DEFAULT_OUTPUT_FILE if fname not in out_folder._repository.list_object_names(): raise OutputParsingError("Cp2k output file not retrieved") abs_fn = os.path.join( out_folder._repository._get_base_folder().abspath, fname) with io.open(abs_fn, mode="r", encoding="utf-8") as fobj: result_dict = parse_cp2k_output(fobj) if "nwarnings" not in result_dict: raise OutputParsingError("CP2K did not finish properly.") if "kpoint_data" in result_dict: bnds = BandsData() bnds.set_kpoints(result_dict["kpoint_data"]["kpoints"]) bnds.labels = result_dict["kpoint_data"]["labels"] bnds.set_bands( result_dict["kpoint_data"]["bands"], units=result_dict["kpoint_data"]["bands_unit"], ) self.out("output_bands", bnds) del result_dict["kpoint_data"] self.out("output_parameters", Dict(dict=result_dict))
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 _parse_stdout(self): """Advanced CP2K output file parser.""" from aiida.orm import BandsData from aiida_cp2k.utils import parse_cp2k_output_advanced fname = self.node.process_class._DEFAULT_OUTPUT_FILE # pylint: disable=protected-access if fname not in self.retrieved.list_object_names(): raise OutputParsingError("Cp2k output file not retrieved") try: output_string = self.retrieved.get_object_content(fname) except IOError: return self.exit_codes.ERROR_OUTPUT_STDOUT_READ result_dict = parse_cp2k_output_advanced(output_string) # nwarnings is the last thing to be printed in th eCP2K output file: # if it is not there, CP2K didn't finish properly if 'nwarnings' not in result_dict: raise OutputParsingError("CP2K did not finish properly.") if "aborted" in result_dict: return self.exit_codes.ERROR_OUTPUT_CONTAINS_ABORT # Compute the bandgap for Spin1 and Spin2 if eigen was parsed (works also with smearing!) if 'eigen_spin1_au' in result_dict: if result_dict['dft_type'] == "RKS": result_dict['eigen_spin2_au'] = result_dict['eigen_spin1_au'] lumo_spin1_idx = result_dict['init_nel_spin1'] lumo_spin2_idx = result_dict['init_nel_spin2'] if (lumo_spin1_idx > len(result_dict['eigen_spin1_au'])-1) or \ (lumo_spin2_idx > len(result_dict['eigen_spin2_au'])-1): #electrons jumped from spin1 to spin2 (or opposite): assume last eigen is lumo lumo_spin1_idx = len(result_dict['eigen_spin1_au']) - 1 lumo_spin2_idx = len(result_dict['eigen_spin2_au']) - 1 homo_spin1 = result_dict['eigen_spin1_au'][lumo_spin1_idx - 1] homo_spin2 = result_dict['eigen_spin2_au'][lumo_spin2_idx - 1] lumo_spin1 = result_dict['eigen_spin1_au'][lumo_spin1_idx] lumo_spin2 = result_dict['eigen_spin2_au'][lumo_spin2_idx] result_dict['bandgap_spin1_au'] = lumo_spin1 - homo_spin1 result_dict['bandgap_spin2_au'] = lumo_spin2 - homo_spin2 if "kpoint_data" in result_dict: bnds = BandsData() bnds.set_kpoints(result_dict["kpoint_data"]["kpoints"]) bnds.labels = result_dict["kpoint_data"]["labels"] bnds.set_bands( result_dict["kpoint_data"]["bands"], units=result_dict["kpoint_data"]["bands_unit"], ) self.out("output_bands", bnds) del result_dict["kpoint_data"] self.out("output_parameters", Dict(dict=result_dict)) return None
def test_valid_node(): """Test that the correct exceptions are thrown for incompatible nodes.""" from aiida.orm import ArrayData, BandsData # Invalid node type node = ArrayData().store() with pytest.raises(ValueError): get_highest_occupied_band(node) # The `occupations` array is missing node = BandsData() node.set_array('not_occupations', numpy.array([])) node.store() with pytest.raises(ValueError): get_highest_occupied_band(node) # The `occupations` array has incorrect shape node = BandsData() node.set_array('occupations', numpy.array([1., 1.])) node.store() with pytest.raises(ValueError): get_highest_occupied_band(node)
def _parse_stdout(self, out_folder): """Advanced CP2K output file parser""" from aiida.orm import BandsData from .parser_functions import parse_cp2k_output_advanced # pylint: disable=protected-access fname = self.node.process_class._DEFAULT_OUTPUT_FILE if fname not in out_folder._repository.list_object_names(): raise OutputParsingError("Cp2k output file not retrieved") abs_fn = os.path.join( out_folder._repository._get_base_folder().abspath, fname) with io.open(abs_fn, mode="r", encoding="utf-8") as fobj: result_dict = parse_cp2k_output_advanced(fobj) # nwarnings is the last thing to be printed in th eCP2K output file: # if it is not there, CP2K didn't finish properly if 'nwarnings' not in result_dict: raise OutputParsingError("CP2K did not finish properly.") # Compute the bandgap for Spin1 and Spin2 if eigen was parsed (works also with smearing!) if 'eigen_spin1_au' in result_dict: if result_dict['dft_type'] == "RKS": result_dict['eigen_spin2_au'] = result_dict['eigen_spin1_au'] lumo_spin1_idx = result_dict['init_nel_spin1'] lumo_spin2_idx = result_dict['init_nel_spin2'] if (lumo_spin1_idx > len(result_dict['eigen_spin1_au'])-1) or \ (lumo_spin2_idx > len(result_dict['eigen_spin2_au'])-1): #electrons jumped from spin1 to spin2 (or opposite): assume last eigen is lumo lumo_spin1_idx = len(result_dict['eigen_spin1_au']) - 1 lumo_spin2_idx = len(result_dict['eigen_spin2_au']) - 1 homo_spin1 = result_dict['eigen_spin1_au'][lumo_spin1_idx - 1] homo_spin2 = result_dict['eigen_spin2_au'][lumo_spin2_idx - 1] lumo_spin1 = result_dict['eigen_spin1_au'][lumo_spin1_idx] lumo_spin2 = result_dict['eigen_spin2_au'][lumo_spin2_idx] result_dict['bandgap_spin1_au'] = lumo_spin1 - homo_spin1 result_dict['bandgap_spin2_au'] = lumo_spin2 - homo_spin2 if "kpoint_data" in result_dict: bnds = BandsData() bnds.set_kpoints(result_dict["kpoint_data"]["kpoints"]) bnds.labels = result_dict["kpoint_data"]["labels"] bnds.set_bands( result_dict["kpoint_data"]["bands"], units=result_dict["kpoint_data"]["bands_unit"], ) self.out("output_bands", bnds) del result_dict["kpoint_data"] self.out("output_parameters", Dict(dict=result_dict))
def example_bands_v2(fresh_aiida_env): """Example eigen values and occupations""" from aiida.orm import BandsData bdata = BandsData() bdata.set_kpoints([[0, 0, 0], [0.25, 0.25, 0.25]]) occ = np.array([[1, 1, 1, 0, 0, 0], [1, 1, 1, 0, 0, 0]], dtype=np.float64) eigen = np.array([[-1, -1, -1, 0, 0, 0], [-0.5, -0.5, -0.5, 0, 0, 0]]) bdata.set_bands(bands=eigen, occupations=occ) return bdata
def bands_from_castepbin(seedname, fmanager): """ Acquire and prepare bands data from the castep_bin file instead """ with fmanager.open(seedname + '.castep_bin', 'rb') as handle: binfile = CastepbinFile(fileobj=handle) bands_node = BandsData() kidx = binfile.kpoints_indices sort_idx = np.argsort(kidx) # Generated sorted arrays kpoints = binfile.kpoints[sort_idx, :] weights = binfile.kpoint_weights[sort_idx].astype(float) eigenvalues = binfile.eigenvalues[:, sort_idx, :] occupancies = binfile.occupancies[:, sort_idx, :] efermi = binfile.fermi_energy bands_node.set_kpoints(kpoints, weights=weights) bands_node.set_bands(eigenvalues, occupations=occupancies, units="eV") bands_node.set_cell(binfile.cell, pbc=(True, True, True)) bands_node.set_attribute('efermi', efermi) return bands_node
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 test_spin_unpolarized(): """Test the function for a non spin-polarized calculation meaning there will be a single spin channel.""" from aiida.orm import BandsData occupations = numpy.array([ [2., 2., 2., 2., 0.], [2., 2., 2., 2., 0.], [2., 2., 2., 2., 0.], [2., 2., 2., 2., 0.], ]) bands = BandsData() bands.set_array('occupations', occupations) bands.store() h**o = get_highest_occupied_band(bands) assert h**o == 4
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 test_spin_polarized(self, fixture_database): """Test the function for a spin-polarized calculation meaning there will be two spin channels.""" from aiida.orm import BandsData occupations = numpy.array([ [ [2., 2., 2., 2., 0.], [2., 2., 2., 2., 0.], ], [ [2., 2., 2., 2., 0.], [2., 2., 2., 2., 0.], ] ]) bands = BandsData() bands.set_array('occupations', occupations) bands.store() h**o = get_highest_occupied_band(bands) assert h**o == 4
def _parse_gsr(self): """Abinit GSR parser.""" # Output GSR Abinit NetCDF file - Default name is aiidao_GSR.nc fname = f'{self.node.get_attribute("prefix")}o_GSR.nc' # Absolute path of the folder in which aiidao_GSR.nc is stored path = self.node.get_remote_workdir() if fname not in self.retrieved.list_object_names(): return self.exit_codes.ERROR_MISSING_OUTPUT_FILES with abilab.abiopen(path + '/' + fname) as gsr: gsr_data = { 'abinit_version': gsr.abinit_version, 'cart_stress_tensor': gsr.cart_stress_tensor.tolist(), 'cart_stress_tensor' + UNITS_SUFFIX: DEFAULT_STRESS_UNITS, 'is_scf_run': bool(gsr.is_scf_run), # 'cart_forces': gsr.cart_forces.tolist(), # 'cart_forces' + units_suffix: DEFAULT_FORCE_UNITS, 'forces': gsr.cart_forces.tolist(), # backwards compatibility 'forces' + UNITS_SUFFIX: DEFAULT_FORCE_UNITS, 'energy': float(gsr.energy), 'energy' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_localpsp': float(gsr.energy_terms.e_localpsp), 'e_localpsp' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_eigenvalues': float(gsr.energy_terms.e_eigenvalues), 'e_eigenvalues' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_ewald': float(gsr.energy_terms.e_ewald), 'e_ewald' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_hartree': float(gsr.energy_terms.e_hartree), 'e_hartree' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_corepsp': float(gsr.energy_terms.e_corepsp), 'e_corepsp' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_corepspdc': float(gsr.energy_terms.e_corepspdc), 'e_corepspdc' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_kinetic': float(gsr.energy_terms.e_kinetic), 'e_kinetic' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_nonlocalpsp': float(gsr.energy_terms.e_nonlocalpsp), 'e_nonlocalpsp' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_entropy': float(gsr.energy_terms.e_entropy), 'e_entropy' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'entropy': float(gsr.energy_terms.entropy), 'entropy' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_xc': float(gsr.energy_terms.e_xc), 'e_xc' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_xcdc': float(gsr.energy_terms.e_xcdc), 'e_xcdc' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_paw': float(gsr.energy_terms.e_paw), 'e_paw' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_pawdc': float(gsr.energy_terms.e_pawdc), 'e_pawdc' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_elecfield': float(gsr.energy_terms.e_elecfield), 'e_elecfield' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_magfield': float(gsr.energy_terms.e_magfield), 'e_magfield' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_fermie': float(gsr.energy_terms.e_fermie), 'e_fermie' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_sicdc': float(gsr.energy_terms.e_sicdc), 'e_sicdc' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_exactX': float(gsr.energy_terms.e_exactX), 'e_exactX' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'h0': float(gsr.energy_terms.h0), 'h0' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_electronpositron': float(gsr.energy_terms.e_electronpositron), 'e_electronpositron' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'edc_electronpositron': float(gsr.energy_terms.edc_electronpositron), 'edc_electronpositron' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e0_electronpositron': float(gsr.energy_terms.e0_electronpositron), 'e0_electronpositron' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'e_monopole': float(gsr.energy_terms.e_monopole), 'e_monopole' + UNITS_SUFFIX: DEFAULT_ENERGY_UNITS, 'pressure': float(gsr.pressure), 'pressure' + UNITS_SUFFIX: DEFAULT_STRESS_UNITS } try: # will return an integer 0 if non-magnetic calculation is run; convert it to a float total_magnetization = float(gsr.ebands.get_collinear_mag()) gsr_data['total_magnetization'] = total_magnetization gsr_data['total_magnetization' + UNITS_SUFFIX] = DEFAULT_MAGNETIZATION_UNITS except ValueError as valerr: # get_collinear_mag will raise ValueError if it doesn't know what to do if 'Cannot calculate collinear magnetization' in valerr.args[ 0]: pass else: raise valerr try: bands_data = BandsData() bands_data.set_kpoints(gsr.ebands.kpoints.get_cart_coords()) bands_data.set_bands(np.array(gsr.ebands.eigens), units=str(gsr.ebands.eigens.unit)) self.out('output_bands', bands_data) except: # pylint: disable=bare-except pass self.out('output_parameters', Dict(dict=gsr_data))
def create_structure_bands(): """Create bands structure object.""" alat = 4. # angstrom cell = [ [ alat, 0., 0., ], [ 0., alat, 0., ], [ 0., 0., alat, ], ] strct = StructureData(cell=cell) strct.append_atom(position=(0., 0., 0.), symbols='Fe') strct.append_atom(position=(alat / 2., alat / 2., alat / 2.), symbols='O') strct.store() @calcfunction 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 bands = connect_structure_bands(strct) bands_isolated = BandsData() bands_isolated.store() # Create 2 groups and add the data to one of them g_ne = Group(label='non_empty_group') g_ne.store() g_ne.add_nodes(bands) g_ne.add_nodes(bands_isolated) g_e = Group(label='empty_group') g_e.store() return { DummyVerdiDataListable.NODE_ID_STR: bands.id, DummyVerdiDataListable.NON_EMPTY_GROUP_ID_STR: g_ne.id, DummyVerdiDataListable.EMPTY_GROUP_ID_STR: g_e.id }
def test_threshold(): """Test the `threshold` parameter.""" from aiida.orm import BandsData threshold = 0.002 bands = BandsData() bands.set_array('occupations', numpy.array([[2., 2., 2., 2., 0.001, 0.0015]])) bands.store() # All bands above the LUMO (occupation of 0.001) are below `2 * threshold` h**o = get_highest_occupied_band(bands, threshold=threshold) assert h**o == 4 bands = BandsData() bands.set_array('occupations', numpy.array([[2., 2., 2., 2., 0.001, 0.003]])) bands.store() # A band above the LUMO (occupation of 0.001) has an occupation above `2 * threshold` with pytest.raises(ValueError): get_highest_occupied_band(bands, threshold=threshold)
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 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 _get_output_nodes(self, output_path, messages_path, xml_path, json_path, bands_path): """ Extracts output nodes from the standard output and standard error files. (And XML and JSON files) """ from aiida.orm import TrajectoryData import re parser_version = '1.0.1' parser_info = {} parser_info['parser_info'] = 'AiiDA Siesta Parser V. {}'.format( parser_version) parser_info['parser_warnings'] = [] #No need for checks anymore xmldoc = get_parsed_xml_doc(xml_path) in_struc = self.node.inputs.structure try: in_settings = self.node.inputs.settings except exceptions.NotExistent: in_settings = None result_dict = get_dict_from_xml_doc(xmldoc) # Add timing information #if json_path is None: ###TODO### #Not sure how to implement what once was logger.info #Also not sure I understood the purpose of this file 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: raise OutputParsingError( "Cannot fully parse the time.json file") else: result_dict["global_time"] = global_time result_dict["timing_decomposition"] = timing_decomp # Add warnings if messages_path is None: # Perhaps using an old version of Siesta warnings_list = ['WARNING: No MESSAGES file...'] else: successful, warnings_list = self.get_warnings_from_file( messages_path) ###TODO### or maybe not #How do we process this successfull booelan? result_dict["warnings"] = warnings_list # Add parser info dictionary parsed_dict = dict( list(result_dict.items()) + list(parser_info.items())) output_data = Dict(dict=parsed_dict) self.out('output_parameters', output_data) # If the structure has changed, save it if is_variable_geometry(xmldoc): # Get the input structure to copy its site names, # as the CML file traditionally contained only the # atomic symbols. # struc = get_last_structure(xmldoc, in_struc) # result_list.append((self.get_linkname_outstructure(),struc)) self.out(self.get_linkname_outstructure(), struc) # Save forces and stress in an ArrayData object forces, stress = get_final_forces_and_stress(xmldoc) if forces is not None and stress is not None: # from aiida.orm.nodes.array import ArrayData from aiida.orm import ArrayData arraydata = ArrayData() arraydata.set_array('forces', np.array(forces)) arraydata.set_array('stress', np.array(stress)) # result_list.append((self.get_linkname_fs_and_stress(),arraydata)) self.out(self.get_linkname_fs_and_stress(), arraydata) # Parse band-structure information if available if bands_path is not None: bands, coords = self.get_bands(bands_path) from aiida.orm import BandsData arraybands = BandsData() f = self.node.inputs.bandskpoints #Temporary workaround due to a bug #f._set_reciprocal_cell() #in KpointData (issue #2749) arraybands.set_kpoints(f.get_kpoints(cartesian=True)) arraybands.labels = f.labels arraybands.set_bands(bands, units="eV") self.out(self.get_linkname_bands(), arraybands) bandsparameters = Dict(dict={"kp_coordinates": coords}) self.out(self.get_linkname_bandsparameters(), bandsparameters) return result_dict
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
def _aiida_bands_data(self, data, cell, kpoints_dict): if not data: return False kpt_idx = sorted(data.keys()) # list of kpoint indices try: k_list = [kpoints_dict[i] for i in kpt_idx] # list of k-point triplet except KeyError: # kpoint triplets are not present (true for .qp and so on, can not use BandsData) # We use the internal Yambo Format [ [Eo_1, Eo_2,... ], ...[So_1,So_2,] ] # QP_TABLE [[ib_1,ik_1,isp_1] ,[ib_n,ik_n,isp_n]] # Each entry in DATA has corresponding legend in QP_TABLE that defines its details # like ib= Band index, ik= kpoint index, isp= spin polarization index. # Eo_1 => at ib_1, ik_1 isp_1. pdata = ArrayData() QP_TABLE = [] ORD = [] Eo = [] E_minus_Eo = [] So = [] Z = [] for ky in data.keys(): # kp == kpoint index as a string 1,2,.. for ind in range(len(data[ky]['Band'])): try: Eo.append(data[ky]['Eo'][ind]) except KeyError: pass try: E_minus_Eo.append(data[ky]['E-Eo'][ind]) except KeyError: pass try: So.append(data[ky]['Sc|Eo'][ind]) except KeyError: pass try: Z.append(data[ky]['Z'][ind]) except KeyError: pass ik = int(ky) ib = data[ky]['Band'][ind] isp = 0 if 'Spin_Pol' in list(data[ky].keys()): isp = data[ky]['Spin_Pol'][ind] QP_TABLE.append([ik, ib, isp]) pdata.set_array('Eo', numpy.array(Eo)) pdata.set_array('E_minus_Eo', numpy.array(E_minus_Eo)) pdata.set_array('So', numpy.array(So)) pdata.set_array('Z', numpy.array(Z)) pdata.set_array('qp_table', numpy.array(QP_TABLE)) return pdata quasiparticle_bands = BandsData() quasiparticle_bands.set_cell(cell) quasiparticle_bands.set_kpoints(k_list, cartesian=True) # labels will come from any of the keys in the nested kp_point data, # there is a uniform set of observables for each k-point, ie Band, Eo, ... # ***FIXME BUG does not seem to handle spin polarizes at all when constructing bandsdata*** bands_labels = [ legend for legend in sorted(data[list(data.keys())[0]].keys()) ] append_list = [[] for i in bands_labels] for kp in kpt_idx: for i in range(len(bands_labels)): append_list[i].append(data[kp][bands_labels[i]]) generalised_bands = [numpy.array(it) for it in append_list] quasiparticle_bands.set_bands(bands=generalised_bands, units='eV', labels=bands_labels) return quasiparticle_bands