def transition_figure_parameters(tpp): """ Figure parameters for Transition levels plots :param tpp: Transition_Plot_Parameters object """ width, height = bf.get_screen_size() if tpp.figure.name == 'New Figure': fig = plt.figure(figsize=(width, height - 2.2)) else: fig = plt.figure(tpp.figure.name, figsize=(width, height - 2.2)) ax = fig.add_subplot(tpp.figure.nb_rows, tpp.figure.nb_cols, tpp.subplot_nb) # Main title if tpp.figure.nb_rows == 1 and tpp.figure.nb_cols == 1: new_title = '$' + tpp.title.replace(' ', '\ ') + '$' else: # add a letter for labelling new_title = '$' + tpp.title.replace(' ', '\ ') + '$' + \ [' ' + f + ')' for f in list(string.ascii_lowercase)][tpp.subplot_nb - 1] ax.set_title(new_title, fontsize=tpp.text_size, fontweight='bold') # Axis titles ylabel = r'$ \Delta E_F\ (eV)$' if tpp.label_display is True: # custom label display if tpp.ylabel_display is True: ax.set_ylabel(ylabel, fontsize=tpp.text_size) if tpp.common_ylabel_display is True: fig.text(0.017, 0.5, ylabel, ha='center', va='center', rotation='vertical', fontsize=tpp.text_size) if tpp.yticklabels_display is False: plt.setp(ax.get_yticklabels(), visible=False) else: if tpp.subplot_nb in np.array(range( tpp.figure.nb_rows)) * tpp.figure.nb_cols + 1: ax.set_ylabel(ylabel, fontsize=tpp.text_size) else: plt.setp(ax.get_yticklabels(), visible=False) ax.tick_params(width=1.5, length=4, labelsize=tpp.text_size - 4, axis='y') ax.spines['right'].set_visible(False) ax.spines['top'].set_visible(False) ax.spines['bottom'].set_visible(False) ax.xaxis.set_major_locator(tk.NullLocator()) ax.yaxis.set_ticks_position('left') ax.set_ylim(tpp.E_range[0], tpp.E_range[1]) return fig, ax
def formation_figure_parameters(fpp): """ Figure parameters for formation energy plots :param fpp: Formation_Plot_Parameters object """ width, height = bf.get_screen_size() if fpp.figure.name == 'New Figure': fig = plt.figure(figsize=(width, height-2.2)) else: fig = plt.figure(fpp.figure.name, figsize=(width, height-2.2)) ax = fig.add_subplot(fpp.figure.nb_rows, fpp.figure.nb_cols, fpp.subplot_nb) # Main title if fpp.figure.nb_rows == 1 and fpp.figure.nb_cols == 1: new_title = '$' + fpp.title.replace(' ', '\ ') + '$' else: # add a letter for labelling new_title = '$' + fpp.title.replace(' ', '\ ') + '$' + [' ' + f + ')' for f in list(string.ascii_lowercase)][fpp.subplot_nb - 1] ax.set_title(new_title, fontsize=fpp.text_size, fontweight='bold') # Axes titles xlabel = r'$\Delta E_F\ (eV)$' ylabel = '$E_{for}^q\ (eV)$' if fpp.label_display is True: # custom label display if fpp.xlabel_display is True: ax.set_xlabel(xlabel, fontsize=fpp.text_size) if fpp.ylabel_display is True: ax.set_ylabel(ylabel, fontsize=fpp.text_size) if fpp.common_ylabel_display is True: fig.text(0.017, 0.5, ylabel, ha='center', va='center', rotation='vertical', fontsize=fpp.text_size) if fpp.xticklabels_display is False: plt.setp(ax.get_xticklabels(), visible=False) if fpp.yticklabels_display is False: plt.setp(ax.get_yticklabels(), visible=False) else: # automatic label display (assuming that the energy range is the same for all plots) if fpp.subplot_nb >= (fpp.figure.nb_rows - 1) * fpp.figure.nb_cols + 1: ax.set_xlabel(xlabel, fontsize=fpp.text_size) else: plt.setp(ax.get_xticklabels(), visible=False) if fpp.subplot_nb in np.array(range(fpp.figure.nb_rows)) * fpp.figure.nb_cols + 1: ax.set_ylabel(ylabel, fontsize=fpp.text_size) else: plt.setp(ax.get_yticklabels(), visible=False) ax.tick_params(width=1.5, length=4, labelsize=fpp.text_size - 2) ax.set_xlim(fpp.E_range) if fpp.for_range != ['auto', 'auto']: ax.set_ylim(float(fpp.for_range[0]), float(fpp.for_range[1])) ax.grid('on') return fig, ax
def plot_dos(self): """ Plot the DOS of the calculation according to the parameters in dpp """ if self.doscar == '': raise bf.PydefDoscarError('No DOSCAR file specified') data = [[float(f) for f in q.split()] for q in self.doscar[6:]] # total and projected DOS energy, total_dos = np.transpose( data[:self.nedos])[:2] # Total DOS and energy E_F = self.fermi_energy E_CBM = self.CBM E_VBM = self.VBM if self.dpp.fermi_shift: shift = -E_F - self.dpp.input_shift else: shift = 0.0 - self.dpp.input_shift if self.dpp.normalise_dos is True: normalise = float(self.dosmax) else: normalise = 1.0 energy += shift E_F += shift E_CBM += shift E_VBM += shift if self.dpp.display_proj_dos is True: # Orbitals projected DOS dos_op = data[self.nedos:] # Projected dos on every orbitals (s, px, py, pz, dxx,...) for each atom (and energy) dos_opa_all = [ np.transpose(f) for f in [ dos_op[(self.nedos + 1) * i - self.nedos:(self.nedos + 1) * i] for i in range(1, sum(self.nb_atoms) + 1) ] ] # Projected dos on orbitals for every atom if len(self.orbitals) == 3: # s p d case dos_opa_spdf = [[ np.array(f[1]), np.sum(f[2:5], axis=0), np.sum(f[5:10], axis=0) ] for f in dos_opa_all] elif len(self.orbitals) == 4: # s p d f case dos_opa_spdf = [[ np.array(f[1]), np.sum(f[2:5], axis=0), np.sum(f[5:10], axis=0), np.sum(f[10:17], axis=0) ] for f in dos_opa_all] else: return None # Atomic species index for splitting ats_indices = [ int(sum(self.nb_atoms[:f + 1])) for f in range(len(self.nb_atoms)) ][:-1] # Projected dos on orbitals for every atoms divided between atomic species dos_opa = [ dos_opa_spdf[i:j] for i, j in zip([0] + ats_indices, ats_indices + [None]) ] if self.dpp.dos_type == 'OPAS': # Projected DOS on s, p, d orbitals for each atomic species if len(self.orbitals) == 3: proj_dos = [[ np.sum([f[0] for f in g], axis=0), np.sum([f[1] for f in g], axis=0), np.sum([f[2] for f in g], axis=0) ] for g in dos_opa] proj_labels = [[ '$' + f + '\ s$', '$' + f + '\ p$', '$' + f + '\ d$' ] for f in self.atoms_types] elif len(self.orbitals) == 4: proj_dos = [[ np.sum([f[0] for f in g], axis=0), np.sum([f[1] for f in g], axis=0), np.sum([f[2] for f in g], axis=0), np.sum([f[3] for f in g], axis=0) ] for g in dos_opa] proj_labels = [[ '$' + f + '\ s$', '$' + f + '\ p$', '$' + f + '\ d$', '$' + f + '\ f$' ] for f in self.atoms_types] else: return None colors = self.dpp.colors_proj if self.dpp.tot_proj_dos is True: # Total projected DOS on s, p, d orbitals for each atomic species proj_dos = [np.sum(f, axis=0) for f in proj_dos] proj_labels = [['$' + f + '$'] for f in self.atoms_types] colors = self.dpp.colors_tot proj_dos_dict = dict(zip(self.atoms_types, proj_dos)) proj_labels_dict = dict(zip(self.atoms_types, proj_labels)) proj_dos = [proj_dos_dict[f] for f in self.dpp.choice_opas] proj_labels = [ proj_labels_dict[f] for f in self.dpp.choice_opas ] elif self.dpp.dos_type == 'OPA': # Projected DOS on s, p, d orbitals for every atoms proj_dos = dos_opa_spdf if len(self.orbitals) == 3: proj_labels = [[ '$' + f + '\ s$', '$' + f + '\ p$', '$' + f + '\ d$' ] for f in self.atoms] elif len(self.orbitals) == 4: proj_labels = [[ '$' + f + '\ s$', '$' + f + '\ p$', '$' + f + '\ d$', '$' + f + '\ f$' ] for f in self.atoms] colors = self.dpp.colors_proj if self.dpp.tot_proj_dos is True: # Total projected DOS on s, p, d orbitals for every atoms proj_dos = [np.sum(f, axis=0) for f in proj_dos] proj_labels = [['$' + f + '$'] for f in self.atoms] colors = self.dpp.colors_tot proj_dos_dict = dict(zip(self.atoms, proj_dos)) proj_labels_dict = dict(zip(self.atoms, proj_labels)) proj_dos = [proj_dos_dict[f] for f in self.dpp.choice_opa] proj_labels = [ proj_labels_dict[f] for f in self.dpp.choice_opa ] # ----------------------------------------------- PLOT PARAMETERS ---------------------------------------------- width, height = bf.get_screen_size() if self.dpp.figure.name == 'New Figure': fig = plt.figure(figsize=(width, height - 2.2)) else: fig = plt.figure(self.dpp.figure.name, figsize=(width, height - 2.2)) ax = fig.add_subplot(self.dpp.figure.nb_rows, self.dpp.figure.nb_cols, self.dpp.subplot_nb) # Main title if self.dpp.figure.nb_rows == 1 and self.dpp.figure.nb_cols == 1: new_title = '$' + self.dpp.title.replace(' ', '\ ') + '$' else: # add a letter for labelling new_title = '$' + self.dpp.title.replace(' ', '\ ') + '$' + [ ' ' + f + ')' for f in list(string.ascii_lowercase) ][self.dpp.subplot_nb - 1] ax.set_title(new_title, fontsize=self.dpp.text_size, fontweight='bold') # Axes titles if self.dpp.fermi_shift is True: xlabel = '$E-E_F\ (eV)$' else: xlabel = '$E\ (eV)$' ylabel = '$DOS\ (a.u.)$' if self.dpp.label_display is True: # custom label display if self.dpp.xlabel_display is True: ax.set_xlabel(xlabel, fontsize=self.dpp.text_size) if self.dpp.ylabel_display is True: ax.set_ylabel(ylabel, fontsize=self.dpp.text_size) if self.dpp.common_ylabel_display is True: fig.text(0.017, 0.5, ylabel, ha='center', va='center', rotation='vertical', fontsize=self.dpp.text_size) if self.dpp.xticklabels_display is False: plt.setp(ax.get_xticklabels(), visible=False) else: # automatic label display (assuming that the energy range is the same for all plots) if self.dpp.subplot_nb >= (self.dpp.figure.nb_rows - 1) * self.dpp.figure.nb_cols + 1: ax.set_xlabel(xlabel, fontsize=self.dpp.text_size) else: plt.setp(ax.get_xticklabels(), visible=False) if self.dpp.subplot_nb in np.array(range( self.dpp.figure.nb_rows)) * self.dpp.figure.nb_cols + 1: ax.set_ylabel(ylabel, fontsize=self.dpp.text_size) ax.yaxis.set_major_locator(tk.NullLocator()) ax.tick_params(width=1.5, length=4, labelsize=self.dpp.text_size - 2) ax.set_xlim(self.dpp.E_range) ax.set_ylim(self.dpp.DOS_range) # ----------------------------------------------- PLOT & DISPLAY ----------------------------------------------- if self.dpp.display_proj_dos is True: ax.stackplot(energy, np.row_stack(proj_dos) / normalise, colors=colors, linewidths=0, labels=np.concatenate(proj_labels)) if self.dpp.display_total_dos is True: ax.plot(energy, total_dos / normalise, color='black', label='Total DOS') # Display energy levels if self.dpp.display_BM_levels is True: ax.plot([E_CBM, E_CBM], [self.dpp.DOS_range[0], self.dpp.DOS_range[1]], '--', color='red') ax.text(E_CBM, self.dpp.DOS_range[1] * 0.75, '$E_C$', fontsize=self.dpp.text_size - 2, color='red') ax.plot([E_VBM, E_VBM], [self.dpp.DOS_range[0], self.dpp.DOS_range[1]], '--', color='blue') ax.text(E_VBM, self.dpp.DOS_range[1] * 0.75, '$E_V$', fontsize=self.dpp.text_size - 2, color='blue') if self.dpp.display_Fermi_level is True: ax.plot([E_F, E_F], [self.dpp.DOS_range[0], self.dpp.DOS_range[1]], '--', color='black') ax.text(E_F, self.dpp.DOS_range[1] * 0.75, '$E_F$', fontsize=self.dpp.text_size - 2, color='black') # Legends if self.dpp.display_legends is True: legend = ax.legend(fontsize=self.dpp.text_size - 6, loc='upper right') legend.draggable() fig.tight_layout(rect=(0.02, 0, 1, 1)) fig.show()
def potential_alignment_correction(Host_Cell, Defect_Cell, DefectS, spheres_radius, plotsphere=True, display_atom_name=False): """ Compute the potential alignment correction by calculating the average difference of electrostatic potentials of atoms far away from the defects and their images. This is done by considering spheres around the defects and their images with the same radius. Only the atoms outside these spheres (so at a minimum distance from a defect) are considered. :param Host_Cell: Cell object of the host cell calculation :param Defect_Cell: Cell object of the defect cell calculation :param DefectS: list of Defect objects :param spheres_radius: radius of the spheres in angstrom (float) :param plotsphere: if True, then represent the spheres and positions of the atoms :param display_atom_name: if True, display the name of each atom on the representation """ # Positions of the defects and their images [ f.get_defect_position(Host_Cell.atoms_positions, Defect_Cell.atoms_positions) for f in DefectS ] # retrieve the defects positions defects_position = [np.array(f.coord) for f in DefectS] # positions of the defects defect_images_positions = [ np.dot(Host_Cell.cell_parameters, f) for f in [[0, 0, 0], [0, 0, -1], [0, 0, 1], [1, 0, 0], [-1, 0, 0], [0, -1, 0], [0, 1, 0]] ] # relative position of the images of the defects defects_positions = [ [f + g for g in defect_images_positions] for f in defects_position ] # all positions of the defects and their respective images # Removing useless data atoms_positions_def = copy.deepcopy( Defect_Cell.atoms_positions ) # positions of the atoms in the defect cell V_host = copy.deepcopy( Host_Cell.potentials ) # electrostatic potentials of the atoms of the host cell V_def = copy.deepcopy( Defect_Cell.potentials ) # electrostatic potentials of the atoms of the defect cell atoms_host = list(copy.deepcopy( Host_Cell.atoms)) # atoms labels of the host cell atoms_def = list(copy.deepcopy( Defect_Cell.atoms)) # atoms labels of the defect cell for Defect in DefectS: if Defect.defect_type == 'Vacancy': V_host.pop( Defect.atom[0] ) # remove the electrostatic potential of the atom removed from the host cell data atoms_host.remove(Defect.atom[0]) elif Defect.defect_type == 'Interstitial': V_def.pop( Defect.atom[0] ) # remove the electrostatic potential of the atom added from the defect cell data atoms_positions_def.pop( Defect.atom[0] ) # remove the position of the corresponding atom so the number of positions and potentials match atoms_def.remove(Defect.atom[0]) elif Defect.defect_type == 'Substitutional': V_host.pop(Defect.atom[0]) V_def.pop(Defect.atom[1]) atoms_positions_def.pop(Defect.atom[1]) atoms_host.remove(Defect.atom[0]) atoms_def.remove(Defect.atom[1]) # Compute the average electrostatic potential outside the spheres V_host_list = [V_host[f] for f in atoms_host] V_def_list = [V_def[f] for f in atoms_def] atoms_positions_def_list = [atoms_positions_def[f] for f in atoms_def] distances = [ np.array([bf.distance(f, g) for f in atoms_positions_def_list]) for g in np.concatenate(defects_positions) ] # distance of each atom from each defect min_distances = [ min(f) for f in np.transpose(distances) ] # minimum distance between an atom and any defect or its image index_out = [ np.where(f > spheres_radius)[0] for f in distances ] # index of the atoms outside the spheres which centers are the defects common_index_out = bf.get_common_values( index_out) # index of the atoms outside all the spheres radius E_PA = np.array(V_def_list) - np.array( V_host_list ) # difference of electrostatic energy between the defect and host cells E_PA_out = np.mean( E_PA[common_index_out] ) # average electrostatic difference between the defect and host cells taking into # account only the atoms outside the spheres if plotsphere is True: width, height = bf.get_screen_size() fig = plt.figure(figsize=(width, height - 2.2)) ax = fig.add_axes([0.01, 0.1, 0.45, 0.8], projection='3d', aspect='equal') # Display the spheres and defects [ bf.plot_sphere(spheres_radius, f[0], ax, '-') for f in defects_positions ] # spheres around the defects [[bf.plot_sphere(spheres_radius, f, ax, '--') for f in q[1:]] for q in defects_positions ] # spheres around the images of the defects [[ ax.scatter(f[0], f[1], f[2], color='red', s=400, marker='*') for f in q ] for q in defects_positions ] # Position of the defects objects and images [[ ax.text(f[0], f[1], f[2] + 0.2, s='$' + g.name + '$', ha='center', va='bottom', color='red', fontsize=20) for f in q ] for q, g in zip(defects_positions, DefectS)] # Atoms positions atoms_positions = np.transpose(atoms_positions_def_list) scatterplot = ax.scatter(atoms_positions[0], atoms_positions[1], atoms_positions[2], s=100, c=E_PA, cmap='hot', depthshade=False) if display_atom_name is True: [ ax.text(f[0], f[1], f[2], s=g, ha='center', va='bottom') for f, g in zip(atoms_positions_def_list, atoms_def) ] # Plot parameters ax._axis3don = False # X limit is set as the maximum value of the projection of the cristallographic parameters on the x-axe, etc. ax.set_xlim(0, np.max(np.transpose(Defect_Cell.cell_parameters)[0])) ax.set_ylim(0, np.max(np.transpose(Defect_Cell.cell_parameters)[1])) ax.set_zlim(0, np.max(np.transpose(Defect_Cell.cell_parameters)[2])) # Colorbar temp1 = fig.get_window_extent() temp2 = ax.get_window_extent() ax_cb = fig.add_axes([ temp2.x0 / temp1.x1, temp2.y0 / temp1.y1 - 0.04, (temp2.x1 - temp2.x0) / temp1.x1, 0.03 ]) cb = fig.colorbar(scatterplot, cax=ax_cb, orientation='horizontal') cb.set_label('$\Delta V\ (eV)$', fontsize=24) cb.ax.tick_params(width=1.25, length=2, labelsize=16) return [E_PA_out, fig] else: return [min_distances, E_PA, E_PA_out]