Example #1
0
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
Example #2
0
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
Example #3
0
    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]