def run_single_point():
    logger = setup_logger(output_filename="dielectrics.log")
    structure = VaspReader(input_location='./POSCAR').read_POSCAR()
    vasp = Vasp(**single_point_pbe)
    vasp.set_crystal(structure)
    vasp.execute()
    if vasp.completed:
        logger.info("PBE self-consistent run completed properly.")
    else:
        raise Exception(
            "PBE self-consistent run failed to converge, will stop proceeding")
def get_total_energies(db, dir=None):
    all_zips = glob.glob(dir + "/*.zip")
    for zip in all_zips:
        kvp = {}
        data = {}
        kvp['uid'] = zip.replace(".zip", '').replace('/', '_')
        archive = zipfile.ZipFile(zip)

        atoms = None
        total_energy = None

        has_contcar = False
        for name in archive.namelist():
            if 'OSZICAR' in name:
                oszicar = archive.read(name)
                oszicar_reader = VaspReader(
                    file_content=str(oszicar).split('\\n'))
                total_energy = oszicar_reader.get_free_energies_from_oszicar(
                )[-1]
                kvp['total_energy'] = total_energy

            if 'CONTCAR' in name:
                with open('CONTCAR_temp', 'w') as f:
                    for l in str(archive.read(name)).split('\\n'):
                        f.write(l + '\n')
                has_contcar = True

        if not has_contcar:
            for name in archive.namelist():
                if 'POSCAR' in name:
                    with open('CONTCAR_temp', 'w') as f:
                        for l in str(archive.read(name)).split('\\n'):
                            f.write(l + '\n')

        crystal = ase.io.read('CONTCAR_temp', format='vasp')
        f.close()
        os.remove('CONTCAR_temp')

        if (crystal is not None) and (total_energy is not None):
            print(kvp['uid'], total_energy)
            populate_db(db, crystal, kvp, data)
Exemple #3
0
folders = glob.glob('*' + halo + '*')

_sigma_100K = []
_sigma_300K = []

_compositions = []

_sigma_ref_300K = []

for folder in ['../CsSnI_Pnma', '../CsPbI_Pnma']:
    print(folder)
    #if not os.path.isdir(folder): continue
    os.chdir(folder)

    # get the compositions
    crystal = VaspReader(input_location="./phonon/POSCAR").read_POSCAR()
    _d = crystal.all_atoms_count_dictionaries()
    scorer = AnharmonicScore(md_frames='./vasprun_md.xml',
                             ref_frame='./phonon/POSCAR',
                             atoms=None,
                             unit_cell_frame='./phonon/POSCAR')
    __sigmas, _ = scorer.structural_sigma(return_trajectory=True)
    _sigma_ref_300K.append(__sigmas[2000:])

    os.chdir(cwd)

for folder in folders:
    if not os.path.isdir(folder): continue
    os.chdir(folder)

    # get the compositions
    def __init__(self, ref_frame=None,
                 unit_cell_frame=None,
                 md_frames=None,
                 potim=1,
                 force_constants=None,
                 supercell=[1, 1, 1],
                 primitive_matrix=[[1, 0, 0], [0, 1, 0], [0, 0, 1]],
                 atoms=None,
                 include_third_order=False,
                 third_order_fc='./phono3py/fc3.hdf5',
                 include_fourth_order=False,
                 fourth_order_fc=None,
                 force_sets_filename='FORCE_SETS',
                 mode_resolved=False):
        self.mode_resolved = mode_resolved

        if isinstance(ref_frame, Crystal):
            self.ref_frame = ref_frame
        elif ('POSCAR' in ref_frame) or ('CONTCAR' in ref_frame):
            print(ref_frame)
            print("initialising reference frame from POSCAR ")
            self.ref_frame = VaspReader(input_location=ref_frame).read_POSCAR()
            self.ref_coords = np.array([[a.scaled_position.x, a.scaled_position.y, a.scaled_position.z] for a in
                                        self.ref_frame.asymmetric_unit[0].atoms])

        self.atom_masks = None
        if atoms is not None:
            self.atom_masks = [id for id, atom in enumerate(self.ref_frame.asymmetric_unit[0].atoms) if
                               atom.label in atoms]

        if isinstance(md_frames,list):
            self.md_frames = md_frames # require the vasprun.xml containing the MD data
        elif isinstance(md_frames,str):
            self.md_frames = [md_frames]

        self.get_dft_md_forces()
        self.get_all_md_atomic_displacements()

        if isinstance(force_constants, str):
            try:
                self.phonon = phonopy.load(supercell_matrix=supercell,  # WARNING - hard coded!
                                      primitive_matrix=primitive_matrix,
                                      unitcell_filename=unit_cell_frame,
                                      force_constants_filename=force_constants)
                print("Use supercell " + str(supercell))
                print("Use primitive matrix " + str(primitive_matrix) + " done")
            except:
                self.phonon = phonopy.load(supercell_matrix=supercell,  # WARNING - hard coded!
                                      primitive_matrix='auto',
                                      unitcell_filename=unit_cell_frame,
                                      force_constants_filename=force_constants)
            print("INPUT PHONOPY force constant shape ", np.shape(self.phonon.force_constants))

            #TODO - if the input supercell is not [1,1,1], then it will need to be expanded into the correct supercell shape here!

            new_shape = np.shape(self.phonon.force_constants)[0] * np.shape(self.phonon.force_constants)[2]
            self.force_constant = np.zeros((new_shape, new_shape))
            self.force_constant = self.phonon.force_constants.transpose(0, 2, 1, 3).reshape(new_shape, new_shape)

        elif (force_constants is None):
            """
            Loading directly from SPOSCAR (supercell structure) and FORCESET to avoid problem of the need for
            reconstructing the force constants for supercells from primitive cells
            """
            print("Here try to use FORCE_SETS")

            # if we want to get the eigenvector on all atoms in the supercell, we need to specify the primitive matrix
            # as the identity matrix, making the phonopy to treat the supercell as the primitive, rather than generate them
            # automatically
            if not self.mode_resolved:
                self.phonon  = phonopy.load(supercell_filename=ref_frame, log_level=1, force_sets_filename=force_sets_filename)
            else:
                self.phonon  = phonopy.load(supercell_filename=ref_frame, log_level=1, force_sets_filename=force_sets_filename,primitive_matrix=[[1, 0, 0], [0, 1, 0], [0, 0, 1]])
            self.phonon.produce_force_constants()

            print("INPUT PHONOPY force constant shape ", np.shape(self.phonon.force_constants))
            new_shape = np.shape(self.phonon.force_constants)[0] * np.shape(self.phonon.force_constants)[2]
            self.force_constant = np.zeros((new_shape, new_shape))
            self.force_constant = self.phonon.force_constants.transpose(0, 2, 1, 3).reshape(new_shape, new_shape)
        elif isinstance(force_constants, np.ndarray):
            new_shape = np.shape(force_constants)[0] * np.shape(force_constants)[2]
            self.force_constant = np.zeros((new_shape, new_shape))
            self.force_constant = force_constants.transpose(0, 2, 1, 3).reshape(new_shape, new_shape)

        print("force constant reshape ", np.shape(self.force_constant))
        print("Force constants ready")
        self.time_series = [t * potim for t in range(len(self.all_displacements))]

        self.force_constant_3 = None
        self.include_third_oder = include_third_order
        if self.include_third_oder:
            if isinstance(third_order_fc, str):
                if os.path.isfile(third_order_fc):
                    print("Found third order force constants")
                    import h5py
                    f = h5py.File(third_order_fc)  # './phono3py/fc3.hdf5'
                    raw_force_constant_3 = np.array(f['fc3'])
            elif isinstance(third_order_fc, np.ndarray):
                raw_force_constant_3 = third_order_fc

            s = np.shape(raw_force_constant_3)[0] * 3
            self.force_constant_3 = raw_force_constant_3.transpose([0, 3, 1, 4, 2, 5]).reshape(s, s, s)
            print("Reshaped 3rd order force constant is ", np.shape(self.force_constant_3))

        self.force_constant_4 = None
        self.include_fourth_order = include_fourth_order
        if self.include_fourth_order:
            if isinstance(fourth_order_fc, np.ndarray):
                raw_force_constant_4 = fourth_order_fc
            s = np.shape(raw_force_constant_4)[0] * 3
            self.force_constant_4 = raw_force_constant_4.transpose([0, 4, 1, 5, 2, 6, 3, 7]).reshape(s, s, s, s)

        if self.mode_resolved:
            self.prepare_phonon_eigs()
