Пример #1
0
def obtain_eigenvectors_from_phonopy(structure, q_vector, NAC=False):

    #   Checking data
    force_atoms_file = structure.get_force_set().item(0)["natom"]
    force_atoms_input = np.product(np.diagonal(structure.get_super_cell_phonon())) * structure.get_number_of_atoms()

    if force_atoms_file != force_atoms_input:
        print("Error: FORCE_SETS file does not match with SUPERCELL MATRIX")
        exit()

    #   Preparing the bulk type object
    bulk = PhonopyAtoms(
        symbols=structure.get_atomic_types(),
        scaled_positions=structure.get_scaled_positions(),
        cell=structure.get_cell().T,
    )

    phonon = Phonopy(
        bulk,
        structure.get_super_cell_phonon(),
        primitive_matrix=structure.get_primitive_matrix(),
        is_auto_displacements=False,
    )

    # Non Analytical Corrections (NAC) from Phonopy [Frequencies only, eigenvectors no affected by this option]
    if NAC:
        print("Phonopy warning: Using Non Analytical Corrections")
        get_is_symmetry = True  # from phonopy:   settings.get_is_symmetry()
        primitive = phonon.get_primitive()
        nac_params = parse_BORN(primitive, get_is_symmetry)
        phonon.set_nac_params(nac_params=nac_params)

    phonon.set_displacement_dataset(copy.deepcopy(structure.get_force_set()))
    phonon.produce_force_constants()

    frequencies, eigenvectors = phonon.get_frequencies_with_eigenvectors(q_vector)

    # Making sure eigenvectors are orthonormal (can be omitted)
    if True:
        eigenvectors = eigenvectors_normalization(eigenvectors)
        print("Testing eigenvectors orthonormality")
        np.set_printoptions(precision=3, suppress=True)
        print(np.dot(eigenvectors.T, np.ma.conjugate(eigenvectors)).real)
        np.set_printoptions(suppress=False)

    # Arranging eigenvectors by atoms and dimensions
    number_of_dimensions = structure.get_number_of_dimensions()
    number_of_primitive_atoms = structure.get_number_of_primitive_atoms()

    arranged_ev = np.array(
        [
            [
                [eigenvectors[j * number_of_dimensions + k, i] for k in range(number_of_dimensions)]
                for j in range(number_of_primitive_atoms)
            ]
            for i in range(number_of_primitive_atoms * number_of_dimensions)
        ]
    )

    return arranged_ev, frequencies
Пример #2
0
def ir_intensity_phonopy(
    run_dir=".",
    vasprun="vasprun.xml",
    BornFileName="BORN",
    PoscarName="POSCAR",
    ForceConstantsName="FORCE_CONSTANTS",
    supercell=[[1, 0, 0], [0, 1, 0], [0, 0, 1]],
    nac=True,
    symprec=1e-5,
    primitive=[[1, 0, 0], [0, 1, 0], [0, 0, 1]],
    degeneracy_tolerance=1e-5,
    vector=[0, 0, 0],
    smoothen=False,
):
    """Calculate IR intensity using DFPT and phonopy."""
    from phonopy import Phonopy
    from phonopy.interface.vasp import read_vasp
    from phonopy.file_IO import (
        parse_BORN,
        parse_FORCE_CONSTANTS,
    )
    import shutil
    from phonopy.units import VaspToCm

    # from phonopy.phonon.degeneracy import (
    #    degenerate_sets as get_degenerate_sets,
    # )
    # adapted from https://github.com/JaGeo/IR
    # TODO: Make directory indepndent
    cwd = os.getcwd()
    print("Directory:", cwd)
    os.chdir(run_dir)
    if not os.path.exists(vasprun):
        shutil.copy2(vasprun, "vasprun.xml")
    cmd = str("phonopy --fc vasprun.xml")
    os.system(cmd)
    born_file = os.path.join(os.getcwd(), BornFileName)
    cmd = str("phonopy-vasp-born >  ") + str(born_file)
    os.system(cmd)
    from jarvis.io.vasp.outputs import Vasprun

    v = Vasprun(vasprun)
    strt = v.all_structures[0]
    strt.write_poscar(PoscarName)
    unitcell = read_vasp(PoscarName)
    phonon = Phonopy(
        unitcell,
        supercell_matrix=supercell,
        primitive_matrix=primitive,
        factor=VaspToCm,
        symprec=symprec,
    )
    natoms = phonon.get_primitive().get_number_of_atoms()
    force_constants = parse_FORCE_CONSTANTS(filename=ForceConstantsName)
    phonon.set_force_constants(force_constants)
    masses = phonon.get_primitive().get_masses()
    phonon.set_masses(masses)
    BORN_file = parse_BORN(phonon.get_primitive(), filename=BornFileName)
    BORN_CHARGES = BORN_file["born"]
    # print ('born_charges2',BORN_CHARGES)
    if nac:
        phonon.set_nac_params(BORN_file)
    frequencies, eigvecs = phonon.get_frequencies_with_eigenvectors(vector)
    # frequencies=VaspToTHz*frequencies/VaspToCm
    # x, y = ir_intensity(
    #    phonon_eigenvectors=np.real(eigvecs),
    #    phonon_eigenvalues=frequencies,
    #    masses=masses, #np.ones(len(masses)),
    #    born_charges=born_charges,
    #    smoothen=smoothen,
    # )
    NumberOfBands = len(frequencies)
    EigFormat = {}
    for alpha in range(NumberOfBands):
        laufer = 0
        for beta in range(natoms):
            for xyz in range(0, 3):
                EigFormat[beta, alpha, xyz] = eigvecs[laufer][alpha]
                laufer = laufer + 1
    Intensity = {}
    intensities = []
    for freq in range(len(frequencies)):
        Intensity[freq] = 0
        tmp = 0
        for alpha in range(3):
            asum = 0
            for n in range(natoms):
                for beta in range(3):
                    asum = asum + BORN_CHARGES[n, alpha, beta] * np.real(
                        EigFormat[n, freq, beta]
                    ) / np.sqrt(masses[n])
                    tmp += asum
            Intensity[freq] = Intensity[freq] + np.power(np.absolute(asum), 2)
        intensities.append(Intensity[freq])
    os.chdir(cwd)
    return frequencies, intensities
