def __init__(self, bandstructure_path, info_path, results_path): r""" Initialises an instance of the :class:`~effmass.inputs.Data` class and checks data using :meth:`check_data`. Args: bandStructure_path (str): The path to the bandstructure file. info_path (str): The path to the info file. results_path (str): The path to the results.out file. Returns: None. """ super().__init__() assert (type(bandstructure_path) == str), "The bandStructure path must be a string" assert (type(info_path) == str), "The info path must be a string" assert (type(results_path) == str), "The results path must be a string" # load the Octopus data band_struct = bandstructure.Bandstructure(bandstructure_path) self.number_of_kpoints = band_struct.num_kpoints info_data = info.Info(info_path) results_data = results.Results(results_path, self.number_of_kpoints) # unpack the reciprocal lattice vector lattice_vectors = info_data.get_lattice_vectors() lattice_vector, reciprocal_lattice = zip(*lattice_vectors) reciprocal_lattice_split = [lattice.split() for lattice in reciprocal_lattice] self.reciprocal_lattice = [[float(v) for v in vector] for vector in reciprocal_lattice_split] self.spin_channels = 1 self.number_of_bands = band_struct.num_bands # load energies and occupancies from bandstructure file octo_data_energies, octo_data_occupancies = band_struct.get_eigenvalues() # change to shape (num kpoints, num bands) self.energies = octo_data_energies.T self.occupancy = octo_data_occupancies.T # handle negative occupancy values if np.any(self.occupancy < 0): warnings.warn("One or more occupancies in your data are negative. All negative occupancies will be set to zero.") self.occupancy[ self.occupancy < 0 ] = 0.0 # load kpoints from bandstructure file and into an numpy array kpoints = band_struct.kpoints kx, ky, kz = zip(*kpoints) kpoints = np.array([kx, ky, kz]) # change to shape (number_of_kpoints, 3) self.kpoints = kpoints.T self.CBM = extrema._calc_CBM(self.occupancy, self.energies) self.VBM = extrema._calc_VBM(self.occupancy, self.energies) self.fermi_energy = (self.CBM + self.VBM) / 2 self.check_data(self.spin_channels, self.number_of_kpoints, self.number_of_bands, self.CBM, self.VBM, self.fermi_energy, self.occupancy)
def test_bandedge_energy(toy_segments, toy_data_object): assert math.isclose( toy_segments[0]._bandedge_energy(toy_data_object), extrema._calc_VBM(toy_data_object.occupancy, toy_data_object.energies)) assert math.isclose( toy_segments[1]._bandedge_energy(toy_data_object), extrema._calc_CBM(toy_data_object.occupancy, toy_data_object.energies)) assert math.isclose( toy_segments[2]._bandedge_energy(toy_data_object), extrema._calc_CBM(toy_data_object.occupancy, toy_data_object.energies))
def __init__(self, outcar_path, procar_path, ignore=0): r""" Initialises an instance of the :class:`~effmass.inputs.Data` class and checks data using :meth:`check_data`. Args: outcar_path (str): The path to the OUTCAR file procar_path (str): The path to the PROCAR file ignore (int): The number of kpoints to ignore at the beginning of the bandstructure slice through kspace (useful for hybrid calculations where zero weightings are appended to a previous self-consistent calculation). Returns: None. """ assert (type(outcar_path) == str), "The OUTCAR path must be a string" assert (type(procar_path) == str), "The PROCAR path must be a string" assert (type(ignore) == int and ignore >= 0 ), "The number of kpoints to ignore must be a positive integer" reciprocal_lattice = outcar.reciprocal_lattice_from_outcar(outcar_path) vasp_data = procar.Procar() vasp_data.read_from_file(procar_path) if vasp_data.calculation[ 'spin_polarised']: # to account for the change in PROCAR format for calculations with 2 spin channels (1 k-point block ---> 2 k-point blocks) blocks = 2 else: blocks = 1 self.spin_channels = vasp_data.spin_channels self.number_of_kpoints = vasp_data.number_of_k_points - ignore self.number_of_bands = vasp_data.number_of_bands self.number_of_ions = vasp_data.number_of_ions self.kpoints = vasp_data.k_points[ignore:] self.energies = vasp_data.bands[self.number_of_bands * ignore:, 1:].reshape( self.number_of_kpoints, self.number_of_bands * blocks).T self.occupancy = vasp_data.occupancy[self.number_of_bands * ignore:, 1:].reshape( self.number_of_kpoints, self.number_of_bands * blocks).T self.reciprocal_lattice = reciprocal_lattice * 2 * math.pi self.CBM = extrema._calc_CBM(self.occupancy, self.energies) self.VBM = extrema._calc_VBM(self.occupancy, self.energies) self.fermi_energy = (self.CBM + self.VBM) / 2 self.dos = [] self.integrated_dos = [] self.check_data()
def __init__(self, directory_path, output_name='calculation.out'): r""" Initialises an instance of the :class:`~effmass.inputs.DataAims` class and checks data using :meth:`check_data`. Args: directory_path (str): The path to the directory containing output, geometry.in, control.in and bandstructure files output_name (str): Name of the output file - contrary to the rest of the files, this is chosen by the user during an Aims run. Defaults to 'aims.out'. Returns: None. """ super().__init__() assert (type(directory_path) == str), "The file path must be a string" "Finding reciprocal lattice vectors" latvec = [] for line in open("{}/geometry.in".format(directory_path)): line = line.split("\t")[0] words = line.split() if len(words) == 0: continue if words[0] == "lattice_vector": if len(words) != 4: raise Exception("geometry.in: Syntax error in line '"+line+"'") latvec.append(np.array(words[1:4])) if len(latvec) != 3: raise Exception("geometry.in: Must contain exactly 3 lattice vectors") latvec = np.asarray(latvec) latvec = latvec.astype(np.float) #Calculate reciprocal lattice vectors rlatvec = [] volume = (np.dot(latvec[0,:],np.cross(latvec[1,:],latvec[2,:]))) rlatvec.append(np.array(2*math.pi*np.cross(latvec[1,:],latvec[2,:])/volume)) rlatvec.append(np.array(2*math.pi*np.cross(latvec[2,:],latvec[0,:])/volume)) rlatvec.append(np.array(2*math.pi*np.cross(latvec[0,:],latvec[1,:])/volume)) reciprocal_lattice = np.asarray(rlatvec) self.reciprocal_lattice = reciprocal_lattice "Finding spin channels" spin_channels = 0 for line in open("{}/{}".format(directory_path, output_name)): line = line.split("\t")[0] if "include_spin_orbit" in line: spin_channels = 4 break elif "Number of spin channels" in line: words = line.split() spin_channels = int(words[-1]) break self.spin_channels = spin_channels "Finding number of bands" number_of_bands = 0 for line in open("{}/{}".format(directory_path, output_name)): line = line.split("\t")[0] if "Number of Kohn-Sham" in line: words = line.split() number_of_bands = int(words[-1]) break if spin_channels == 2 or spin_channels == 4: #Doubling for spin-polarised calculation number_of_bands = 2*number_of_bands self.number_of_bands = number_of_bands "Finding number of kpoints and determining number of BZ paths" number_of_kpoints = 0 number_of_BZ_paths = 0 path_list = [] for line in open("{}/{}".format(directory_path, output_name)): line = line.split("\n")[0] if not line.startswith("#") and "output" in line: if "band" in line: words = line.split() if words[0]=="output" and words[1]=="band": path_list.append(int(words[8])) number_of_BZ_paths += 1 number_of_kpoints = sum(path_list) "Reading out bandstructure files to determine kpoint, energy and occupation matrices" kpoints = np.zeros([number_of_kpoints,3]) energies = np.zeros([number_of_bands,number_of_kpoints]) occupancy = np.zeros([number_of_bands,number_of_kpoints]) path_counter = 0 if spin_channels == 1 or spin_channels == 4: kpoint_counter = 0 while path_counter<number_of_BZ_paths: kpoint_counter = sum(path_list[:path_counter]) for line in open("{}/band1{:03d}.out".format(directory_path, path_counter+1)): line = line.split("\t")[0] words = line.split() kpoints[int(kpoint_counter),0] = float(words[1]) kpoints[int(kpoint_counter),1] = float(words[2]) kpoints[int(kpoint_counter),2] = float(words[3]) for i in range(number_of_bands): energies[i,int(kpoint_counter)] = float(words[5+2*i]) occupancy[i,int(kpoint_counter)] = float(words[4+2*i]) kpoint_counter += 1 path_counter +=1 if spin_channels == 2: while path_counter<number_of_BZ_paths: kpoint_counter = int(sum(path_list[:path_counter])) for line in open("{}/band1{:03d}.out".format(directory_path, path_counter+1)): line = line.split("\t")[0] words = line.split() kpoints[int(kpoint_counter),0] = float(words[1]) kpoints[int(kpoint_counter),1] = float(words[2]) kpoints[int(kpoint_counter),2] = float(words[3]) for i in range(number_of_bands//2): energies[i,int(kpoint_counter)] = float(words[5+2*i]) occupancy[i,int(kpoint_counter)] = float(words[4+2*i]) kpoint_counter += 1 kpoint_counter = int(sum(path_list[:path_counter])) for line in open("{}/band2{:03d}.out".format(directory_path, path_counter+1)): line = line.split("\t")[0] words = line.split() for i in range(number_of_bands//2): energies[number_of_bands//2+i,kpoint_counter] = float(words[5+2*i]) occupancy[number_of_bands//2+i,kpoint_counter] = float(words[4+2*i]) kpoint_counter += 1 path_counter += 1 "Delete double kpoints at path edges" index_count = len(kpoints) index = 0 while index < index_count-1: if np.array_equal(kpoints[index],kpoints[index+1]): kpoints = np.delete(kpoints,index+1,axis=0) energies = np.delete(energies,index+1,axis=1) occupancy = np.delete(occupancy,index+1,axis=1) index_count = len(kpoints) index += 1 self.number_of_kpoints = len(kpoints) self.CBM = extrema._calc_CBM(occupancy, energies) self.VBM = extrema._calc_VBM(occupancy, energies) self.fermi_energy = (self.CBM + self.VBM) / 2 "Cutting energy values in a range of 30 eV above and below the Fermi level. FHI AIMS is all electron, but not all states are needed for a meaningful effmass calculation" index_count = len(occupancy) index = 0 while index < index_count-1: if all(item < self.fermi_energy - 30 for item in energies[index]): energies = np.delete(energies, index, axis = 0) occupancy = np.delete(occupancy, index, axis = 0) index_count = len(occupancy) elif all(item > self.fermi_energy + 30 for item in energies[index]): energies = np.delete(energies, index, axis = 0) occupancy = np.delete(occupancy, index, axis = 0) index_count = len(occupancy) else: index += 1 self.energies = energies self.occupancy = occupancy self.kpoints = kpoints self.check_data(self.spin_channels, self.number_of_kpoints, self.number_of_bands, self.CBM, self.VBM, self.fermi_energy, self.occupancy)
def __init__(self, outcar_path, procar_path, ignore=0, **kwargs): r""" Initialises an instance of the :class:`~effmass.inputs.Data` class and checks data using :meth:`check_data`. Args: outcar_path (str): The path to the OUTCAR file procar_path (:obj:`str` or :obj:`list`): The path(s) to one or more PROCAR files. ignore (int): The number of kpoints to ignore at the beginning of the bandstructure slice through kspace (useful for hybrid calculations where zero weightings are appended to a previous self-consistent calculation). **kwargs: Additional keyword arguments for reading the PROCAR file(s). Returns: None. """ super().__init__() assert (type(outcar_path) == str), "The OUTCAR path must be a string" assert (type(ignore) == int and ignore >= 0 ), "The number of kpoints to ignore must be a positive integer" reciprocal_lattice = outcar.reciprocal_lattice_from_outcar(outcar_path) if isinstance(procar_path, list): vasp_data = procar.Procar.from_files(procar_path, **kwargs) elif isinstance(procar_path, str): vasp_data = procar.Procar.from_file(procar_path, **kwargs) else: raise TypeError('procar_path must be a string or list of strings') self.spin_channels = vasp_data.spin_channels self.number_of_bands = vasp_data.number_of_bands number_of_kpoints = vasp_data.number_of_k_points vasp_data_energies = np.array( [ band.energy for band in np.ravel( vasp_data.bands ) ] ) vasp_data_occupancies = np.array( [ band.occupancy for band in np.ravel( vasp_data.bands ) ] ) if vasp_data.calculation['spin_polarised']: # to account for the change in PROCAR format for calculations with 2 spin channels (1 k-point block ---> 2 k-point blocks) energies = np.zeros([self.number_of_bands*2,number_of_kpoints]) # This is a very ugly way to slice 'n' dice. Should avoid creating new array and use array methods instead. But it does the job so will keep for now. for i in range(self.number_of_bands): energies[i] = vasp_data_energies.reshape( number_of_kpoints*2, # factor of 2 for each kpoint block self.number_of_bands).T[i][:number_of_kpoints] energies[self.number_of_bands+i] = vasp_data_energies.reshape( number_of_kpoints*2, self.number_of_bands).T[i][number_of_kpoints:] occupancy = np.zeros([self.number_of_bands*2,number_of_kpoints]) for i in range(self.number_of_bands): occupancy[i] = vasp_data_occupancies.reshape( number_of_kpoints*2, self.number_of_bands).T[i][:number_of_kpoints] occupancy[self.number_of_bands+i] = vasp_data_occupancies.reshape( number_of_kpoints*2, self.number_of_bands).T[i][number_of_kpoints:] else: energies = vasp_data_energies.reshape( number_of_kpoints, self.number_of_bands).T occupancy = vasp_data_occupancies.reshape( number_of_kpoints, self.number_of_bands).T # remove values which are from the self-consistent calculation prior to the bandstructure calculation (workflow for hybrid functionals) self.energies = np.delete(energies,list(range(ignore)),1) self.occupancy = np.delete(occupancy,list(range(ignore)),1) self.number_of_kpoints = number_of_kpoints - ignore # handle negative occupancy values if np.any(self.occupancy < 0): warnings.warn("One or more occupancies in your PROCAR file are negative. All negative occupancies will be set to zero.") self.occupancy[ self.occupancy < 0 ] = 0.0 self.kpoints = np.array( [ kp.frac_coords for kp in vasp_data.k_points[ignore:vasp_data.number_of_k_points] ] ) self.reciprocal_lattice = reciprocal_lattice * 2 * math.pi self.CBM = extrema._calc_CBM(self.occupancy, self.energies) self.VBM = extrema._calc_VBM(self.occupancy, self.energies) self.fermi_energy = (self.CBM + self.VBM) / 2 self.dos = [] self.integrated_dos = [] self.check_data(self.spin_channels, self.number_of_kpoints, self.number_of_bands, self.CBM, self.VBM, self.fermi_energy, self.occupancy)
def test_calc_VBM(toy_data_object): assert extrema._calc_VBM(toy_data_object.occupancy,toy_data_object.energies) == 0.5
def __init__(self, outcar_path, procar_path, ignore=0): r""" Initialises an instance of the :class:`~effmass.inputs.Data` class and checks data using :meth:`check_data`. Args: outcar_path (str): The path to the OUTCAR file procar_path (str): The path to the PROCAR file ignore (int): The number of kpoints to ignore at the beginning of the bandstructure slice through kspace (useful for hybrid calculations where zero weightings are appended to a previous self-consistent calculation). Returns: None. """ assert (type(outcar_path) == str), "The OUTCAR path must be a string" assert (type(procar_path) == str), "The PROCAR path must be a string" assert (type(ignore) == int and ignore >= 0 ), "The number of kpoints to ignore must be a positive integer" reciprocal_lattice = outcar.reciprocal_lattice_from_outcar(outcar_path) vasp_data = procar.Procar() vasp_data.read_from_file(procar_path) self.spin_channels = vasp_data.spin_channels self.number_of_bands = vasp_data.number_of_bands self.number_of_ions = vasp_data.number_of_ions number_of_kpoints = vasp_data.number_of_k_points if vasp_data.calculation[ 'spin_polarised']: # to account for the change in PROCAR format for calculations with 2 spin channels (1 k-point block ---> 2 k-point blocks) energies = np.zeros( [self.number_of_bands * 2, number_of_kpoints] ) # This is a very ugly way to slice 'n' dice. Should avoid creating new array and use array methods instead. But it does the job so will keep for now. for i in range(self.number_of_bands): energies[i] = vasp_data.bands[:, 1:].reshape( number_of_kpoints * 2, # factor or 2 for each kpoint block self.number_of_bands).T[i][:number_of_kpoints] energies[self.number_of_bands + i] = vasp_data.bands[:, 1:].reshape( number_of_kpoints * 2, self.number_of_bands).T[i][number_of_kpoints:] occupancy = np.zeros([self.number_of_bands * 2, number_of_kpoints]) for i in range(self.number_of_bands): occupancy[i] = vasp_data.occupancy[:, 1:].reshape( number_of_kpoints * 2, self.number_of_bands).T[i][:number_of_kpoints] occupancy[self.number_of_bands + i] = vasp_data.occupancy[:, 1:].reshape( number_of_kpoints * 2, self.number_of_bands).T[i][number_of_kpoints:] else: energies = vasp_data.bands[:, 1:].reshape(number_of_kpoints, self.number_of_bands).T occupancy = vasp_data.occupancy[:, 1:].reshape( number_of_kpoints, self.number_of_bands).T # remove values which are from the self-consistent calculation prior to the bandstructure calculation (workflow for hybrid functionals) self.energies = np.delete(energies, list(range(ignore)), 1) self.occupancy = np.delete(occupancy, list(range(ignore)), 1) self.number_of_kpoints = number_of_kpoints - ignore self.kpoints = vasp_data.k_points[ignore:vasp_data.number_of_k_points] self.reciprocal_lattice = reciprocal_lattice * 2 * math.pi self.CBM = extrema._calc_CBM(self.occupancy, self.energies) self.VBM = extrema._calc_VBM(self.occupancy, self.energies) self.fermi_energy = (self.CBM + self.VBM) / 2 self.dos = [] self.integrated_dos = [] self.check_data()