class AnharmonicScore(object):

    def __init__(self, ref_frame=None,
                 unit_cell_frame=None,
                 md_frames=None,
                 potim=1,
                 force_constants=None,
                 supercell=[1, 1, 1],
                 primitive_matrix=[[1, 0, 0], [0, 1, 0], [0, 0, 1]],
                 atoms=None,
                 include_third_order=False,
                 third_order_fc='./phono3py/fc3.hdf5',
                 include_fourth_order=False,
                 fourth_order_fc=None,
                 force_sets_filename='FORCE_SETS',
                 mode_resolved=False):
        self.mode_resolved = mode_resolved

        if isinstance(ref_frame, Crystal):
            self.ref_frame = ref_frame
        elif ('POSCAR' in ref_frame) or ('CONTCAR' in ref_frame):
            print(ref_frame)
            print("initialising reference frame from POSCAR ")
            self.ref_frame = VaspReader(input_location=ref_frame).read_POSCAR()
            self.ref_coords = np.array([[a.scaled_position.x, a.scaled_position.y, a.scaled_position.z] for a in
                                        self.ref_frame.asymmetric_unit[0].atoms])

        self.atom_masks = None
        if atoms is not None:
            self.atom_masks = [id for id, atom in enumerate(self.ref_frame.asymmetric_unit[0].atoms) if
                               atom.label in atoms]

        if isinstance(md_frames,list):
            self.md_frames = md_frames # require the vasprun.xml containing the MD data
        elif isinstance(md_frames,str):
            self.md_frames = [md_frames]

        self.get_dft_md_forces()
        self.get_all_md_atomic_displacements()

        if isinstance(force_constants, str):
            try:
                self.phonon = phonopy.load(supercell_matrix=supercell,  # WARNING - hard coded!
                                      primitive_matrix=primitive_matrix,
                                      unitcell_filename=unit_cell_frame,
                                      force_constants_filename=force_constants)
                print("Use supercell " + str(supercell))
                print("Use primitive matrix " + str(primitive_matrix) + " done")
            except:
                self.phonon = phonopy.load(supercell_matrix=supercell,  # WARNING - hard coded!
                                      primitive_matrix='auto',
                                      unitcell_filename=unit_cell_frame,
                                      force_constants_filename=force_constants)
            print("INPUT PHONOPY force constant shape ", np.shape(self.phonon.force_constants))

            #TODO - if the input supercell is not [1,1,1], then it will need to be expanded into the correct supercell shape here!

            new_shape = np.shape(self.phonon.force_constants)[0] * np.shape(self.phonon.force_constants)[2]
            self.force_constant = np.zeros((new_shape, new_shape))
            self.force_constant = self.phonon.force_constants.transpose(0, 2, 1, 3).reshape(new_shape, new_shape)

        elif (force_constants is None):
            """
            Loading directly from SPOSCAR (supercell structure) and FORCESET to avoid problem of the need for
            reconstructing the force constants for supercells from primitive cells
            """
            print("Here try to use FORCE_SETS")

            # if we want to get the eigenvector on all atoms in the supercell, we need to specify the primitive matrix
            # as the identity matrix, making the phonopy to treat the supercell as the primitive, rather than generate them
            # automatically
            if not self.mode_resolved:
                self.phonon  = phonopy.load(supercell_filename=ref_frame, log_level=1, force_sets_filename=force_sets_filename)
            else:
                self.phonon  = phonopy.load(supercell_filename=ref_frame, log_level=1, force_sets_filename=force_sets_filename,primitive_matrix=[[1, 0, 0], [0, 1, 0], [0, 0, 1]])
            self.phonon.produce_force_constants()

            print("INPUT PHONOPY force constant shape ", np.shape(self.phonon.force_constants))
            new_shape = np.shape(self.phonon.force_constants)[0] * np.shape(self.phonon.force_constants)[2]
            self.force_constant = np.zeros((new_shape, new_shape))
            self.force_constant = self.phonon.force_constants.transpose(0, 2, 1, 3).reshape(new_shape, new_shape)
        elif isinstance(force_constants, np.ndarray):
            new_shape = np.shape(force_constants)[0] * np.shape(force_constants)[2]
            self.force_constant = np.zeros((new_shape, new_shape))
            self.force_constant = force_constants.transpose(0, 2, 1, 3).reshape(new_shape, new_shape)

        print("force constant reshape ", np.shape(self.force_constant))
        print("Force constants ready")
        self.time_series = [t * potim for t in range(len(self.all_displacements))]

        self.force_constant_3 = None
        self.include_third_oder = include_third_order
        if self.include_third_oder:
            if isinstance(third_order_fc, str):
                if os.path.isfile(third_order_fc):
                    print("Found third order force constants")
                    import h5py
                    f = h5py.File(third_order_fc)  # './phono3py/fc3.hdf5'
                    raw_force_constant_3 = np.array(f['fc3'])
            elif isinstance(third_order_fc, np.ndarray):
                raw_force_constant_3 = third_order_fc

            s = np.shape(raw_force_constant_3)[0] * 3
            self.force_constant_3 = raw_force_constant_3.transpose([0, 3, 1, 4, 2, 5]).reshape(s, s, s)
            print("Reshaped 3rd order force constant is ", np.shape(self.force_constant_3))

        self.force_constant_4 = None
        self.include_fourth_order = include_fourth_order
        if self.include_fourth_order:
            if isinstance(fourth_order_fc, np.ndarray):
                raw_force_constant_4 = fourth_order_fc
            s = np.shape(raw_force_constant_4)[0] * 3
            self.force_constant_4 = raw_force_constant_4.transpose([0, 4, 1, 5, 2, 6, 3, 7]).reshape(s, s, s, s)

        if self.mode_resolved:
            self.prepare_phonon_eigs()

    def prepare_phonon_eigs(self,nqpoints=10):
        print("Need to calculate the mode resolved anharmonic scores, first get the phonon eigenvectors")
        from core.models.element import atomic_mass_dict


        from pymatgen.symmetry.bandstructure import HighSymmKpath
        from core.internal.builders.crystal import map_to_pymatgen_Structure
        pmg_path = HighSymmKpath(map_to_pymatgen_Structure(self.ref_frame), symprec=1e-3)
        self._kpath = pmg_path._kpath
        self.prim = pmg_path.prim
        self.conv = pmg_path.conventional
        __qpoints = [[self._kpath['kpoints'][self._kpath['path'][j][i]] for i in range(len(self._kpath['path'][j]))] for
                     j in range(len(self._kpath['path']))]
        from phonopy.phonon.band_structure import get_band_qpoints_and_path_connections
        qpoints, connections = get_band_qpoints_and_path_connections(__qpoints,
                                                                     npoints=nqpoints)  # now this qpoints will contain points within two high-symmetry points along the Q-path
        self.phonon.run_band_structure(qpoints, with_eigenvectors=True)
        _eigvecs = self.phonon.band_structure.__dict__['_eigenvectors']
        _eigvals = self.phonon.band_structure.__dict__['_eigenvalues']
        self.eigvecs = []
        self.eigvals = []

        import math
        self.atomic_masses = []
        for a in self.ref_frame.all_atoms(sort=False, unique=False):
            for _ in range(3):
                self.atomic_masses.append(1.0 / math.sqrt(atomic_mass_dict[a.label.upper()]))

        from phonopy.units import VaspToTHz

        if _eigvecs is not None:
            for i, eigvecs_on_path in enumerate(_eigvecs):
                for j, eigvecs_at_q in enumerate(eigvecs_on_path):
                    for k, vec in enumerate(eigvecs_at_q.T):
                        #vec = np.array(vec).reshape(self.ref_frame.total_num_atoms(), 3)
                        self.eigvecs.append(self.atomic_masses*vec)
                        eigv=_eigvals[i][j][k]
                        self.eigvals.append(np.sqrt(abs(eigv))*np.sign(eigv)*VaspToTHz)
        print('eigenvector shape ', np.shape(vec))
        print('Total number of eigenstates ' + str(len(self.eigvals)))

    def mode_resolved_sigma(self):
        if self.mode_resolved is not True: raise Exception("eigenmodes not prepared for running this function")
        print("do dot")
        dft_dot = np.dot(self.dft_forces, np.array(self.eigvecs).T).std(axis=0)
        anh_dot = np.dot(self.anharmonic_forces, np.array(self.eigvecs).T).std(axis=0)
        print(np.shape(dft_dot), np.shape(anh_dot))
        print("finished dot")
        #self.mode_sigmas = [np.dot(self.anharmonic_forces,eigvec).std()/np.dot(self.dft_forces,eigvec).std() for eigvec in self.eigvecs]
        self.mode_sigmas = np.divide(anh_dot,dft_dot)
        return self.eigvals,self.mode_sigmas

    def mode_resolved_sigma_band(self):
        from pymatgen.symmetry.bandstructure import HighSymmKpath
        from core.internal.builders.crystal import map_to_pymatgen_Structure
        pmg_path = HighSymmKpath(map_to_pymatgen_Structure(self.ref_frame), symprec=1e-3)
        self._kpath = pmg_path._kpath


        distances = self.phonon.band_structure.__dict__['_distances']
        print(np.shape(distances[0]))
        eigvals = self.phonon.band_structure.__dict__['_frequencies']
        print(np.shape(eigvals[0]))
        eigvecs = self.phonon.band_structure.__dict__['_eigenvectors']

        sp_pts = self.phonon.band_structure.__dict__['_special_points']
        print(sp_pts)
        sp_pt_labels = self._kpath['path']
        print(sp_pt_labels)

        f, (a0, a1) = plt.subplots(1, 2, gridspec_kw={'width_ratios': [3, 1]})
        from phonopy.units import VaspToTHz
        freqs=[]
        sigmas=[]
        for j in range(len(distances)):

            for i in range(len(eigvals[j].T)):
                #now need something to calculate the sigma here along each point
                print(str(j) + '/' + str(len(distances)-1), str(i)+'/'+str(len(eigvals[j].T)-1))
                _sigmas=[]
                for k in range(len(distances[j])):
                    e=eigvecs[j][k][i]
                    _s=np.dot(self.anharmonic_forces,e).std()/np.dot(self.dft_forces,e).std()
                    _sigmas.append(np.exp(1.5*_s))
                    #sigmas.append(_s)
                    #eigv=eigvals[j][k][i]
                    #eigv=np.sqrt(abs(eigv)) * np.sign(eigv) * VaspToTHz
                    #freqs.append(eigv)
                #print(max(sigmas),min(sigmas))
                #sigmas=[np.dot(self.anharmonic_forces,e).std()/np.dot(self.dft_forces,e).std() for e in eigvecs[j][:][i]]
                #print(sigmas)
                a0.plot(distances[j],eigvals[j].T[i],'-',c='#FFBB00',alpha=0.75,linewidth=0.85)
                a0.scatter(distances[j],eigvals[j].T[i],marker='o',s=_sigmas,fc='#3F681C',alpha=0.45)

        a0.set_xlim(min(distances[0]),max(distances[-1]))

        unique_labels = []
        for i in range(len(sp_pt_labels)):
            for j in range(len(sp_pt_labels[i])):
                if sp_pt_labels[i][j]=='\\Gamma': _l = "$\\Gamma$"
                elif "_1" in sp_pt_labels[i][j]: _l = sp_pt_labels[i][j].replace("_1","")
                else: _l = sp_pt_labels[i][j]
                if (j==len(sp_pt_labels[i])-1) and (i!=len(sp_pt_labels)-1):
                    unique_labels.append(_l+'$\\vert$')
                elif (i>0) and (j==0):
                    unique_labels[-1] = unique_labels[-1]+_l
                else:
                    unique_labels.append(_l)

        a0.set_xticks(sp_pts)
        a0.set_xticklabels(unique_labels)
        for i in sp_pts:
            a0.axvline(x=i,ls=':',c='k')

        a0.set_ylabel("Frequency (THz)")

        freqs,sigmas=self.mode_resolved_sigma()
        a1.scatter(sigmas,freqs,marker='o',fc='#3F681C',alpha=0.6, s=1)
        a1.set_ylim(a0.get_ylim())
        a1.set_xlabel('$\\sigma$ (300 K)')
        a1.axvline(x=1, ls=':', c='k')
        a1.set_yticks([])
        plt.tight_layout()
        plt.savefig('phonon_band_sigma.pdf')

    def plot_fc(self):
        plt.matshow(self.force_constant)
        plt.colorbar()
        plt.savefig('fc.pdf')

    @property
    def lattice_vectors(self):
        """
        :return: A numpy array representation of the lattice vector
        """
        _lv = self.ref_frame.lattice.lattice_vectors
        return np.array(
            [[_lv[0][0], _lv[0][1], _lv[0][2]], [_lv[1][0], _lv[1][1], _lv[1][2]], [_lv[2][0], _lv[2][1], _lv[2][2]]])

    def get_dft_md_forces(self):
        all_forces = []
        for frame in self.md_frames:
            for event, elem in etree.iterparse(frame):
                if elem.tag == 'varray':
                    if elem.attrib['name'] == 'forces':
                        this_forces = []
                        if not self.mode_resolved:
                            for v in elem:
                                this_force = [float(_v) for _v in v.text.split()]
                                this_forces.append(this_force)
                        else:
                            for v in elem:
                                for this_force in [float(_v) for _v in v.text.split()]:
                                    this_forces.append(this_force)
                        all_forces.append(np.array(this_forces))

        print('MD force vector shape ', np.shape(this_forces))
        self.dft_forces = np.array(all_forces)
        print('All MD force vector shape ', np.shape(self.dft_forces))
        print("Atomic forces along the MD trajectory loaded\n")

    def get_all_md_atomic_displacements(self):

        all_positions = []
        for frame in self.md_frames:
            if len(self.md_frames)!=1:
                this = []
            for event, elem in etree.iterparse(frame):
                if elem.tag == 'varray':
                    if elem.attrib['name'] == 'positions':
                        this_positions = []
                        for v in elem:
                            this_position = [float(_v) for _v in v.text.split()]
                            this_positions.append(this_position)
                        if len(self.md_frames) != 1:
                            this.append(this_positions)
                        else:
                            all_positions.append(np.array(this_positions))
            if len(self.md_frames) != 1:
                all_positions.append(this[-1])

        # only need those with forces
        all_positions = all_positions[-len(self.dft_forces):]
        all_positions = np.array(all_positions)
        print("Atomic positions along the MD trajectory loaded, converting to displacement, taking into account PBC")

        __all_displacements = np.array(
            [all_positions[i, :] - self.ref_coords for i in range(all_positions.shape[0])])

        # periodic boundary conditions
        #__all_displacements = (__all_displacements + 0.5 + 1e-5) % 1 - 0.5 - 1e-5
        __all_displacements = __all_displacements - np.round(__all_displacements)  # this is how it's done in Pymatgen
        # Convert to Cartesian
        self.all_displacements = np.zeros(np.shape(__all_displacements))

        for i in range(__all_displacements.shape[0]):
            np.dot(__all_displacements[i, :, :], self.lattice_vectors, out=self.all_displacements[i, :, :])

    @property
    def harmonic_forces(self):
        if (not hasattr(self, '_harmonic_forces')) or (
                hasattr(self, '_harmonic_forces') and self._harmonic_force is None):
            self._harmonic_force = np.zeros(np.shape(self.dft_forces))
            for i in range(np.shape(self.all_displacements)[0]):  # this loop over MD frames
                if not self.mode_resolved:
                    self._harmonic_force[i, :, :] = -1.0 * (
                        np.dot(self.force_constant, self.all_displacements[i, :, :].flatten())).reshape(
                        self.all_displacements[0, :, :].shape)
                else:
                    self._harmonic_force[i, :] = -1.0 * (np.dot(self.force_constant, self.all_displacements[i, :, :].flatten()))

        return self._harmonic_force

    @property
    def third_order_forces(self):
        print("Do we have third_order constant " + str(self.force_constant_3.__class__))
        if self.mode_resolved: raise NotImplementedError
        if self.force_constant_3 is not None:
            if (not hasattr(self, '_third_order_forces')) or (
                    hasattr(self, '_third_order_forces') and self._third_order_forces is None):
                self._third_order_forces = np.zeros(np.shape(self.dft_forces))
                _a = self.force_constant_3
                for i in range(np.shape(self.all_displacements)[0]):  # this loop over MD frames
                    _b = self.all_displacements[i, :, :].flatten()
                    _A = np.einsum('ijk,k->ij', _a, _b)
                    self._third_order_forces[i, :, :] = -1 * np.einsum('ij,j->i', _A, _b).reshape(
                        self.all_displacements[0, :, :].shape)
                return self._third_order_forces / 2.0  # see https://hiphive.materialsmodeling.org/background/force_constants.html

    @property
    def fourth_order_forces(self):
        if self.mode_resolved: raise NotImplementedError
        print("Do we have fourth_order constant " + str(self.force_constant_4.__class__))
        if self.force_constant_4 is not None:
            if (not hasattr(self, '_fourth_order_forces')) or (
                    hasattr(self, '_fourth_order_forces') and self._fourth_order_forces is None):
                self._fourth_order_forces = np.zeros(np.shape(self.dft_forces))
                _a = self.force_constant_4
                for i in range(np.shape(self.all_displacements)[0]):  # this loop over MD frames
                    print("fourth order forces, frame "+str(i))
                    _b = self.all_displacements[i, :, :].flatten()
                    _A = np.einsum('ijkl,l->ijk', _a, _b)
                    _B = np.einsum('ijk,k->ij', _A, _b)
                    self._fourth_order_forces[i, :, :] = -1 * np.einsum('ij,j->i', _B, _b).reshape(
                        self.all_displacements[0, :, :].shape)
                print("fourth order forces done")
                return self._fourth_order_forces / 6.0

    @property
    def anharmonic_forces(self):
        if (not hasattr(self, '_anharmonic_forces')) or (
                hasattr(self, '_anharmonic_forces') and self._anharmonic_forces is None):
            self._anharmonic_forces = self.dft_forces - self.harmonic_forces

            if self.include_third_oder:
                self._anharmonic_forces = self._anharmonic_forces - self.third_order_forces
            if self.include_fourth_order:
                self._anharmonic_forces = self._anharmonic_forces - self.fourth_order_forces
        return self._anharmonic_forces

    def trajectory_normalized_dft_forces(self, flat=False):
        all_forces_std = self.dft_forces.flatten().std()
        out = np.zeros(np.shape(self.dft_forces))
        np.divide(self.dft_forces, all_forces_std, out=out)
        if flat:
            return out.flatten()
        return out

    def trajectory_normalized_anharmonic_forces(self, flat=False):
        # anharmonic_forces_std = self.anharmonic_forces.flatten().std()
        all_forces_std = self.dft_forces.flatten().std()
        out = np.zeros(np.shape(self.anharmonic_forces))
        np.divide(self.anharmonic_forces, all_forces_std, out=out)
        if flat:
            return out.flatten()
        return out

    def atom_normalized_dft_forces(self, atom, flat=False):
        _mask = [id for id, a in enumerate(self.ref_frame.asymmetric_unit[0].atoms) if a.label == atom]
        dft_forces = self.dft_forces[:, _mask, :]
        dft_forces_std = dft_forces.flatten().std()
        out = np.zeros(np.shape(dft_forces))
        np.divide(dft_forces, dft_forces_std, out=out)
        if flat:
            return out.flatten(), dft_forces_std
        return out, dft_forces_std

    def atom_normalized_anharmonic_forces(self, atom, flat=False):
        _mask = [id for id, a in enumerate(self.ref_frame.asymmetric_unit[0].atoms) if a.label == atom]
        print(atom, _mask)
        anharmonic_forces = self.anharmonic_forces[:, _mask, :]

        dft_forces = self.dft_forces[:, _mask, :]
        dft_forces_std = dft_forces.flatten().std()

        out = np.zeros(np.shape(anharmonic_forces))
        np.divide(anharmonic_forces, dft_forces_std, out=out)
        if flat:
            return out.flatten()
        return out

    def plot_atom_joint_distributions(self):
        atoms = [a.label for a in self.ref_frame.asymmetric_unit[0].atoms]
        atoms = list(set(atoms))
        atoms = sorted(atoms)
        print(atoms)
        fig, axs = plt.subplots(1, len(atoms), figsize=(4 * len(atoms), 4))
        for id, atom in enumerate(atoms):
            divider = make_axes_locatable(axs[id])
            cax1 = divider.append_axes("right", size="5%", pad=0.05)

            X, std = self.atom_normalized_dft_forces(atom, flat=True)
            Y = self.atom_normalized_anharmonic_forces(atom, flat=True)

            X_pred, Y_pred = np.mgrid[-2:2:50j, -2:2:50j]
            positions = np.vstack([X_pred.ravel(), Y_pred.ravel()])
            values = np.vstack([X, Y])
            print("Perform Gaussian Kernel Density Estimate")
            kernel = gaussian_kde(values)
            Z = np.reshape(kernel(positions).T, X_pred.shape)

            print("Making the plot")
            a = axs[id].imshow(np.rot90(Z), cmap=plt.get_cmap('Blues'), extent=[-1, 1, -1, 1])

            axs[id].plot([-1, -0.5, 0, 0.5, 1], [std for i in range(5)], 'k--')
            axs[id].plot([-1, -0.5, 0, 0.5, 1], [-1.0 * std for i in range(5)], 'k--')

            axs[id].set_xlim([-1, 1])
            axs[id].set_ylim([-1, 1])
            axs[id].set_xlabel("$F_{i}/\\sigma(F_{i})$", fontsize=16)
            axs[id].set_ylabel("$F^{A}_{i}/\\sigma(F_{i})$", fontsize=16)
            axs[id].set_title(str(atom), fontsize=20)
            axs[id].tick_params(axis='both', which='major', labelsize=10)
            plt.colorbar(a, cax=cax1)  # .set_label(label='probability density',size=15)
        plt.tight_layout()
        plt.savefig('atom_joint_PDF.pdf')

    def plot_total_joint_distribution(self, x='DFT', y='anh'):
        if x == 'DFT':
            X = self.dft_forces.flatten() / self.dft_forces.flatten().std()
        else:
            raise NotImplementedError()
        if y == 'anh':
            Y = self.anharmonic_forces.flatten() / self.dft_forces.flatten().std()
        elif y == 'har':
            Y = self.harmonic_forces.flatten() / self.dft_forces.flatten().std()
        else:
            raise NotImplementedError()

        X_pred, Y_pred = np.mgrid[-1:1:50j, -1:1:50j]
        positions = np.vstack([X_pred.ravel(), Y_pred.ravel()])
        values = np.vstack([X, Y])
        print("Gaussian KDE")
        kernel = gaussian_kde(values)
        print("Kernel done")
        Z = np.reshape(kernel(positions).T, X_pred.shape)
        plt.imshow(np.rot90(Z), cmap=plt.get_cmap('Blues'), extent=[-1, 1, -1, 1])
        plt.xlim([-1, 1])
        plt.ylim([-1, 1])

        plt.xlabel("$F/\\sigma(F)$", fontsize=16)
        if y == 'anh':
            plt.ylabel("$F^{A}/\\sigma(F)$", fontsize=16)
        elif y == 'har':
            plt.ylabel("$F^{(2)}/\\sigma(F)$", fontsize=16)

        if y == 'anh':
            plt.plot([-1, -0.5, 0, 0.5, 1], [self.anharmonic_forces.flatten().std() for i in range(5)], 'k--')
            plt.plot([-1, -0.5, 0, 0.5, 1], [-1.0 * self.anharmonic_forces.flatten().std() for i in range(5)], 'k--')

        plt.tick_params(axis='both', which='major', labelsize=10)
        plt.colorbar().set_label(label='probability density', size=15)

        plt.tight_layout()
        plt.savefig('joint_PDF.pdf')

    def structure_averaged_sigma_trajectory(self):
        atoms = [a.label for a in self.ref_frame.asymmetric_unit[0].atoms]
        atoms = list(set(atoms))
        self.sigma_frames = []
        for atom in atoms:
            anh_f = self.atom_normalized_anharmonic_forces(atom)
            dft_f = self.atom_normalized_dft_forces(atom)

            for i in range(np.shape(self.all_displacements)[0]):
                _sigma_frame = anh_f[i, :, :].flatten().std() / dft_f[i, :, :].flatten().std()
                self.sigma_frames.append(_sigma_frame)

    def structural_sigma(self, return_trajectory=False):
        if self.atom_masks is None:
            rmse = self.anharmonic_forces
            std = self.dft_forces
        else:
            rmse = self.anharmonic_forces[:, self.atom_masks, :]
            std = self.dft_forces[:, self.atom_masks, :]

        if not return_trajectory:
            print(return_trajectory, 'calculate sigma')
            sigma = rmse.std(dtype=np.float64) / std.std(dtype=np.float64)
            print("Sigma for entire structure over the MD trajectory is ", str(sigma))
            return sigma, self.time_series
        else:
            sigma = rmse.std(axis=(1,2), dtype=np.float64) / std.std(axis=(1,2), dtype=np.float64)
            print('sigma is ', sigma)
            print('averaged sigma is ',sigma.mean())
            return sigma, self.time_series
    'LWAVE': False,
    'LCHARG': False,
    'LREAL': 'Auto',
    'NELM': 150,
    'NSW': 0,
    'NCORE': 48,
    'use_gw': True,
    'Gamma_centered': True,
    'MP_points': [1, 1, 1],
    'executable': 'vasp_gam'
}