class AtomicContributionsCalculator:
    def __init__(self,
                 PoscarName='POSCAR',
                 ForceConstants=False,
                 ForceFileName='FORCE_SETS',
                 BornFileName='BORN',
                 supercell=[[1, 0, 0], [0, 1, 0], [0, 0, 1]],
                 nac=False,
                 symprec=1e-5,
                 masses=[],
                 primitive=[[1, 0, 0], [0, 1, 0], [0, 0, 1]],
                 degeneracy_tolerance=1e-4,
                 factor=VaspToCm,
                 q=[0, 0, 0]):
        """Class that calculates contributions of each atom to the phonon modes at Gamma
            Args:
            PoscarName (str): name of the POSCAR that was used for the phonon calculation
            BornFileName (str): name of the file with BORN charges (formatted with outcar-born)
            ForceConstants (boolean): If True, ForceConstants are read in. If False, forces are read in.
            ForceFileName (str): name of the file including force constants or forces
            supercell (list of lists): reads in supercell
            nac (boolean): If true, NAC is applied. (please be careful if you give a primitive cell. NAC should then be calculated for primitive cell)
            symprec (float): contains symprec tag as used in Phonopy
            masses (list): Masses in this list are used instead of the ones prepared in Phonopy. Useful for isotopes.
            primitive (list of lists): contains rotational matrix to arrive at primitive cell
            factor (float): VaspToCm or VaspToTHz or VaspToEv
            q (list of int): q point for the plot. So far only Gamma works

        """

        self.__unitcell = read_vasp(PoscarName)
        self.__supercell = supercell
        self.__phonon = Phonopy(self.__unitcell,
                                supercell_matrix=self.__supercell,
                                primitive_matrix=primitive,
                                factor=factor,
                                symprec=symprec)
        self.__natoms = self.__phonon.get_primitive().get_number_of_atoms()
        self.__symbols = self.__phonon.get_primitive().get_chemical_symbols()
        self.__factor = factor
        # If different masses are supplied
        if masses:
            self.__phonon.set_masses(masses)
        self.__masses = self.__phonon.get_primitive().get_masses()

        # Forces or Force Constants
        if not ForceConstants:
            self.__set_ForcesSets(filename=ForceFileName, phonon=self.__phonon)

        if ForceConstants:
            self.__set_ForceConstants(filename=ForceFileName,
                                      phonon=self.__phonon)

        # Apply NAC Correction
        if nac:
            BORN_file = parse_BORN(self.__phonon.get_primitive(),
                                   filename=BornFileName)
            self.__BORN_CHARGES = BORN_file['born']
            self.__phonon.set_nac_params(BORN_file)

        # frequencies and eigenvectors at Gamma
        self._frequencies, self._eigvecs = self.__phonon.get_frequencies_with_eigenvectors(
            q)

        self.__NumberOfBands = len(self._frequencies)

        # Nicer format of the eigenvector file
        self.__FormatEigenvectors()

        # Get Contributions
        self.__set_Contributions()
        self.__set_Contributions_withoutmassweight()

        # irrepsobject
        try:
            self.__set_IRLabels(phonon=self.__phonon,
                                degeneracy_tolerance=degeneracy_tolerance,
                                factor=factor,
                                q=q,
                                symprec=symprec)
        except:
            print(
                "Cannot assign IR labels. Play around with symprec, degeneracy_tolerance. The point group could not be implemented."
            )
            self.__freqlist = {}
            for i in range(0, len(self._frequencies)):
                self.__freqlist[i] = i

    def show_primitivecell(self):
        """
        shows primitive cell used for the plots and evaluations on screen
        """
        print(self.__phonon.get_primitive())

    def __set_ForcesSets(self, filename, phonon):
        """
        sets forces

        """

        force_sets = parse_FORCE_SETS(filename=filename)
        phonon.set_displacement_dataset(force_sets)
        phonon.produce_force_constants()

    def __set_ForceConstants(self, filename, phonon):
        """
        sets force constants
        """
        force_constants = parse_FORCE_CONSTANTS(filename=filename)
        phonon.set_force_constants(force_constants)

    def __set_IRLabels(self, phonon, degeneracy_tolerance, factor, q, symprec):
        """
        sets list of irreducible labels and list of frequencies without degeneracy
        """
        # phonon.set_dynamical_matrix()
        self.__Irrep = IrReps(dynamical_matrix=phonon._dynamical_matrix,
                              q=q,
                              is_little_cogroup=False,
                              nac_q_direction=None,
                              factor=factor,
                              symprec=symprec,
                              degeneracy_tolerance=degeneracy_tolerance)
        self.__Irrep.run()
        self._IRLabels = self.__Irrep._get_ir_labels()
        self.__ListOfModesWithDegeneracy = self.__Irrep._get_degenerate_sets()
        self.__freqlist = {}
        for band in range(len(self.__ListOfModesWithDegeneracy)):
            self.__freqlist[band] = self.__ListOfModesWithDegeneracy[band][0]

    def __FormatEigenvectors(self):
        """
        Formats eigenvectors to a dictionary: the first argument is the number of bands, the second the number of atoms, the third the Cartesian coordinate
        """

        self._EigFormat = {}
        for alpha in range(self.__NumberOfBands):
            laufer = 0
            for beta in range(self.__natoms):
                for xyz in range(0, 3):
                    self._EigFormat[beta, alpha,
                                    xyz] = self._eigvecs[laufer][alpha]
                    laufer = laufer + 1

    def _Eigenvector(self, atom, band, xoryorz):
        """
        Gives a certain eigenvector corresponding to one specific atom, band and Cartesian coordinate

        args:
            atom (int) : number of the atoms (same order as in POSCAR)
            band (int) : number of the frequency (ordered by energy)
            xoryorz (int): Cartesian coordinate of the eigenvector


        """

        return np.real(self._EigFormat[atom, band, xoryorz])

    def __massEig(self, atom, band, xoryorz):
        """
        Gives a certain eigenvector divided by sqrt(mass of the atom) corresponding to one specific atom, band and Cartesian coordinate

        args:
            atom (int) : number of the atoms (same order as in POSCAR)
            band (int) : number of the frequency (ordered by energy)
            xoryorz (int): Cartesian coordinate of the eigenvector


        """

        return self._Eigenvector(atom, band, xoryorz) / np.sqrt(
            self.__masses[atom])

    def __set_Contributions(self):
        """
        Calculate contribution of each atom to modes"
        """
        self._PercentageAtom = {}
        for freq in range(len(self._frequencies)):
            for atom in range(self.__natoms):
                sum = 0
                for alpha in range(3):
                    sum = sum + abs(
                        self._Eigenvector(atom, freq, alpha) *
                        self._Eigenvector(atom, freq, alpha))
                self._PercentageAtom[freq, atom] = sum

    def __get_Contributions(self, band, atom):
        """
        Gives contribution of specific atom to modes with certain frequency
        args:
            band (int): number of the frequency (ordered by energy)
        """
        return self._PercentageAtom[band, atom]

    def __set_Contributions_withoutmassweight(self):
        """
        Calculate contribution of each atom to modes
        Here, eigenvectors divided by sqrt(mass of the atom) are used for the calculation
        """
        self.__PercentageAtom_massweight = {}
        atomssum = {}
        saver = {}
        for freq in range(len(self._frequencies)):
            atomssum[freq] = 0
            for atom in range(self.__natoms):
                sum = 0
                for alpha in range(3):
                    sum = sum + abs(
                        self.__massEig(atom, freq, alpha) *
                        self.__massEig(atom, freq, alpha))
                atomssum[freq] = atomssum[freq] + sum

                # Hier muss noch was hin, damit rechnung richtig wird
                saver[freq, atom] = sum

        for freq in range(len(self._frequencies)):
            for atom in range(self.__natoms):
                self.__PercentageAtom_massweight[
                    freq, atom] = saver[freq, atom] / atomssum[freq]

    def __get_Contributions_withoutmassweight(self, band, atom):
        """
        Gives contribution of specific atom to modes with certain frequency
        Here, eigenvectors divided by sqrt(mass of the atom) are used for the calculation

        args:
                   band (int): number of the frequency (ordered by energy)
        """
        return self.__PercentageAtom_massweight[band, atom]

    def write_file(self, filename="Contributions.txt"):
        """
        Writes contributions of each atom in file

        args:

            filename (str): filename
        """
        file = open(filename, 'w')
        file.write('Frequency Contributions \n')
        for freq in range(len(self._frequencies)):
            file.write('%s ' % (self._frequencies[freq]))
            for atom in range(self.__natoms):
                file.write('%s ' % (self.__get_Contributions(freq, atom)))
            file.write('\n ')

        file.close()

    def plot(self,
             atomgroups,
             colorofgroups,
             legendforgroups,
             freqstart=[],
             freqend=[],
             freqlist=[],
             labelsforfreq=[],
             filename="Plot.eps",
             transmodes=True,
             massincluded=True):
        """
        Plots contributions of atoms/several atoms to modes with certain frequencies (freqlist starts at 1 here)

        args:
            atomgroups (list of list of ints): list that groups atoms, atom numbers start at 1
            colorofgroups (list of str): list that matches a color to each group of atoms
            legendforgroups (list of str): list that gives a legend for each group of atoms
            freqstart (float): min frequency of plot in cm-1
            freqend (float): max frequency of plot in cm-1
            freqlist (list of int): list of frequencies that will be plotted; if no list is given all frequencies in the range from freqstart to freqend are plotted, list begins at 1
            labelsforfreq (list of str): list of labels (str) for each frequency
            filename (str): filename for the plot
            transmodes (boolean): if transmode is true than translational modes are shown
            massincluded (boolean): if false, uses eigenvector divided by sqrt(mass of the atom) for the calculation instead of the eigenvector
        """

        p = {}
        summe = {}
        try:
            if labelsforfreq == []:
                labelsforfreq = self._IRLabels
        except:
            print("")

        if freqlist == []:
            freqlist = self.__freqlist

        else:

            for freq in range(len(freqlist)):
                freqlist[freq] = freqlist[freq] - 1

        newfreqlist = []
        newlabelsforfreq = []
        for freq in range(len(freqlist)):
            if not transmodes:
                if not freqlist[freq] in [0, 1, 2]:
                    newfreqlist.append(freqlist[freq])
                    try:
                        newlabelsforfreq.append(labelsforfreq[freq])
                    except:
                        newlabelsforfreq.append('')

            else:
                newfreqlist.append(freqlist[freq])
                try:
                    newlabelsforfreq.append(labelsforfreq[freq])
                except:
                    newlabelsforfreq.append('')

        self._plot(atomgroups=atomgroups,
                   colorofgroups=colorofgroups,
                   legendforgroups=legendforgroups,
                   freqstart=freqstart,
                   freqend=freqend,
                   freqlist=newfreqlist,
                   labelsforfreq=newlabelsforfreq,
                   filename=filename,
                   massincluded=massincluded)

    def _plot(self,
              atomgroups,
              colorofgroups,
              legendforgroups,
              freqstart=[],
              freqend=[],
              freqlist=[],
              labelsforfreq=[],
              filename="Plot.eps",
              massincluded=True):
        """
        Plots contributions of atoms/several atoms to modes with certain frequencies (freqlist starts at 0 here)

        args:
            atomgroups (list of list of ints): list that groups atoms, atom numbers start at 1
            colorofgroups (list of str): list that matches a color to each group of atoms
            legendforgroups (list of str): list that gives a legend for each group of atoms
            freqstart (float): min frequency of plot in cm-1
            freqend (float): max frequency of plot in cm-1
            freqlist (list of int): list of frequencies that will be plotted; this freqlist starts at 0
            labelsforfreq (list of str): list of labels (str) for each frequency
            filename (str): filename for the plot
            massincluded (boolean): if false, uses eigenvector divided by sqrt(mass of the atom) for the calculation instead of the eigenvector
        """
        # setting of some parameters in matplotlib: http://matplotlib.org/users/customizing.html
        mpl.rcParams["savefig.directory"] = os.chdir(os.getcwd())
        mpl.rcParams["savefig.format"] = 'eps'

        fig, ax1 = plt.subplots()
        p = {}
        summe = {}

        for group in range(len(atomgroups)):
            color1 = colorofgroups[group]
            Entry = {}
            for freq in range(len(freqlist)):
                Entry[freq] = 0
            for number in atomgroups[group]:
                # set the first atom to 0
                atom = int(number) - 1
                for freq in range(len(freqlist)):
                    if massincluded:
                        Entry[freq] = Entry[freq] + self.__get_Contributions(
                            freqlist[freq], atom)
                    else:
                        Entry[freq] = Entry[
                            freq] + self.__get_Contributions_withoutmassweight(
                                freqlist[freq], atom)
                    if group == 0:
                        summe[freq] = 0

            # plot bar chart
            p[group] = ax1.barh(np.arange(len(freqlist)),
                                list(Entry.values()),
                                left=list(summe.values()),
                                color=color1,
                                edgecolor="black",
                                height=1,
                                label=legendforgroups[group])
            # needed for "left" in the bar chart plot
            for freq in range(len(freqlist)):
                if group == 0:
                    summe[freq] = Entry[freq]
                else:
                    summe[freq] = summe[freq] + Entry[freq]
        labeling = {}
        for freq in range(len(freqlist)):
            labeling[freq] = round(self._frequencies[freqlist[freq]], 1)
        # details for the plot
        plt.rc("font", size=8)
        ax1.set_yticklabels(list(labeling.values()))
        ax1.set_yticks(np.arange(0.0, len(self._frequencies) + 0.0))
        ax2 = ax1.twinx()
        ax2.set_yticklabels(labelsforfreq)
        ax2.set_yticks(np.arange(0.0, len(self._frequencies) + 0.0))
        # start and end of the yrange
        start, end = self.__get_freqbordersforplot(freqstart, freqend,
                                                   freqlist)
        ax1.set_ylim(start - 0.5, end - 0.5)
        ax2.set_ylim(start - 0.5, end - 0.5)
        ax1.set_xlim(0.0, 1.0)
        ax1.set_xlabel('Contribution of Atoms to Modes')
        if self.__factor == VaspToCm:
            ax1.set_ylabel('Wavenumber (cm$^{-1}$)')
        elif self.__factor == VaspToTHz:
            ax1.set_ylabel('Frequency (THz)')
        elif self.__factor == VaspToEv:
            ax1.set_ylabel('Frequency (eV)')
        else:
            ax1.set_ylabel('Frequency')
        ax1.legend(bbox_to_anchor=(0, 1.02, 1, 0.2),
                   loc="lower left",
                   mode="expand",
                   borderaxespad=0,
                   ncol=len(atomgroups))

        plt.savefig(filename, bbox_inches="tight")

        plt.show()

    def __get_freqbordersforplot(self, freqstart, freqend, freqlist):
        if freqstart == []:
            start = 0.0
        else:
            for freq in range(len(freqlist)):
                if self._frequencies[freqlist[freq]] > freqstart:
                    start = freq
                    break
                else:
                    start = len(freqlist)
        if freqend == []:
            end = len(freqlist)
        else:
            for freq in range(len(freqlist) - 1, 0, -1):
                if self._frequencies[freqlist[freq]] < freqend:
                    end = freq + 1
                    break
                else:
                    end = len(freqlist)

        return start, end

    def plot_irred(self,
                   atomgroups,
                   colorofgroups,
                   legendforgroups,
                   transmodes=False,
                   irreps=[],
                   filename="Plot.eps",
                   freqstart=[],
                   freqend=[],
                   massincluded=True):
        """
        Plots contributions of atoms/several atoms to modes with certain irreducible representations (selected by Mulliken symbol)
        args:
            atomgroups (list of list of ints): list that groups atoms, atom numbers start at 1
            colorofgroups (list of str): list that matches a color to each group of atoms
            legendforgroups (list of str): list that gives a legend for each group of atoms
            transmodes (boolean): translational modes are included if true
            irreps (list of str): list that includes the irreducible modes that are plotted
            filename (str): filename for the plot
            massincluded (boolean): if false, uses eigenvector divided by sqrt(mass of the atom) for the calculation instead of the eigenvector
        """

        freqlist = []
        labelsforfreq = []
        for band in range(len(self.__freqlist)):
            if self._IRLabels[band] in irreps:
                if not transmodes:
                    if not self.__freqlist[band] in [0, 1, 2]:
                        freqlist.append(self.__freqlist[band])
                        labelsforfreq.append(self._IRLabels[band])
                else:
                    freqlist.append(self.__freqlist[band])
                    labelsforfreq.append(self._IRLabels[band])

        self._plot(atomgroups=atomgroups,
                   colorofgroups=colorofgroups,
                   legendforgroups=legendforgroups,
                   filename=filename,
                   freqlist=freqlist,
                   labelsforfreq=labelsforfreq,
                   freqstart=freqstart,
                   freqend=freqend,
                   massincluded=massincluded)