pwd = os.getcwd()

# get the trajectory
all_frames = VaspReader(args.traj).read_XDATCAR()

if not args.batch:
    output = 'gap_dynamics.dat'
else:
    output = 'gap_dynamics_' + str(args.part) + '.dat'

# figure out if this is a continuing calculation
if not os.path.exists(pwd + '/' + output):
    o = open(pwd + '/' + output, 'w+')
    o.write('Frame\t VBM \t CBM \t E_f \t E_g\n')
    o.close()
    last = 0
else:
    o = open(pwd + '/' + output, 'r')
    for l in o.readlines():
def composition_eq_point_curve():
    cwd_1 = os.getcwd()
    labels = {
        0: 'Cs(Pb$_{x}$Sn$_{1-x})$Cl$_3$',
        1: 'Cs(Pb$_{x}$Sn$_{1-x})$Br$_3$',
        2: 'Cs(Pb$_{x}$Sn$_{1-x})$I$_3$'
    }
    colors = {
        0: '#000000',  # hex code for black
        1: '#dd0000',  # hex code for electric red
        2: '#ffce00'  # hex code for tangerine yellow
    }
    for counter, X in enumerate(['Cl', 'Br', 'I']):
        data_dict = {}
        for dir in [
                r for r in glob.glob(os.getcwd() + "/*" + str(X) + "*")
                if '.pdf' not in r
        ]:
            os.chdir(dir)
            print(dir)
            cwd_2 = os.getcwd()
            data = []
            directories = glob.glob(os.getcwd() + "/disp_111_*_0_05")
            if 'CsSnBr3_cubic' in dir:
                directories = glob.glob(os.getcwd() + "/disp_Sn_111_*_0_05")
            for sub_dir in directories:
                os.chdir(sub_dir)
                print(sub_dir)
                energy = VaspReader(input_location='./OSZICAR'
                                    ).get_free_energies_from_oszicar()[-1]
                crystal = VaspReader(input_location='./POSCAR').read_POSCAR()
                energy = energy / crystal.total_num_atoms()
                info = pickle.load(open('info.p', 'rb'))
                info['energy'] = energy
                data.append(info)
                os.chdir(cwd_2)
            os.chdir(cwd_1)
            displacements = list(sorted([d['displacement'] for d in data]))

            energy = []
            for _dis in displacements:
                for d in data:
                    if d['displacement'] == _dis:
                        energy.append(d['energy'])
            energy = [e - energy[0] for e in energy]

            x = np.array(displacements)
            y = np.array(energy)

            from scipy.optimize import curve_fit
            popt, pcov = curve_fit(pes, x, y)
            x = [0 + (0.6 / 100) * i for i in range(100)]
            y = pes(np.array(x), *popt)
            a = popt[0]
            b = popt[1]
            if 2 * a / (4 * b) > 0:
                min_x = 0
            else:
                min_x = math.sqrt(-2 * a / (4 * b))
            concentration = data[0]['concentration']
            data_dict[1.0 - concentration] = min_x
        os.chdir(cwd_1)
        x = list(sorted(data_dict.keys()))
        plt.plot(x, [data_dict[_x] for _x in x],
                 'o-',
                 c=colors[counter],
                 label=labels[counter],
                 ms=12,
                 lw=3)
    plt.legend()
    plt.xlabel('$x$ in Cs(Pb$_{x}$Sn$_{1-x}$)X$_{3}$')
    plt.ylabel('Minima on PES ($x_{\min}$, \AA)')
    plt.tight_layout()
    plt.savefig('xeq_comp_summary.pdf')
def make_distorted_structure(poscar,
                             direction='111',
                             max_displacement=0.6,
                             number_of_points=15,
                             lattice_deformation=0,
                             atom_to_displace=['Sn', 'Pb']):
    for counter, delta_d in enumerate([
            0 + max_displacement / number_of_points * k
            for k in range(number_of_points)
    ]):
        crystal = VaspReader(input_location=poscar).read_POSCAR()
        # expand or shrink the lattice parameters
        crystal = deform_crystal_by_lattice_expansion_coefficients(
            crystal, def_fraction=[lattice_deformation for _ in range(3)])

        d = [int(_d) for _d in list(direction)]
        displacement_vector = cVector3D(d[0], d[1], d[2]).normalise()

        # just displace the first atom that is the element that we want to displace
        _displace_vec = displacement_vector.vec_scale(delta_d)

        # _next = True

        for mol in crystal.asymmetric_unit:
            for atom in mol.atoms:
                if (atom.label in atom_to_displace):  # and (_next is True):
                    print(atom.label)
                    atom.position = atom.position + _displace_vec
                    # _next = False

        try:
            n = crystal.all_atoms_count_dictionaries()['Sn']
        except KeyError:
            n = 0

        folder_name = 'disp' + '_' + str(direction) + '_' + str(
            n) + '_str_' + str(counter) + '_a_def_' + str(
                lattice_deformation).replace('.', '_')
        cwd = os.getcwd()
        try:
            os.makedirs(cwd + '/' + folder_name)
        except:
            pass

        os.chdir(cwd + '/' + folder_name)

        _dict = crystal.all_atoms_count_dictionaries()

        if 'Sn' not in _dict.keys():
            _dict['Sn'] = 0
        if 'Pb' not in _dict.keys():
            _dict['Pb'] = 0

        system_dict = {
            'atom_to_displace': atom_to_displace,
            'concentration': _dict['Sn'] / (_dict['Sn'] + _dict['Pb']),
            'displacement': delta_d,
            'id': counter,
            'direction': direction,
            'deformation': lattice_deformation
        }
        pickle.dump(system_dict, open('info.p', 'wb'))

        VaspWriter().write_structure(crystal, 'POSCAR')
        os.chdir(cwd)