Пример #4
0
# labels = ['$\Gamma$', 'X', 'U|K', '$\Gamma$', 'L']

# DOS grid
# from kpoints_gen import get_grid_num
# k_grids = get_grid_num(phonon.get_primitive().cell, precision=pdos_precision)
phonon.run_mesh(
    pdos_mesh,
    # is_mesh_symmetry=False,
    with_eigenvectors=True,
    is_gamma_center=True,
)

######### eigenvectors #########
# freq, eigvec = phonon.get_frequencies_with_eigenvectors([0.00000333,0.00000333,0.])
# freq, eigvec = phonon.get_frequencies_with_eigenvectors([0.,0.000001,0.])
freq, eigvec = phonon.get_frequencies_with_eigenvectors(G)
eigvec = eigvec.T
np.savez('freqNeigvec', freq=freq, eigvec=eigvec)

########## Plot ################
from subprocess import call
#### Band plot
if run_mode == 'phonon':
    #
    N_q = 100
    bands = ssp.make_band(path, N_q)

    phonon.run_band_structure(
        bands,
        with_eigenvectors=True,
    )
Пример #5
0
class IR:
    def __init__(self, PoscarName='POSCAR', BornFileName='BORN', ForceConstants=False, ForceFileName='FORCE_SETS',
                 supercell=[[1, 0, 0], [0, 1, 0], [0, 0, 1]]
                 , nac=False, symprec=1e-5, masses=[], primitive=[[1, 0, 0], [0, 1, 0], [0, 0, 1]],
                 degeneracy_tolerance=1e-5):
        """
        Class for calculating the IR spectra in the dipole approximation according to:
        P. Giannozzi and S. Baroni, J. Chem. Phys. 100, 8537 (1994).
        and
        D. Karhanek, T. Bucko, J. Hafner, J. Phys.: Condens. Matter 22 265006 (2010).


        This class was also carefully compared to the results of the script by D. Karhanek available at http://homepage.univie.ac.at/david.karhanek/downloads.html

        args:
            PoscarNamse (str): name of the POSCAR that was used for the phonon calculation
            BornFileName (str): name of the file with BORN charges (formatted with outcar-born)
            ForceConstants (boolean): If True, ForceConstants are read in. If False, forces are read in.
            ForceFileName (str): name of the file including force constants or forces
            supercell (list of lists): reads in supercell
            nac (boolean): If true, NAC is applied.
            symprec (float): contains symprec tag as used in Phonopy
            masses (list): Masses in this list are used instead of the ones prepared in Phonopy. Useful for isotopes.
            primitive (list of lists): contains rotational matrix to arrive at primitive cell
            degeneracy_tolerance (float): tolerance for degenerate modes
        """

        self.__unitcell = read_vasp(PoscarName)
        self.__supercell = supercell
        self.__phonon = Phonopy(self.__unitcell, supercell_matrix=self.__supercell, primitive_matrix=primitive,
                                factor=VaspToCm, symprec=symprec)
        self.__natoms = self.__phonon.get_primitive().get_number_of_atoms()
        self._degeneracy_tolerance = degeneracy_tolerance
        # If different masses are supplied
        if not ForceConstants:
            self.__force_sets = parse_FORCE_SETS(filename=ForceFileName)
            self.__phonon.set_displacement_dataset(self.__force_sets)
            self.__phonon.produce_force_constants()

        if ForceConstants:
            force_constants = parse_FORCE_CONSTANTS(filename=ForceFileName)
            self.__phonon.set_force_constants(force_constants)


        if masses:
            self.__phonon._build_supercell()
            self.__phonon._build_primitive_cell()
            # if ForceConstants:
            #     force_constants = parse_FORCE_CONSTANTS(filename=ForceFileName)
            #     self.__phonon.set_force_constants(force_constants)
            self.__phonon.set_masses(masses)

        self.__masses = self.__phonon.get_primitive().get_masses()
        # Forces or Force Constants

        # Read in BORN file
        BORN_file = parse_BORN(self.__phonon.get_primitive(), filename=BornFileName)
       
        self.__BORN_CHARGES = BORN_file['born']

        # Apply NAC Correction
        if nac:
            self.__phonon.set_nac_params(BORN_file)
        self._frequencies, self._eigvecs = self.__phonon.get_frequencies_with_eigenvectors([0, 0, 0])

        self.__NumberOfBands = len(self._frequencies)

        # Nicer format of the eigenvector file
        self.__FormatEigenvectors()

        # Get dipole approximation of the intensitiess
        self.__set_intensities()

    def __FormatEigenvectors(self):
        """
        Formats eigenvectors to a dictionary: the first argument is the number of bands, the second the number of atoms, the third the Cartesian coordinate
        """

        self._EigFormat = {}
        for alpha in range(self.__NumberOfBands):
            laufer = 0
            for beta in range(self.__natoms):
                for xyz in range(0, 3):
                    self._EigFormat[beta, alpha, xyz] = self._eigvecs[laufer][alpha]
                    laufer = laufer + 1

    def _Eigenvector(self, atom, band, xoryorz):
        """
        Gives a certain eigenvector corresponding to one specific atom, band and Cartesian coordinate

        args:
            atom (int) : number of the atoms (same order as in POSCAR)
            band (int) : number of the frequency (ordered by energy)
            xoryorz (int): Cartesian coordinate of the eigenvector


        """

        return np.real(self._EigFormat[atom, band, xoryorz])

    def __massEig(self, atom, band, xoryorz):
        """
        Gives a certain eigenvector divided by sqrt(mass of the atom) corresponding to one specific atom, band and Cartesian coordinate

        args:
            atom (int) : number of the atoms (same order as in POSCAR)
            band (int) : number of the frequency (ordered by energy)
            xoryorz (int): Cartesian coordinate of the eigenvector


        """

        return self._Eigenvector(atom, band, xoryorz) / np.sqrt(self.__masses[atom])

    def __set_intensities(self):
        """
        Calculates the oscillator strenghts according to "P. Giannozzi and S. Baroni, J. Chem. Phys. 100, 8537 (1994)."
        """

        Intensity = {}
        for freq in range(len(self._frequencies)):
            Intensity[freq] = 0
            for alpha in range(3):
                sum = 0
                for l in range(self.__natoms):
                    for beta in range(3):
                        sum = sum + self.__BORN_CHARGES[l, alpha, beta] * self.__massEig(l, freq, beta)
                Intensity[freq] = Intensity[freq] + np.power(np.absolute(sum), 2)

        # get degenerate modes
        freqlist_deg = get_degenerate_sets(self._frequencies, cutoff=self._degeneracy_tolerance)

        ReformatIntensity = []
        for i in Intensity:
            ReformatIntensity.append(Intensity[i])

        # if degenerate modes exist:
        if (len(freqlist_deg) < len(self._frequencies)):

            Intensity_deg = {}
            for sets in range(len(freqlist_deg)):
                Intensity_deg[sets] = 0
                for band in range(len(freqlist_deg[sets])):
                    Intensity_deg[sets] = Intensity_deg[sets] + ReformatIntensity[freqlist_deg[sets][band]]

            ReformatIntensity = []
            for i in range(len(Intensity_deg)):
                ReformatIntensity.append(Intensity_deg[i])

            Freq = []
            for band in range(len(freqlist_deg)):
                Freq.append(self._frequencies[freqlist_deg[band][0]])

            self.__frequencies_deg = np.array(Freq)

        else:
            self.__frequencies_deg = self._frequencies

        self.__Intensity = np.array(ReformatIntensity)

    def get_intensities(self):
        """
        returns calculated oscillator strengths as a numpy array
        """

        return self.__Intensity

    def get_frequencies(self):
        """
        returns frequencies as a numpy array
        """

        return self.__frequencies_deg

    def get_spectrum(self):
        """
        returns spectrum as a dict of numpy arrays
        """

        """
        Degeneracy should be treated for the Oscillator strengths

        """

        spectrum = {'Frequencies': self.get_frequencies(), 'Intensities': self.get_intensities()}
        return spectrum

    # only gaussian broadening so far
    def get_gaussiansmearedspectrum(self, sigma):
        """
        returns a spectrum with gaussian-smeared intensities

        args:
            sigma (float): smearing
        """

        #unsmearedspectrum = self.get_spectrum()
        frequencies = self.get_frequencies()
        Intensity = self.get_intensities()
        rangex = np.linspace(0, np.nanmax(frequencies) + 50, num=int(np.nanmax(frequencies) + 50) * 100)
        y = np.zeros(int(np.nanmax(frequencies) + 50) * 100)
        for i in range(len(frequencies)):
            y = y + self.__gaussiansmearing(rangex, frequencies[i], Intensity[i], sigma)
        smearedspectrum = {'Frequencies': rangex, 'Intensities': y}
        return smearedspectrum

    def __gaussiansmearing(self, rangex, frequency, Intensity, sigma):
        """
        applies gaussian smearing to a range of x values, a certain frequency, a given intensity

        args:
            rangex (ndarray): Which values are in your spectrum
            frequency (float): frequency corresponding to the intensity that will be smeared
            Intensity (float): Intensity that will be smeared
            sigma (float): value for the smearing

        """

        y = np.zeros(rangex.size)
        y = Intensity * np.exp(-np.power((rangex - frequency), 2) / (2 * np.power(sigma, 2))) * np.power(
            np.sqrt(2 * np.pi) * sigma, -1)
        return y

    def write_spectrum(self, filename, type='yaml'):
        """
        writes oscillator strenghts to file

        args:
            filename(str): Filename
            type(str): either txt or yaml
        """
        #TODO: csv
        spectrum = self.get_spectrum()
        if type == 'txt':
            self.__write_file(filename, spectrum)
        elif type == 'yaml':
            self.__write_file_yaml(filename, spectrum)

    def write_gaussiansmearedspectrum(self, filename, sigma, type='txt'):
        """
        writes smeared oscillator strenghts to file

        args:
            filename(str): Filename
            sigma(float): smearing of the spectrum
            type(str): either txt or yaml
        """
        #TODO csv
        spectrum = self.get_gaussiansmearedspectrum(sigma)
        if type == 'txt':
            self.__write_file(filename, spectrum)
        elif type == 'yaml':
            self.__write_file_yaml(filename, spectrum)

    def __write_file(self, filename, spectrum):
        """
        writes dict for any spectrum into txt file

        args:
            filename(str): Filename
            spectrum (dict): Includes nparray for 'Frequencies'
             and 'Intensities'

        """

        Freq = np.array(spectrum['Frequencies'].tolist())
        Intens = np.array(spectrum['Intensities'].tolist())
        file = open(filename, 'w')
        file.write('Frequency (cm-1) Oscillator Strengths \n')
        for i in range(len(Freq)):
            file.write('%s %s \n' % (Freq[i], Intens[i]))
        file.close()

    def __write_file_yaml(self, filename, spectrum):
        """
        writes dict for any spectrum into yaml file

        args:
                filename(str): Filename
                spectrum (dict): Includes nparray for 'Frequencies'
                and 'Intensities'

        """
        Freq = np.array(spectrum['Frequencies'].tolist())
        Intens = np.array(spectrum['Intensities'].tolist())
        file = open(filename, 'w')
        file.write('Frequency: \n')
        for i in range(len(Freq)):
            file.write('- %s \n' % (Freq[i]))
        file.write('Oscillator Strengths: \n')
        for i in range(len(Intens)):
            file.write('- %s \n' % (Intens[i]))
        file.close()

    def plot_spectrum(self, filename):
        """
        Plots frequencies in cm-1 and oscillator strengths
        args:
            filename(str): name of the file
        """
        spectrum = self.get_spectrum()
        plt.stem(spectrum['Frequencies'].tolist(), spectrum['Intensities'].tolist(), markerfmt=' ')
        plt.xlabel('Wave number (cm$^{-1}$)')
        plt.ylabel('Oscillator Strengths')
        plt.savefig(filename)
        plt.show()

    def plot_gaussiansmearedspectrum(self, filename, sigma):
        """
        Plots frequencies in cm-1 and smeared oscillator strengths
        args:
            filename(str): name of the file
            sigma(float): smearing
        """
        spectrum = self.get_gaussiansmearedspectrum(sigma)
        plt.plot(spectrum['Frequencies'].tolist(), spectrum['Intensities'].tolist())
        plt.xlabel('Wave number (cm$^{-1}$)')
        plt.ylabel('Oscillator Strengths')
        plt.savefig(filename)
        plt.show()