def get_this_pes(directory=os.getcwd()):
    cmap = matplotlib.cm.get_cmap('YlOrRd')

    cwd = os.getcwd()
    data = []
    for dir in glob.glob(directory + "/disp_*"):
        os.chdir(dir)
        try:
            energy = VaspReader(input_location='./OSZICAR'
                                ).get_free_energies_from_oszicar()[-1]
            crystal = VaspReader(input_location='./POSCAR').read_POSCAR()
            energy = energy / crystal.total_num_atoms()
            info = pickle.load(open('info.p', 'rb'))
            info['energy'] = energy
            data.append(info)
        except:
            pass
        os.chdir(cwd)

    deformations = set([d['deformation'] for d in data])
    deformations = list(sorted(deformations))
    max_def = max(deformations) + 0.0001
    max_displacement = 0

    min_x = []
    min_y = []

    for id, _def in enumerate(deformations):
        displacement = [
            d['displacement'] for d in data if d['deformation'] == _def
        ]
        displacement = list(sorted(displacement))

        energy = []
        for dis in displacement:
            for d in data:
                if (d['deformation'] == _def) and (d['displacement'] == dis):
                    energy.append(d['energy'])
        energy = [e - energy[0] for e in energy]

        x = np.array(displacement)
        y = np.array(energy)
        from scipy.optimize import curve_fit
        popt, pcov = curve_fit(pes, x, y)

        x = [0 + (0.6 / 100) * i for i in range(100)]
        y = pes(np.array(x), *popt)

        a = popt[0]
        b = popt[1]

        if 2 * a / (4 * b) > 0:
            min_x.append(0)
            min_y.append(0)
        else:
            min_x.append(math.sqrt(-2 * a / (4 * b)))
            min_y.append(pes(math.sqrt(-2 * a / (4 * b)), *popt))

        if id % 2 == 0:
            plt.plot(x,
                     y,
                     '-',
                     c=cmap((0.05 + _def) / (2 * max_def)),
                     label=str(_def * 100) + '\%',
                     lw=2)
        else:
            plt.plot(x, y, '-', c=cmap((0.05 + _def) / (2 * max_def)), lw=2)

        max_displacement = max(x)

    plt.ticklabel_format(style='sci', axis='y', scilimits=(0, 0))
    plt.plot([0, max_displacement], [0, 0], 'k--')
    plt.plot(min_x, min_y, 'o-', c='k', lw=3.5)
    plt.legend()
    plt.ylim([-0.01, 0.0150])
    plt.xlim([0, max_displacement])
    plt.xlabel('B-Cation Displacement $\Delta d$ (\AA)')
    plt.ylabel('$E-E_{\Delta d=0}$ (eV/atom)')
    plt.tight_layout()
    plt.savefig('pes.pdf')
def pressure_eq_point_curve():
    cwd = os.getcwd()
    labels = {
        0: 'CsPbBr$_3$',
        1: 'Cs(Pb$_{0.5}$Sn$_{0.5})$Br$_3$',
        2: 'CsSnBr$_3$'
    }
    colors = {
        0: '#000000',  # hex code for black
        1: '#dd0000',  # hex code for electric red
        2: '#ffce00'  # hex code for tangerine yellow
    }
    for counter, sys in enumerate([
            'CsPbBr3_cubic', 'mixed_CsPbSnBr3_SC_1_1_1_CsPbSnBr3_5_str_17',
            'CsSnBr3_cubic'
    ]):
        os.chdir(sys)
        this_dir = os.getcwd()
        data = []
        for dir in glob.glob("./disp_*"):

            os.chdir(dir)

            try:
                energy = VaspReader(input_location='./OSZICAR'
                                    ).get_free_energies_from_oszicar()[-1]
                crystal = VaspReader(input_location='./POSCAR').read_POSCAR()
                energy = energy / crystal.total_num_atoms()
                info = pickle.load(open('info.p', 'rb'))
                info['energy'] = energy
                data.append(info)
            except:
                pass
            os.chdir(this_dir)
        deformations = set([d['deformation'] for d in data])
        deformations = list(sorted(deformations))

        min_x = []

        for id, _def in enumerate(deformations):
            displacement = [
                d['displacement'] for d in data if d['deformation'] == _def
            ]
            displacement = list(sorted(displacement))

            energy = []
            for dis in displacement:
                for d in data:
                    if (d['deformation'] == _def) and (d['displacement']
                                                       == dis):
                        energy.append(d['energy'])
            energy = [e - energy[0] for e in energy]

            x = np.array(displacement)
            y = np.array(energy)
            from scipy.optimize import curve_fit
            popt, pcov = curve_fit(pes, x, y)

            a = popt[0]
            b = popt[1]

            if 2 * a / (4 * b) > 0:
                min_x.append(0)
            else:
                min_x.append(math.sqrt(-2 * a / (4 * b)))

        plt.plot(deformations,
                 min_x,
                 'o--',
                 label=labels[counter],
                 ms=10,
                 c=colors[counter])

        _deformations = []
        _min_x = []
        for t in range(len(min_x)):
            if (min_x[t] > 0):
                _deformations.append(deformations[t])
                _min_x.append(min_x[t])
        x = np.array(_deformations)
        y = np.array(_min_x)

        from scipy import interpolate
        f = interpolate.interp1d(x, y)
        x = np.array([
            min(_deformations) + k *
            (max(_deformations) - min(_deformations)) / 100 for k in range(100)
        ])
        y = f(x)

        from scipy.optimize import curve_fit

        popt, pcov = curve_fit(square, y, x, maxfev=2000)
        a = popt[0]
        b = popt[1]
        _y = np.array([(max(y) + 0.1 * max(y)) * k / 100 for k in range(100)])
        _x = square(_y, *popt)
        print(_x[0])
        plt.plot(_x, _y, '-', lw=2, c=colors[counter])

        os.chdir(cwd)
    plt.xlim([0.015, 0.052])
    plt.xlabel('Lattice Deformation $\delta $')
    plt.ylabel('Minima on PES ($x_{\min}$, \AA)')
    plt.legend()
    plt.tight_layout()
    plt.savefig("xeq_pressure_summary_Br.pdf")
def get_pes_across_composition(X='Cl'):
    cmap = matplotlib.cm.get_cmap('coolwarm')
    cwd_1 = os.getcwd()
    data_dict = {}
    min_x = []
    min_y = []
    for dir in [
            r for r in glob.glob(os.getcwd() + "/*" + str(X) + "*")
            if '.pdf' not in r
    ]:
        os.chdir(dir)
        cwd_2 = os.getcwd()
        data = []
        for sub_dir in glob.glob(os.getcwd() + "/disp_111_*_0_05"):
            os.chdir(sub_dir)
            print(sub_dir)
            energy = VaspReader(input_location='./OSZICAR'
                                ).get_free_energies_from_oszicar()[-1]
            crystal = VaspReader(input_location='./POSCAR').read_POSCAR()
            energy = energy / crystal.total_num_atoms()
            info = pickle.load(open('info.p', 'rb'))
            info['energy'] = energy
            data.append(info)
            os.chdir(cwd_2)
        os.chdir(cwd_1)

        displacements = list(sorted([d['displacement'] for d in data]))
        energy = []
        for _dis in displacements:
            for d in data:
                if d['displacement'] == _dis:
                    energy.append(d['energy'])
        energy = [e - energy[0] for e in energy]

        x = np.array(displacements)
        y = np.array(energy)
        from scipy.optimize import curve_fit
        popt, pcov = curve_fit(pes, x, y)
        x = [0 + (0.6 / 100) * i for i in range(100)]
        y = pes(np.array(x), *popt)

        a = popt[0]
        b = popt[1]

        if 2 * a / (4 * b) > 0:
            min_x.append(0)
            min_y.append(0)
        else:
            min_x.append(math.sqrt(-2 * a / (4 * b)))
            min_y.append(pes(math.sqrt(-2 * a / (4 * b)), *popt))

        data_dict[1.0 - data[0]['concentration']] = {'x': x, 'y': y}

    for k in list(sorted(data_dict.keys())):
        plt.plot(data_dict[k]['x'],
                 data_dict[k]['y'],
                 '-',
                 c=cmap(k),
                 label=str(k))
    # plt.legend(loc=2)
    plt.ticklabel_format(style='sci', axis='y', scilimits=(0, 0))
    plt.plot(min_x, min_y, 'o', c='#00743F')

    max_displacemnt = 0.6
    plt.xlim([0, max_displacemnt])
    plt.plot([0, max_displacemnt], [0, 0], 'k--')

    plt.xlabel('B-Cation Displacement $\Delta d$ (\AA)')
    plt.ylabel('$E-E_{\Delta d=0}$ (eV/atom)')
    plt.tight_layout()
    plt.savefig(X + "_B_111_landscape.pdf")