def test_soc_chgcar(self): filepath = os.path.join(test_dir, "CHGCAR.NiO_SOC.gz") chg = Chgcar.from_file(filepath) self.assertEqual(set(chg.data.keys()), {'total', 'diff_x', 'diff_y', 'diff_z', 'diff'}) self.assertTrue(chg.is_soc) self.assertEqual(chg.data['diff'].shape, chg.data['diff_y'].shape) # check our construction of chg.data['diff'] makes sense # this has been checked visually too and seems reasonable self.assertEqual( abs(chg.data['diff'][0][0][0]), np.linalg.norm([ chg.data['diff_x'][0][0][0], chg.data['diff_y'][0][0][0], chg.data['diff_z'][0][0][0] ])) # and that the net magnetization is about zero # note: we get ~ 0.08 here, seems a little high compared to # vasp output, but might be due to chgcar limitations? self.assertAlmostEqual(chg.net_magnetization, 0.0, places=0) chg.write_file("CHGCAR_pmg_soc") chg_from_file = Chgcar.from_file("CHGCAR_pmg_soc") self.assertTrue(chg_from_file.is_soc) os.remove("CHGCAR_pmg_soc")
def to_chgcar(self, filename=None): """ Convert a :class:`Density` object into a ``Chgar`` object. If ``filename`` is not None, density is written to this file in Chgar format Return: :class:`Chgcar` instance. .. note:: From: http://cms.mpi.univie.ac.at/vasp/vasp/CHGCAR_file.html: This file contains the total charge density multiplied by the volume For spinpolarized calculations, two sets of data can be found in the CHGCAR file. The first set contains the total charge density (spin up plus spin down), the second one the magnetization density (spin up minus spin down). For non collinear calculations the CHGCAR file contains the total charge density and the magnetisation density in the x, y and z direction in this order. """ myrhor = self.datar * self.structure.volume if self.nspinor == 1: if self.nsppol == 1: data_dict = {"total": myrhor[0]} if self.nsppol == 2: data_dict = {"total": myrhor[0] + myrhor[1], "diff": myrhor[0] - myrhor[1]} elif self.nspinor == 2: raise NotImplementedError("pymatgen Chgcar does not implement nspinor == 2") chgcar = Chgcar(Poscar(self.structure), data_dict) if filename is not None: chgcar.write_file(filename) return chgcar
def test_writestate_ncl(self): print("TEST WRITE NCL") sys.stdout.flush() from pymatgen.io.vasp.outputs import Chgcar wf = NCLWavefunction.from_directory("noncollinear") fileprefix = "" b, k, s = 10, 1, 0 state1, state2 = wf.write_state_realspace(b, k, s, fileprefix="") state1, state2 = wf.write_state_realspace(b, k, s, fileprefix="", dim=np.array([30, 30, 30])) assert state1.shape[0] == 30 assert state1.shape[1] == 30 assert state1.shape[2] == 30 assert state1.dtype == np.complex128 filename_base = "%sB%dK%dS%d" % (fileprefix, b, k, s) filename1 = "%s_UP_REAL.vasp" % filename_base filename2 = "%s_UP_IMAG.vasp" % filename_base filename3 = "%s_DOWN_REAL.vasp" % filename_base filename4 = "%s_DOWN_IMAG.vasp" % filename_base chg1 = Chgcar.from_file(filename1) chg2 = Chgcar.from_file(filename2) chg3 = Chgcar.from_file(filename3) chg4 = Chgcar.from_file(filename4) upart = chg1.data["total"]**2 + chg2.data["total"]**2 dpart = chg3.data["total"]**2 + chg4.data["total"]**2 vol = chg1.poscar.structure.volume assert_almost_equal(np.sum((upart + dpart) * vol / 30**3), 1, 2) os.remove(filename1) os.remove(filename2) os.remove(filename3) os.remove(filename4)
def from_path(cls, path, suffix="", zpsp=None): """ Convenience method to run critic2 analysis on a folder containing typical VASP output files. This method will: 1. Look for files CHGCAR, AECAR0, AECAR2, POTCAR or their gzipped counterparts. 2. If AECCAR* files are present, constructs a temporary reference file as AECCAR0 + AECCAR2. 3. Runs critic2 analysis twice: once for charge, and a second time for the charge difference (magnetization density). :param path: path to folder to search in :param suffix: specific suffix to look for (e.g. '.relax1' for 'CHGCAR.relax1.gz') :param zpsp: manually specify ZPSP if POTCAR not present :return: """ chgcar_path = get_filepath("CHGCAR", "Could not find CHGCAR!", path, suffix) chgcar = Chgcar.from_file(chgcar_path) chgcar_ref = None if not zpsp: potcar_path = get_filepath( "POTCAR", "Could not find POTCAR, will not be able to calculate charge transfer.", path, suffix, ) if potcar_path: potcar = Potcar.from_file(potcar_path) zpsp = {p.element: p.zval for p in potcar} if not zpsp: # try and get reference "all-electron-like" charge density if zpsp not present aeccar0_path = get_filepath( "AECCAR0", "Could not find AECCAR0, interpret Bader results with caution.", path, suffix, ) aeccar0 = Chgcar.from_file(aeccar0_path) if aeccar0_path else None aeccar2_path = get_filepath( "AECCAR2", "Could not find AECCAR2, interpret Bader results with caution.", path, suffix, ) aeccar2 = Chgcar.from_file(aeccar2_path) if aeccar2_path else None chgcar_ref = aeccar0.linear_add(aeccar2) if (aeccar0 and aeccar2) else None return cls(chgcar.structure, chgcar, chgcar_ref, zpsp=zpsp)
def from_path(cls, path, suffix=""): """ Convenient constructor that takes in the path name of VASP run to perform Bader analysis. Args: path (str): Name of directory where VASP output files are stored. suffix (str): specific suffix to look for (e.g. '.relax1' for 'CHGCAR.relax1.gz'). """ def _get_filepath(filename): name_pattern = (filename + suffix + "*" if filename != "POTCAR" else filename + "*") paths = glob.glob(os.path.join(path, name_pattern)) fpath = None if len(paths) >= 1: # using reverse=True because, if multiple files are present, # they likely have suffixes 'static', 'relax', 'relax2', etc. # and this would give 'static' over 'relax2' over 'relax' # however, better to use 'suffix' kwarg to avoid this! paths.sort(reverse=True) warning_msg = ("Multiple files detected, using %s" % os.path.basename(paths[0]) if len(paths) > 1 else None) fpath = paths[0] else: warning_msg = "Could not find %s" % filename if filename in ["AECCAR0", "AECCAR2"]: warning_msg += ", cannot calculate charge transfer." elif filename == "POTCAR": warning_msg += ", interpret Bader results with caution." if warning_msg: warnings.warn(warning_msg) return fpath chgcar_filename = _get_filepath("CHGCAR") if chgcar_filename is None: raise IOError("Could not find CHGCAR!") potcar_filename = _get_filepath("POTCAR") aeccar0 = _get_filepath("AECCAR0") aeccar2 = _get_filepath("AECCAR2") if aeccar0 and aeccar2: # `chgsum.pl AECCAR0 AECCAR2` equivalent to obtain chgref_file chgref = Chgcar.from_file(aeccar0) + Chgcar.from_file(aeccar2) chgref_filename = "CHGREF" chgref.write_file(chgref_filename) else: chgref_filename = None return cls( chgcar_filename=chgcar_filename, potcar_filename=potcar_filename, chgref_filename=chgref_filename, )
def from_path(cls, path, suffix=''): """ Convenience method to run critic2 analysis on a folder containing typical VASP output files. This method will: 1. Look for files CHGCAR, AECAR0, AECAR2, POTCAR or their gzipped counterparts. 2. If AECCAR* files are present, constructs a temporary reference file as AECCAR0 + AECCAR2. 3. Runs critic2 analysis twice: once for charge, and a second time for the charge difference (magnetization density). :param path: path to folder to search in :param suffix: specific suffix to look for (e.g. '.relax1' for 'CHGCAR.relax1.gz') :return: """ def _get_filepath(filename, warning, path=path, suffix=suffix): paths = glob.glob(os.path.join(path, filename + suffix + '*')) if not paths: warnings.warn(warning) return None if len(paths) > 1: # using reverse=True because, if multiple files are present, # they likely have suffixes 'static', 'relax', 'relax2', etc. # and this would give 'static' over 'relax2' over 'relax' # however, better to use 'suffix' kwarg to avoid this! paths.sort(reverse=True) warnings.warn('Multiple files detected, using {}'.format( os.path.basename(path))) path = paths[0] return path chgcar_path = _get_filepath('CHGCAR', 'Could not find CHGCAR!') chgcar = Chgcar.from_file(chgcar_path) aeccar0_path = _get_filepath( 'AECCAR0', 'Could not find AECCAR0, interpret Bader results with caution.') aeccar0 = Chgcar.from_file(aeccar0_path) if aeccar0_path else None aeccar2_path = _get_filepath( 'AECCAR2', 'Could not find AECCAR2, interpret Bader results with caution.') aeccar2 = Chgcar.from_file(aeccar2_path) if aeccar2_path else None chgcar_ref = aeccar0.linear_add(aeccar2) if (aeccar0 and aeccar2) else None return cls(chgcar.structure, chgcar, chgcar_ref)
def from_path(cls, path, suffix=""): """ Convenient constructor that takes in the path name of VASP run to perform Bader analysis. Args: path (str): Name of directory where VASP output files are stored. suffix (str): specific suffix to look for (e.g. '.relax1' for 'CHGCAR.relax1.gz'). """ def _get_filepath(filename): name_pattern = filename + suffix + '*' if filename != 'POTCAR' \ else filename + '*' paths = glob.glob(os.path.join(path, name_pattern)) fpath = None if len(paths) >= 1: # using reverse=True because, if multiple files are present, # they likely have suffixes 'static', 'relax', 'relax2', etc. # and this would give 'static' over 'relax2' over 'relax' # however, better to use 'suffix' kwarg to avoid this! paths.sort(reverse=True) warning_msg = "Multiple files detected, using %s" \ % os.path.basename(paths[0]) if len(paths) > 1 \ else None fpath = paths[0] else: warning_msg = "Could not find %s" % filename if filename in ['AECCAR0', 'AECCAR2']: warning_msg += ", cannot calculate charge transfer." elif filename == "POTCAR": warning_msg += ", interpret Bader results with caution." if warning_msg: warnings.warn(warning_msg) return fpath chgcar_filename = _get_filepath("CHGCAR") if chgcar_filename is None: raise IOError("Could not find CHGCAR!") potcar_filename = _get_filepath("POTCAR") aeccar0 = _get_filepath("AECCAR0") aeccar2 = _get_filepath("AECCAR2") if (aeccar0 and aeccar2): # `chgsum.pl AECCAR0 AECCAR2` equivalent to obtain chgref_file chgref = Chgcar.from_file(aeccar0) + Chgcar.from_file(aeccar2) chgref_filename = "CHGREF" chgref.write_file(chgref_filename) else: chgref_filename = None return cls(chgcar_filename, potcar_filename=potcar_filename, chgref_filename=chgref_filename)
def bader_analysis_from_path(path, suffix=""): """ Convenience method to run Bader analysis on a folder containing typical VASP output files. This method will: 1. Look for files CHGCAR, AECCAR0, AECCAR2, POTCAR or their gzipped counterparts. 2. If AECCAR* files are present, constructs a temporary reference file as AECCAR0 + AECCAR2 3. Runs Bader analysis twice: once for charge, and a second time for the charge difference (magnetization density). :param path: path to folder to search in :param suffix: specific suffix to look for (e.g. '.relax1' for 'CHGCAR.relax1.gz' :return: summary dict """ def _get_filepath(filename, warning, path=path, suffix=suffix): paths = glob.glob(os.path.join(path, filename + suffix + "*")) if not paths: warnings.warn(warning) return None if len(paths) > 1: # using reverse=True because, if multiple files are present, # they likely have suffixes 'static', 'relax', 'relax2', etc. # and this would give 'static' over 'relax2' over 'relax' # however, better to use 'suffix' kwarg to avoid this! paths.sort(reverse=True) warnings.warn( f"Multiple files detected, using {os.path.basename(path)}") path = paths[0] return path chgcar_path = _get_filepath("CHGCAR", "Could not find CHGCAR!") chgcar = Chgcar.from_file(chgcar_path) aeccar0_path = _get_filepath( "AECCAR0", "Could not find AECCAR0, interpret Bader results with caution.") aeccar0 = Chgcar.from_file(aeccar0_path) if aeccar0_path else None aeccar2_path = _get_filepath( "AECCAR2", "Could not find AECCAR2, interpret Bader results with caution.") aeccar2 = Chgcar.from_file(aeccar2_path) if aeccar2_path else None potcar_path = _get_filepath( "POTCAR", "Could not find POTCAR, cannot calculate charge transfer.") potcar = Potcar.from_file(potcar_path) if potcar_path else None return bader_analysis_from_objects(chgcar, potcar, aeccar0, aeccar2)
def setUp(self): # This is a CHGCAR_sum file with reduced grid size chgcar_path = os.path.join(test_dir, "CHGCAR.FePO4") chg_FePO4 = Chgcar.from_file(chgcar_path) self.chgcar_path = chgcar_path self.chg_FePO4 = chg_FePO4 self.cia_FePO4 = ChargeInsertionAnalyzer(chg_FePO4)
def test_init(self): filepath = os.path.join(test_dir, "CHGCAR.nospin") chg = Chgcar.from_file(filepath) self.assertAlmostEqual(chg.get_integrated_diff(0, 2)[0, 1], 0) filepath = os.path.join(test_dir, "CHGCAR.spin") chg = Chgcar.from_file(filepath) self.assertAlmostEqual(chg.get_integrated_diff(0, 1)[0, 1], -0.0043896932237534022) # test sum chg += chg self.assertAlmostEqual(chg.get_integrated_diff(0, 1)[0, 1], -0.0043896932237534022 * 2) filepath = os.path.join(test_dir, "CHGCAR.Fe3O4") chg = Chgcar.from_file(filepath) ans = [1.93313368, 3.91201473, 4.11858277, 4.1240093, 4.10634989, 3.38864822] myans = chg.get_integrated_diff(0, 3, 6) self.assertTrue(np.allclose(myans[:, 1], ans))
def bader_analysis_from_path(path, suffix=''): """ Convenience method to run Bader analysis on a folder containing typical VASP output files. This method will: 1. Look for files CHGCAR, AECAR0, AECAR2, POTCAR or their gzipped counterparts. 2. If AECCAR* files are present, constructs a temporary reference file as AECCAR0 + AECCAR2 3. Runs Bader analysis twice: once for charge, and a second time for the charge difference (magnetization density). :param path: path to folder to search in :param suffix: specific suffix to look for (e.g. '.relax1' for 'CHGCAR.relax1.gz' :return: summary dict """ def _get_filepath(filename, warning, path=path, suffix=suffix): paths = glob.glob(os.path.join(path, filename + suffix + '*')) if not paths: warnings.warn(warning) return None if len(paths) > 1: # using reverse=True because, if multiple files are present, # they likely have suffixes 'static', 'relax', 'relax2', etc. # and this would give 'static' over 'relax2' over 'relax' # however, better to use 'suffix' kwarg to avoid this! paths.sort(reverse=True) warnings.warn('Multiple files detected, using {}'.format(os.path.basename(path))) path = paths[0] return path chgcar_path = _get_filepath('CHGCAR', 'Could not find CHGCAR!') chgcar = Chgcar.from_file(chgcar_path) aeccar0_path = _get_filepath('AECCAR0', 'Could not find AECCAR0, interpret Bader results with caution.') aeccar0 = Chgcar.from_file(aeccar0_path) if aeccar0_path else None aeccar2_path = _get_filepath('AECCAR2', 'Could not find AECCAR2, interpret Bader results with caution.') aeccar2 = Chgcar.from_file(aeccar2_path) if aeccar2_path else None potcar_path = _get_filepath('POTCAR', 'Could not find POTCAR, cannot calculate charge transfer.') potcar = Potcar.from_file(potcar_path) if potcar_path else None return bader_analysis_from_objects(chgcar, potcar, aeccar0, aeccar2)
def setUp(self): # This is a CHGCAR_sum file with reduced grid size chgcar_path = os.path.join(test_dir, "CHGCAR.FePO4") chg_FePO4 = Chgcar.from_file(chgcar_path) self.chgcar_path = chgcar_path self.chg_FePO4 = chg_FePO4 self.ca_FePO4 = ChargeDensityAnalyzer(chg_FePO4) self.s_LiFePO4 = Structure.from_file(os.path.join(test_dir, "LiFePO4.cif"))
def test_density(self): print("TEST DENSITY") sys.stdout.flush() wf = Wavefunction.from_directory('nosym') #wf = wf.desymmetrized_copy() wf.write_density_realspace(dim=np.array([40,40,40]), scale = wf.structure.lattice.volume) tstchg = Chgcar.from_file("AECCAR2").data['total']# / wf.structure.volume chg = Chgcar.from_file("PYAECCAR").data['total'] reldiff = np.sqrt(np.mean(((chg-tstchg)/tstchg)**2)) newchg = chg-tstchg Chgcar(Poscar(wf.structure), {'total': newchg}).write_file('DIFFCHGCAR.vasp') print(np.sum(chg)/40**3, np.sum(tstchg)/40**3) assert_almost_equal(reldiff, 0, decimal=2) wf = Wavefunction.from_directory('nosym') res = wf.write_density_realspace(filename="BAND4DENS", bands=4) #os.remove('PYAECCAR') print("DENS shape", res.shape) assert_almost_equal(np.sum(res)*wf.structure.lattice.volume/np.cumprod(res.shape)[-1], 1, 4)
def test_density_ncl(self): print("TEST DENSITY NCL") sys.stdout.flush() print("LOAD WAVEFUNCTION") sys.stdout.flush() wf = NCLWavefunction.from_directory('noncollinear') print("FINISHED LOAD WAVEFUNCTION") sys.stdout.flush() #wf = wf.desymmetrized_copy() wf.write_density_realspace(scale = wf.structure.lattice.volume) wf.write_density_realspace(dim=np.array([40,40,40]), scale = wf.structure.lattice.volume) tstchg = Chgcar.from_file("AECCAR2").data['total']# / wf.structure.volume chg = Chgcar.from_file("PYAECCAR").data['total'] reldiff = np.sqrt(np.mean(((chg-tstchg)/tstchg)**2)) newchg = chg-tstchg Chgcar(Poscar(wf.structure), {'total': newchg}).write_file('DIFFCHGCAR.vasp') print(np.sum(chg)/40**3, np.sum(tstchg)/40**3) assert_almost_equal(reldiff, 0, decimal=3)
def test_init(self): filepath = os.path.join(test_dir, 'CHGCAR.nospin') chg = Chgcar.from_file(filepath) self.assertAlmostEqual(chg.get_integrated_diff(0, 2)[0, 1], 0) filepath = os.path.join(test_dir, 'CHGCAR.spin') chg = Chgcar.from_file(filepath) self.assertAlmostEqual(chg.get_integrated_diff(0, 1)[0, 1], -0.0043896932237534022) #test sum chg += chg self.assertAlmostEqual(chg.get_integrated_diff(0, 1)[0, 1], -0.0043896932237534022 * 2) filepath = os.path.join(test_dir, 'CHGCAR.Fe3O4') chg = Chgcar.from_file(filepath) ans = [1.56472768, 3.25985108, 3.49205728, 3.66275028, 3.8045896, 5.10813352] myans = chg.get_integrated_diff(0, 3, 6) self.assertTrue(np.allclose(myans[:, 1], ans))
def test_init(self): filepath = os.path.join(test_dir, 'CHGCAR.nospin') chg = Chgcar.from_file(filepath) self.assertAlmostEqual(chg.get_integrated_diff(0, 2)[0, 1], 0) filepath = os.path.join(test_dir, 'CHGCAR.spin') chg = Chgcar.from_file(filepath) self.assertAlmostEqual(chg.get_integrated_diff(0, 1)[0, 1], -0.0043896932237534022) #test sum chg += chg self.assertAlmostEqual(chg.get_integrated_diff(0, 1)[0, 1], -0.0043896932237534022 * 2) filepath = os.path.join(test_dir, 'CHGCAR.Fe3O4') chg = Chgcar.from_file(filepath) ans = [1.93313368, 3.91201473, 4.11858277, 4.1240093, 4.10634989, 3.38864822] myans = chg.get_integrated_diff(0, 3, 6) self.assertTrue(np.allclose(myans[:, 1], ans))
def to_chgcar(self, filename=None): from pymatgen.io.vasp.inputs import Poscar from pymatgen.io.vasp.outputs import Chgcar # From: http://cms.mpi.univie.ac.at/vasp/vasp/CHGCAR_file.html # This file contains the total charge density multiplied by the volume # For spinpolarized calculations, two sets of data can be found in the CHGCAR file. # The first set contains the total charge density (spin up plus spin down), # the second one the magnetization density (spin up minus spin down). # For non collinear calculations the CHGCAR file contains the total charge density # and the magnetisation density in the x, y and z direction in this order. myrhor = self.datar * self.structure.volume data_dict = {"total": myrhor[0]} if self.nsppol == 2: {"diff": myrhor[0] - myrhor[1]} if self.nspinor == 2: raise NotImplementedError("pymatgen Chgcar does not implement nspinor == 2") chgcar = Chgcar(Poscar(self.structure), data_dict) if filename is not None: chgcar.write_file(filename) return chgcar
def test_write(self): filepath = os.path.join(test_dir, 'CHGCAR.spin') chg = Chgcar.from_file(filepath) chg.write_file("CHGCAR_pmg") with open("CHGCAR_pmg") as f: for i, line in enumerate(f): if i == 22130: self.assertEqual("augmentation occupancies 1 15\n", line) if i == 44255: self.assertEqual("augmentation occupancies 1 15\n", line) os.remove("CHGCAR_pmg")
def bader_analysis_from_path(path, suffix=''): """ Convenience method to run Bader analysis on a folder containing typical VASP output files. This method will: 1. Look for files CHGCAR, AECAR0, AECAR2, POTCAR or their gzipped counterparts. 2. If AECCAR* files are present, constructs a temporary reference file as AECCAR0 + AECCAR2 3. Runs Bader analysis twice: once for charge, and a second time for the charge difference (magnetization density). :param path: path to folder to search in :param suffix: specific suffix to look for (e.g. '.relax1' for 'CHGCAR.relax1.gz' :return: summary dict """ chgcar_path = get_filepath('CHGCAR', 'Could not find CHGCAR!', path, suffix) chgcar = Chgcar.from_file(chgcar_path) aeccar0_path = get_filepath( 'AECCAR0', 'Could not find AECCAR0, interpret Bader results with caution.', path, suffix) aeccar0 = Chgcar.from_file(aeccar0_path) if aeccar0_path else None aeccar2_path = get_filepath( 'AECCAR2', 'Could not find AECCAR2, interpret Bader results with caution.', path, suffix) aeccar2 = Chgcar.from_file(aeccar2_path) if aeccar2_path else None potcar_path = get_filepath( 'POTCAR', 'Could not find POTCAR, cannot calculate charge transfer.', path, suffix) potcar = Potcar.from_file(potcar_path) if potcar_path else None return bader_analysis_from_objects(chgcar, potcar, aeccar0, aeccar2)
def _load_parchg(self): if self.preloaded_data: with open(os.path.join(self.folder, 'parchg.npy'), 'rb') as p: data = np.load(p) else: parchg = Chgcar.from_file(os.path.join(self.folder, 'PARCHG')) data = parchg.data['total'] np.save(os.path.join(self.folder, 'parchg.npy'), data) a_vals = np.linspace(0, 1, data.shape[0]) b_vals = np.linspace(0, 1, data.shape[1]) c_vals = np.linspace(0, 1, data.shape[2]) return data, a_vals, b_vals, c_vals
def test_soc_chgcar(self): filepath = os.path.join(test_dir, "CHGCAR.NiO_SOC.gz") chg = Chgcar.from_file(filepath) self.assertEqual(set(chg.data.keys()), {'total', 'diff_x', 'diff_y', 'diff_z', 'diff'}) self.assertTrue(chg.is_soc) self.assertEqual(chg.data['diff'].shape, chg.data['diff_y'].shape) # check our construction of chg.data['diff'] makes sense # this has been checked visually too and seems reasonable self.assertEqual(abs(chg.data['diff'][0][0][0]), np.linalg.norm([chg.data['diff_x'][0][0][0], chg.data['diff_y'][0][0][0], chg.data['diff_z'][0][0][0]])) # and that the net magnetization is about zero # note: we get ~ 0.08 here, seems a little high compared to # vasp output, but might be due to chgcar limitations? self.assertAlmostEqual(chg.net_magnetization, 0.0, places=0) chg.write_file("CHGCAR_pmg_soc") chg_from_file = Chgcar.from_file("CHGCAR_pmg_soc") self.assertTrue(chg_from_file.is_soc) os.remove("CHGCAR_pmg_soc")
def test_hdf5(self): chgcar = Chgcar.from_file(os.path.join(test_dir, "CHGCAR.NiO_SOC.gz")) chgcar.to_hdf5("chgcar_test.hdf5") import h5py with h5py.File("chgcar_test.hdf5", "r") as f: self.assertArrayAlmostEqual(np.array(f["vdata"]["total"]), chgcar.data["total"]) self.assertArrayAlmostEqual(np.array(f["vdata"]["diff"]), chgcar.data["diff"]) self.assertArrayAlmostEqual(np.array(f["lattice"]), chgcar.structure.lattice.matrix) self.assertArrayAlmostEqual(np.array(f["fcoords"]), chgcar.structure.frac_coords) for z in f["Z"]: self.assertIn(z, [Element.Ni.Z, Element.O.Z]) for sp in f["species"]: self.assertIn(sp, ["Ni", "O"]) chgcar2 = Chgcar.from_hdf5("chgcar_test.hdf5") self.assertArrayAlmostEqual(chgcar2.data["total"], chgcar.data["total"]) os.remove("chgcar_test.hdf5")
def __init__(self, chgcar_filename, potcar_filename=None): """ Initializes the Bader caller. Args: chgcar_filename: The filename of the CHGCAR. potcar_filename: Optional: the filename of the corresponding POTCAR file. Used for calculating the charge transfer. If None, the get_charge_transfer method will raise a ValueError. """ self.chgcar = Chgcar.from_file(chgcar_filename) self.potcar = Potcar.from_file(potcar_filename) \ if potcar_filename is not None else None self.natoms = self.chgcar.poscar.natoms chgcarpath = os.path.abspath(chgcar_filename) with ScratchDir(".") as temp_dir: shutil.copy(chgcarpath, os.path.join(temp_dir, "CHGCAR")) rs = subprocess.Popen(["bader", "CHGCAR"], stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True) rs.communicate() if rs.returncode != 0: raise RuntimeError("bader exited with return code %d. " "Pls check your bader installation." % rs.returncode) data = [] with open("ACF.dat") as f: raw = f.readlines() headers = [s.lower() for s in raw.pop(0).split()] raw.pop(0) while True: l = raw.pop(0).strip() if l.startswith("-"): break vals = map(float, l.split()[1:]) data.append(dict(zip(headers[1:], vals))) for l in raw: toks = l.strip().split(":") if toks[0] == "VACUUM CHARGE": self.vacuum_charge = float(toks[1]) elif toks[0] == "VACUUM VOLUME": self.vacuum_volume = float(toks[1]) elif toks[0] == "NUMBER OF ELECTRONS": self.nelectrons = float(toks[1]) self.data = data
def from_chgcar_poscar(cls, chgcar, poscar): """ Build a :class`Density` object from Vasp data. Args: chgcar: Either string with the name of a CHGCAR file or :class:`Chgcar` pymatgen object. poscar: Either string with the name of a POSCAR file or :class:`Poscar` pymatgen object. .. warning: The present version does not support non-collinear calculations. The Chgcar object provided by pymatgen does not provided enough information to understand if the calculation is collinear or no. """ if is_string(chgcar): chgcar = Chgcar.from_file(chgcar) if is_string(poscar): poscar = Poscar.from_file(poscar, check_for_POTCAR=False, read_velocities=False) nx, ny, nz = chgcar.dim nspinor = 1 nsppol = 2 if chgcar.is_spin_polarized else 1 nspden = 2 if nsppol == 2 else 1 # Convert pymatgen chgcar data --> abipy representation. abipy_datar = np.empty((nspden, nx, ny, nz)) if nspinor == 1: if nsppol == 1: abipy_datar = chgcar.data["total"] elif nsppol == 2: total, diff = chgcar.data["total"], chgcar.data["diff"] abipy_datar[0] = 0.5 * (total + diff) abipy_datar[1] = 0.5 * (total - diff) else: raise ValueError("Wrong nspden %s" % nspden) else: raise NotImplementedError("nspinor == 2 requires more info in Chgcar") # density in Chgcar is multiplied by volume! abipy_datar /= poscar.structure.volume return cls(nspinor=nspinor, nsppol=nsppol, nspden=nspden, datar=abipy_datar, structure=poscar.structure, iorder="c")
def callback_update_structure(contents, filename, last_modified): if not contents: raise PreventUpdate # assume we only want the first input for now content_type, content_string = contents.split(",") decoded_contents = b64decode(content_string) error = None data = None # necessary to write to file so pymatgen's filetype detection can work with NamedTemporaryFile(suffix=filename) as tmp: tmp.write(decoded_contents) tmp.flush() try: struct_or_mol = Structure.from_file(tmp.name) data = self.to_data(struct_or_mol) except: try: struct_or_mol = Molecule.from_file(tmp.name) data = self.to_data(struct_or_mol) except: try: struct_or_mol = Chgcar.from_file(tmp.name) except: # TODO: fix these horrible try/excepts, loadfn may be dangerous try: struct_or_mol = loadfn(tmp.name) data = self.to_data(struct_or_mol) except: error = ( "Could not parse uploaded file. " "If this seems like a bug, please report it. " "Crystal Toolkit understands all crystal " "structure file types and molecule file types " "supported by pymatgen.") return { "data": data, "error": error, "time_requested": self.get_time() }
def __init__(self, chgcar_filename, potcar_filename=None): """ Initializes the Bader caller. Args: chgcar_filename: The filename of the CHGCAR. potcar_filename: Optional: the filename of the corresponding POTCAR file. Used for calculating the charge transfer. If None, the get_charge_transfer method will raise a ValueError. """ self.chgcar = Chgcar.from_file(chgcar_filename) self.potcar = Potcar.from_file(potcar_filename) \ if potcar_filename is not None else None self.natoms = self.chgcar.poscar.natoms chgcarpath = os.path.abspath(chgcar_filename) with ScratchDir(".") as temp_dir: shutil.copy(chgcarpath, os.path.join(temp_dir, "CHGCAR")) rs = subprocess.Popen(["bader", "CHGCAR"], stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True) rs.communicate() data = [] with open("ACF.dat") as f: raw = f.readlines() headers = [s.lower() for s in raw.pop(0).split()] raw.pop(0) while True: l = raw.pop(0).strip() if l.startswith("-"): break vals = map(float, l.split()[1:]) data.append(dict(zip(headers[1:], vals))) for l in raw: toks = l.strip().split(":") if toks[0] == "VACUUM CHARGE": self.vacuum_charge = float(toks[1]) elif toks[0] == "VACUUM VOLUME": self.vacuum_volume = float(toks[1]) elif toks[0] == "NUMBER OF ELECTRONS": self.nelectrons = float(toks[1]) self.data = data
def __init__(self, filename): """ Reads PARCHG file and make class attributes """ self.filename = filename self.parchg = Chgcar.from_file(filename) # charge data from CHGCAR self.chgdata = self.parchg.data['total'] # structure associated w/ volumetric data self.structure = self.parchg.structure # fraction of cell occupied by structure self.zmax = self.structure.cart_coords[:, 2].max() \ / self.structure.lattice.c # max charge density # rmax should be maximum rho value within the stm_cell # i.e., [X, Y, Z[nzmin:nzmax]] rather than the whole box self.rmax = None
def get_chg_matrix(folder): chg = Chgcar.from_file(os.path.join(folder, 'CHGCAR')) chg_matrix = chg.data['total'] / chg.ngridpts potcar = Potcar.from_file(os.path.join(folder, 'POTCAR')) poscar = Poscar.from_file(os.path.join(folder, 'POSCAR')) natoms = poscar.natoms cumm_natoms = np.array([sum(natoms[0:i + 1]) for i in range(len(natoms))]) lengths = [] for i in range(3): lengths.append(len(chg.get_axis_grid(i))) lengths = np.array(lengths) for atom in range(len(chg.structure.sites)): # iterating over ion centers potcarsingle = potcar[np.argmax(cumm_natoms >= (atom+1))] # get Potcarsingle for each atom charge = potcarsingle.nelectrons site = chg.structure.sites[atom] # atoms are 1 indexed # number = site.species_and_occu.elements[0].number i = np.round(np.array([site.a, site.b, site.c]) * lengths) # getting indecies to place atomic charges in cell chg_matrix[int(i[0] % lengths[0])][int(i[1] % lengths[1])][int(i[2] % lengths[2])] -= (charge) # placing ionic centers in cell return -chg_matrix
def process_vasprun(self, dir_name, taskname, filename): """ Adapted from matgendb.creator Process a vasprun.xml file. """ vasprun_file = os.path.join(dir_name, filename) vrun = Vasprun(vasprun_file, parse_potcar_file=self.parse_potcar_file) d = vrun.as_dict() # rename formula keys for k, v in { "formula_pretty": "pretty_formula", "composition_reduced": "reduced_cell_formula", "composition_unit_cell": "unit_cell_formula" }.items(): d[k] = d.pop(v) for k in ["eigenvalues", "projected_eigenvalues" ]: # large storage space breaks some docs if k in d["output"]: del d["output"][k] comp = Composition(d["composition_unit_cell"]) d["formula_anonymous"] = comp.anonymized_formula d["formula_reduced_abc"] = comp.reduced_composition.alphabetical_formula d["dir_name"] = os.path.abspath(dir_name) d["completed_at"] = str( datetime.datetime.fromtimestamp(os.path.getmtime(vasprun_file))) d["density"] = vrun.final_structure.density # replace 'crystal' with 'structure' d["input"]["structure"] = d["input"].pop("crystal") d["output"]["structure"] = d["output"].pop("crystal") for k, v in { "energy": "final_energy", "energy_per_atom": "final_energy_per_atom" }.items(): d["output"][k] = d["output"].pop(v) # Process bandstructure and DOS if self.bandstructure_mode != False: bs = self.process_bandstructure(vrun) if bs: d["bandstructure"] = bs if self.parse_dos != False: dos = self.process_dos(vrun) if dos: d["dos"] = dos # Parse electronic information if possible. # For certain optimizers this is broken and we don't get an efermi resulting in the bandstructure try: bs = vrun.get_band_structure() bs_gap = bs.get_band_gap() d["output"]["vbm"] = bs.get_vbm()["energy"] d["output"]["cbm"] = bs.get_cbm()["energy"] d["output"]["bandgap"] = bs_gap["energy"] d["output"]["is_gap_direct"] = bs_gap["direct"] d["output"]["is_metal"] = bs.is_metal() if not bs_gap["direct"]: d["output"]["direct_gap"] = bs.get_direct_band_gap() if isinstance(bs, BandStructureSymmLine): d["output"]["transition"] = bs_gap["transition"] except Exception: logger.warning("Error in parsing bandstructure") if vrun.incar["IBRION"] == 1: logger.warning( "Vasp doesn't properly output efermi for IBRION == 1") if self.bandstructure_mode is True: logger.error(traceback.format_exc()) logger.error("Error in " + os.path.abspath(dir_name) + ".\n" + traceback.format_exc()) raise # Should roughly agree with information from .get_band_structure() above, subject to tolerances # If there is disagreement, it may be related to VASP incorrectly assigning the Fermi level try: band_props = vrun.eigenvalue_band_properties d["output"]["eigenvalue_band_properties"] = { "bandgap": band_props[0], "cbm": band_props[1], "vbm": band_props[2], "is_gap_direct": band_props[3] } except Exception: logger.warning("Error in parsing eigenvalue band properties") # store run name and location ,e.g. relax1, relax2, etc. d["task"] = {"type": taskname, "name": taskname} # include output file names d["output_file_paths"] = self.process_raw_data(dir_name, taskname=taskname) # parse axially averaged locpot if "locpot" in d["output_file_paths"] and self.parse_locpot: locpot = Locpot.from_file( os.path.join(dir_name, d["output_file_paths"]["locpot"])) d["output"]["locpot"] = { i: locpot.get_average_along_axis(i) for i in range(3) } if self.store_volumetric_data: for file in self.store_volumetric_data: if file in d["output_file_paths"]: try: # assume volumetric data is all in CHGCAR format data = Chgcar.from_file( os.path.join(dir_name, d["output_file_paths"][file])) d[file] = data.as_dict() except: raise ValueError("Failed to parse {} at {}.".format( file, d["output_file_paths"][file])) # parse force constants if hasattr(vrun, "force_constants"): d["output"]["force_constants"] = vrun.force_constants.tolist() d["output"][ "normalmode_eigenvals"] = vrun.normalmode_eigenvals.tolist() d["output"][ "normalmode_eigenvecs"] = vrun.normalmode_eigenvecs.tolist() # perform Bader analysis using Henkelman bader if self.parse_bader and "chgcar" in d["output_file_paths"]: suffix = '' if taskname == 'standard' else ".{}".format(taskname) bader = bader_analysis_from_path(dir_name, suffix=suffix) d["bader"] = bader return d
VASP chgcar file is required ''' import sys import json import numpy as np from pymatgen.io.vasp.outputs import Chgcar from ase.calculators.vasp import VaspChargeDensity from PIL import Image from ChargeDensity.image_file_process import charge_file_crop_2d from CrystalToolkit.geometry_properties import symmetry_order_2d, rectangle_transform_2d from CrystalToolkit.vasp_chgcar import chgcar_file_slice_2d, charge_file_2d_reformat file_chgcar = sys.argv[1] stru = Chgcar.from_file(file_chgcar).poscar.structure ''' # find the symmetry order of the 2D structure with open('data.json') as json_file: symmetry_data = json.load(json_file) point_group_list = symmetry_data['point_group_list'] symmetry_order_2d(structure=stru, point_group_list=point_group_list) ''' # slice the vasp chgcar file into 2D vasp_charge = VaspChargeDensity(filename=file_chgcar) density = vasp_charge.chg[-1] atoms = vasp_charge.atoms[-1] *_, chgden_2d = chgcar_file_slice_2d(density=density, atoms=atoms) # change to RGB values, type must be unit 8 otherwise PIL will complain chgden_2d = (255.0 / chgden_2d.max() * (chgden_2d - chgden_2d.min())).astype( np.uint8)
def __init__(self, chgcar_filename, potcar_filename=None, chgref_filename=None, parse_atomic_densities=False): """ Initializes the Bader caller. Args: chgcar_filename (str): The filename of the CHGCAR. potcar_filename (str): Optional: the filename of the corresponding POTCAR file. Used for calculating the charge transfer. If None, the get_charge_transfer method will raise a ValueError. chgref_filename (str): Optional. The filename of the reference CHGCAR, which calculated by AECCAR0 + AECCAR2. (See http://theory.cm.utexas.edu/henkelman/code/bader/ for details.) parse_atomic_densities (bool): Optional. turns on atomic partition of the charge density charge densities are atom centered """ if not BADEREXE: raise RuntimeError( "BaderAnalysis requires the executable bader to be in the path." " Please download the library at http://theory.cm.utexas" ".edu/vasp/bader/ and compile the executable.") self.chgcar = Chgcar.from_file(chgcar_filename) self.potcar = Potcar.from_file(potcar_filename) \ if potcar_filename is not None else None self.natoms = self.chgcar.poscar.natoms chgcarpath = os.path.abspath(chgcar_filename) chgrefpath = os.path.abspath( chgref_filename) if chgref_filename else None self.reference_used = bool(chgref_filename) self.parse_atomic_densities = parse_atomic_densities with ScratchDir("."): with zopen(chgcarpath, 'rt') as f_in: with open("CHGCAR", "wt") as f_out: shutil.copyfileobj(f_in, f_out) args = [BADEREXE, "CHGCAR"] if chgref_filename: with zopen(chgrefpath, 'rt') as f_in: with open("CHGCAR_ref", "wt") as f_out: shutil.copyfileobj(f_in, f_out) args += ['-ref', 'CHGCAR_ref'] if parse_atomic_densities: args += ['-p', 'all_atom'] rs = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True) stdout, stderr = rs.communicate() if rs.returncode != 0: raise RuntimeError("bader exited with return code %d. " "Please check your bader installation." % rs.returncode) try: self.version = float(stdout.split()[5]) except Exception: self.version = -1 # Unknown if self.version < 1.0: warnings.warn('Your installed version of Bader is outdated, ' 'calculation of vacuum charge may be incorrect.') data = [] with open("ACF.dat") as f: raw = f.readlines() headers = ('x', 'y', 'z', 'charge', 'min_dist', 'atomic_vol') raw.pop(0) raw.pop(0) while True: line = raw.pop(0).strip() if line.startswith("-"): break vals = map(float, line.split()[1:]) data.append(dict(zip(headers, vals))) for l in raw: toks = l.strip().split(":") if toks[0] == "VACUUM CHARGE": self.vacuum_charge = float(toks[1]) elif toks[0] == "VACUUM VOLUME": self.vacuum_volume = float(toks[1]) elif toks[0] == "NUMBER OF ELECTRONS": self.nelectrons = float(toks[1]) self.data = data if self.parse_atomic_densities: # convert the charge denisty for each atom spit out by Bader into Chgcar objects for easy parsing atom_chgcars = [ Chgcar.from_file("BvAt{}.dat".format(str(i).zfill(4))) for i in range(1, len(self.chgcar.structure) + 1) ] atomic_densities = [] # For each atom in the structure for atom, loc, chg in zip(self.chgcar.structure, self.chgcar.structure.frac_coords, atom_chgcars): # Find the index of the atom in the charge density atom index = np.round(np.multiply(loc, chg.dim)) data = chg.data['total'] # Find the shift vector in the array shift = (np.divide(chg.dim, 2) - index).astype(int) # Shift the data so that the atomic charge density to the center for easier manipulation shifted_data = np.roll(data, shift, axis=(0, 1, 2)) # Slices a central window from the data array def slice_from_center(data, xwidth, ywidth, zwidth): x, y, z = data.shape startx = x // 2 - (xwidth // 2) starty = y // 2 - (ywidth // 2) startz = z // 2 - (zwidth // 2) return data[startx:startx + xwidth, starty:starty + ywidth, startz:startz + zwidth] # Finds the central encompassing volume which holds all the data within a precision def find_encompassing_vol(data, prec=1e-3): total = np.sum(data) for i in range(np.max(data.shape)): sliced_data = slice_from_center(data, i, i, i) if total - np.sum(sliced_data) < 0.1: return sliced_data return None d = { "data": find_encompassing_vol(shifted_data), "shift": shift, "dim": self.chgcar.dim } atomic_densities.append(d) self.atomic_densities = atomic_densities
def from_file(cls, chgcar_filename): chgcar = Chgcar.from_file(chgcar_filename) return cls(chgcar=chgcar)
def asym(chg, n): data = [] for j in range(chg.dim[1]): for i in range(chg.dim[0]): for k in range(chg.dim[2]): if i + k == int(chg.dim[0] + chg.dim[2]) / 2 + n: data.append(chg.data['total'][i, j, k]) data = np.array(data) data = np.reshape(data, (chg.dim[1], len(data) // chg.dim[1])) return data.astype(float) os.chdir('/home/jinho93/molecule/ddt/vasp/2020/bulk/sym/t1') chgcar = Chgcar.from_file('SPIN.vasp') dsym = sym(chgcar, 5) os.chdir('/home/jinho93/molecule/ddt/vasp/2020/bulk/asym/t1') chgcar = Chgcar.from_file('SPIN.vasp') dasym = asym(chgcar, 5) #%% os.chdir('/home/jinho93/molecule/ddt/vasp/2020/bulk/sym/t1') chgcar = Chgcar.from_file('SPIN.vasp') mlist = [] for i in range(25): mlist.append(np.max(sym(chgcar, i))) print(np.argmax(mlist), mlist[np.argmax(mlist)]) # %%
def dipole_chgcar(args): # Getting info about the cell print('Getting Electron Densities...', end='') sys.stdout.flush() chg = Chgcar.from_file('BvAt_summed.dat') s = chg.structure d = chg.data['total'] / chg.ngridpts # get charge density in e-/A^2 lengths = [] for i in range(3): lengths.append(len(chg.get_axis_grid(i))) lengths = np.array(lengths) print('done') # Adding ionic centers to cell print('Adding Ions to Cell...', end='') sys.stdout.flush() poscar = Poscar.from_file('POSCAR') potcar = Potcar.from_file('POTCAR') natoms = poscar.natoms cumm_natoms = np.array([ sum(natoms[0:i+1]) for i in range(len(natoms)) ]) for atom in args.atoms: # iterating over ion centers potcarsingle = potcar[np.argmax(cumm_natoms>=atom)] # get Potcarsingle for each atom charge = potcarsingle.nelectrons site = s.sites[atom - 1] # atoms are 1 indexed # number = site.species_and_occu.elements[0].number i = np.round(np.array([site.a, site.b, site.c]) * lengths) # getting indecies to place atomic charges in cell d[i[0] % lengths[0]][i[1] % lengths[1]][i[2] % lengths[2]] -= (charge) # placing ionic centers in cell print('done') # Make correction for charged species print('Calculating Correction for Charged Species...', end='') sys.stdout.flush() element_charge = sum(sum(sum(d))) bader_gridpts = len(np.nonzero(d)[2])# number of gridpoints in bader volume correction = element_charge / bader_gridpts # normalization constant to account for charged species print('done') print('\nCharge = ' + str(-element_charge) + ' e-\n') # print('\nCorrection = ' + str(correction) + ' e-\n') # Calculating lattice #axis = np.array(args.axis) # / np.linalg.norm(np.array(args.axis)) cart_axis = np.matrix(args.axis) * s.lattice.matrix unit_vector = cart_axis / np.linalg.norm((cart_axis)) a_axis = chg.get_axis_grid(0) b_axis = chg.get_axis_grid(1) c_axis = chg.get_axis_grid(2) len_a = len(a_axis) len_b = len(b_axis) len_c = len(c_axis) mod_a = int(np.round(float(len_a) * args.origin[0])) mod_b = int(np.round(float(len_b) * args.origin[1])) mod_c = int(np.round(float(len_c) * args.origin[2])) mat = s.lattice.matrix def get_first_moment(a, b, c, x): a = float((a - mod_a) % len_a) / len_a b = float((b - mod_b) % len_b) / len_b c = float((c - mod_c) % len_c) / len_c cart_vector = np.matrix([a, b, c]) * mat return float(np.dot(cart_vector, unit_vector.transpose())) * (x - correction) # integrate over charge density print('Calculating Dipole... ') sys.stdout.flush() dipole = 0 for a in range(lengths[0]): sys.stdout.write('\r%d percent' % int(a * 100 / len_a)) sys.stdout.flush() for b in range(lengths[1]): for c in range(lengths[2]): x = d[a][b][c] if x != 0: dipole += get_first_moment(a, b, c, x) print('done') print('Dipole = ' + str(dipole) + ' eA') print('Dipole = ' + str(dipole / 0.20819434) + ' D') return dipole sys.stdout.flush()
#%% from pymatgen.io.vasp.outputs import Chgcar from pymatgen.io.cube import Cube import os os.chdir('/home/jinho93/molecule/ddt/cp2k/bulk/asym/t1') cub = Cube('s1-SPIN_DENSITY-1_0.cube') cub.NZ for i in range(cub.NX): for j in range(cub.NY): for k in range(cub.NZ): if i + k > cub.NZ + 2 or i + k < cub.NZ / 2 + 5: cub.data[i, j, k] = 0 chg = Chgcar(cub.structure, {'total': cub.data}) chg.write_file('NEW_CHGCAR') # chg = Chgcar(cub.structure, cub.data) # %%
def from_path(cls, path, suffix="", zpsp=None): """ Convenience method to run critic2 analysis on a folder containing typical VASP output files. This method will: 1. Look for files CHGCAR, AECAR0, AECAR2, POTCAR or their gzipped counterparts. 2. If AECCAR* files are present, constructs a temporary reference file as AECCAR0 + AECCAR2. 3. Runs critic2 analysis twice: once for charge, and a second time for the charge difference (magnetization density). :param path: path to folder to search in :param suffix: specific suffix to look for (e.g. '.relax1' for 'CHGCAR.relax1.gz') :param zpsp: manually specify ZPSP if POTCAR not present :return: """ def _get_filepath(filename, warning, path=path, suffix=suffix): paths = glob.glob(os.path.join(path, filename + suffix + "*")) if not paths: warnings.warn(warning) return None if len(paths) > 1: # using reverse=True because, if multiple files are present, # they likely have suffixes 'static', 'relax', 'relax2', etc. # and this would give 'static' over 'relax2' over 'relax' # however, better to use 'suffix' kwarg to avoid this! paths.sort(reverse=True) warnings.warn("Multiple files detected, using {}".format( os.path.basename(path))) path = paths[0] return path chgcar_path = _get_filepath("CHGCAR", "Could not find CHGCAR!") chgcar = Chgcar.from_file(chgcar_path) chgcar_ref = None if not zpsp: potcar_path = _get_filepath( "POTCAR", "Could not find POTCAR, will not be able to calculate charge transfer.", ) if potcar_path: potcar = Potcar.from_file(potcar_path) zpsp = {p.symbol: p.zval for p in potcar} if not zpsp: # try and get reference "all-electron-like" charge density if zpsp not present aeccar0_path = _get_filepath( "AECCAR0", "Could not find AECCAR0, interpret Bader results with caution.", ) aeccar0 = Chgcar.from_file(aeccar0_path) if aeccar0_path else None aeccar2_path = _get_filepath( "AECCAR2", "Could not find AECCAR2, interpret Bader results with caution.", ) aeccar2 = Chgcar.from_file(aeccar2_path) if aeccar2_path else None chgcar_ref = aeccar0.linear_add(aeccar2) if (aeccar0 and aeccar2) else None return cls(chgcar.structure, chgcar, chgcar_ref, zpsp=zpsp)
def neb(directory, nimages=7, functional=("pbe", {}), is_metal=False, is_migration=False): """ Set up the NEB calculation from the initial and final structures. Args: directory (str): Directory in which the transition calculations should be set up. functional (tuple): Tuple with the functional choices. The first element contains a string that indicates the functional used ("pbe", "hse", ...), whereas the second element contains a dictionary that allows the user to specify the various functional tags. nimages (int): Number of images to use in the NEB calculation. is_metal (bool): Flag that indicates the material being studied is a metal, which changes the smearing from Gaussian to second order Methfessel-Paxton of 0.2 eV. is_migration (bool): Flag that indicates that the transition is a migration of an atom in the structure. Returns: None """ directory = os.path.abspath(directory) # Extract the optimized initial and final geometries initial_dir = os.path.join(directory, "initial") final_dir = os.path.join(directory, "final") try: # Check to see if the initial final_cathode structure is present initial_structure = Cathode.from_file( os.path.join(initial_dir, "final_cathode.json")).as_ordered_structure() except FileNotFoundError: # In case the required json file is not present, check to see if # there is VASP output which can be used initial_structure = Structure.from_file( os.path.join(initial_dir, "CONTCAR")) # Add the magnetic configuration to the initial structure initial_out = Outcar(os.path.join(initial_dir, "OUTCAR")) initial_magmom = [site["tot"] for site in initial_out.magnetization] try: initial_structure.add_site_property("magmom", initial_magmom) except ValueError: if len(initial_magmom) == 0: print("No magnetic moments found in OUTCAR file. Setting " "magnetic moments to zero.") initial_magmom = [0] * len(initial_structure) initial_structure.add_site_property("magmom", initial_magmom) else: raise ValueError("Number of magnetic moments in OUTCAR file " "do not match the number of sites!") except BaseException: raise FileNotFoundError("Could not find required structure " "information in " + initial_dir + ".") try: final_structure = Structure.from_file( os.path.join(final_dir, "CONTCAR")) except FileNotFoundError: final_structure = Cathode.from_file( os.path.join(final_dir, "final_cathode.json")).as_ordered_structure() # In case the transition is a migration if is_migration: # Set up the static potential for the Pathfinder from the host charge # density host_charge_density = Chgcar.from_file(os.path.join(directory, "host")) host_potential = ChgcarPotential(host_charge_density) migration_site_index = find_migrating_ion(initial_structure, final_structure) neb_path = NEBPathfinder(start_struct=initial_structure, end_struct=final_structure, relax_sites=migration_site_index, v=host_potential) images = neb_path.images neb_path.plot_images("neb.vasp") # In case an "middle image" has been provided via which to interpolate elif os.path.exists(os.path.join(directory, "middle")): print("Found a 'middle' directory in the NEB directory. Interpolating " "via middle geometry.") # Load the middle image middle_structure = Structure.from_file( os.path.join(directory, "middle", "CONTCAR")) # Perform an interpolation via this image images_1 = initial_structure.interpolate( end_structure=middle_structure, nimages=int((nimages + 1) / 2), interpolate_lattices=True) images_2 = middle_structure.interpolate(end_structure=final_structure, nimages=int((nimages) / 2 + 1), interpolate_lattices=True) images = images_1[:-1] + images_2 else: # Linearly interpolate the initial and final structures images = initial_structure.interpolate(end_structure=final_structure, nimages=nimages + 1, interpolate_lattices=True) # TODO Add functionality for NEB calculations with changing lattices user_incar_settings = {} # Set up the functional if functional[0] != "pbe": functional_config = _load_yaml_config(functional[0] + "Set") functional_config["INCAR"].update(functional[1]) user_incar_settings.update(functional_config["INCAR"]) # Add the standard Methfessel-Paxton smearing for metals if is_metal: user_incar_settings.update({"ISMEAR": 2, "SIGMA": 0.2}) neb_calculation = PybatNEBSet(images, potcar_functional=DFT_FUNCTIONAL, user_incar_settings=user_incar_settings) # Set up the NEB calculation neb_calculation.write_input(directory) # Make a file to visualize the transition neb_calculation.visualize_transition( os.path.join(directory, "transition.cif"))
def __init__( self, chgcar_filename=None, potcar_filename=None, chgref_filename=None, parse_atomic_densities=False, cube_filename=None, ): """ Initializes the Bader caller. Args: chgcar_filename (str): The filename of the CHGCAR. potcar_filename (str): The filename of the POTCAR. chgref_filename (str): The filename of the reference charge density. parse_atomic_densities (bool): Optional. turns on atomic partition of the charge density charge densities are atom centered cube_filename (str): Optional. The filename of the cube file. """ if not BADEREXE: raise RuntimeError( "BaderAnalysis requires the executable bader to be in the path." " Please download the library at http://theory.cm.utexas" ".edu/vasp/bader/ and compile the executable.") if not (cube_filename or chgcar_filename): raise ValueError("You must provide either a cube file or a CHGCAR") if cube_filename and chgcar_filename: raise ValueError( "You cannot parse a cube and a CHGCAR at the same time.") self.parse_atomic_densities = parse_atomic_densities if chgcar_filename: fpath = os.path.abspath(chgcar_filename) self.is_vasp = True self.chgcar = Chgcar.from_file(fpath) self.structure = self.chgcar.structure self.potcar = Potcar.from_file( potcar_filename) if potcar_filename is not None else None self.natoms = self.chgcar.poscar.natoms chgrefpath = os.path.abspath( chgref_filename) if chgref_filename else None self.reference_used = bool(chgref_filename) # List of nelects for each atom from potcar potcar_indices = [] for i, v in enumerate(self.natoms): potcar_indices += [i] * v self.nelects = ([ self.potcar[potcar_indices[i]].nelectrons for i in range(len(self.structure)) ] if self.potcar else []) else: fpath = os.path.abspath(cube_filename) self.is_vasp = False self.cube = Cube(fpath) self.structure = self.cube.structure self.nelects = None chgrefpath = os.path.abspath( chgref_filename) if chgref_filename else None self.reference_used = bool(chgref_filename) with ScratchDir("."): tmpfile = "CHGCAR" if chgcar_filename else "CUBE" with zopen(fpath, "rt") as f_in: with open(tmpfile, "wt") as f_out: shutil.copyfileobj(f_in, f_out) args = [BADEREXE, tmpfile] if chgref_filename: with zopen(chgrefpath, "rt") as f_in: with open("CHGCAR_ref", "wt") as f_out: shutil.copyfileobj(f_in, f_out) args += ["-ref", "CHGCAR_ref"] if parse_atomic_densities: args += ["-p", "all_atom"] with subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True) as rs: stdout, _ = rs.communicate() if rs.returncode != 0: raise RuntimeError( f"bader exited with return code {rs.returncode}. Please check your bader installation." ) try: self.version = float(stdout.split()[5]) except ValueError: self.version = -1 # Unknown if self.version < 1.0: warnings.warn( "Your installed version of Bader is outdated, calculation of vacuum charge may be incorrect.", UserWarning, ) data = [] with open("ACF.dat") as f: raw = f.readlines() headers = ("x", "y", "z", "charge", "min_dist", "atomic_vol") raw.pop(0) raw.pop(0) while True: l = raw.pop(0).strip() if l.startswith("-"): break vals = map(float, l.split()[1:]) data.append(dict(zip(headers, vals))) for l in raw: toks = l.strip().split(":") if toks[0] == "VACUUM CHARGE": self.vacuum_charge = float(toks[1]) elif toks[0] == "VACUUM VOLUME": self.vacuum_volume = float(toks[1]) elif toks[0] == "NUMBER OF ELECTRONS": self.nelectrons = float(toks[1]) self.data = data if self.parse_atomic_densities: # convert the charge density for each atom spit out by Bader into Chgcar objects for easy parsing atom_chgcars = [ Chgcar.from_file(f"BvAt{str(i).zfill(4)}.dat") for i in range(1, len(self.chgcar.structure) + 1) ] atomic_densities = [] # For each atom in the structure for _, loc, chg in zip( self.chgcar.structure, self.chgcar.structure.frac_coords, atom_chgcars, ): # Find the index of the atom in the charge density atom index = np.round(np.multiply(loc, chg.dim)) data = chg.data["total"] # Find the shift vector in the array shift = (np.divide(chg.dim, 2) - index).astype(int) # Shift the data so that the atomic charge density to the center for easier manipulation shifted_data = np.roll(data, shift, axis=(0, 1, 2)) # Slices a central window from the data array def slice_from_center(data, xwidth, ywidth, zwidth): x, y, z = data.shape startx = x // 2 - (xwidth // 2) starty = y // 2 - (ywidth // 2) startz = z // 2 - (zwidth // 2) return data[startx:startx + xwidth, starty:starty + ywidth, startz:startz + zwidth, ] # Finds the central encompassing volume which holds all the data within a precision def find_encompassing_vol(data): total = np.sum(data) for i in range(np.max(data.shape)): sliced_data = slice_from_center(data, i, i, i) if total - np.sum(sliced_data) < 0.1: return sliced_data return None d = { "data": find_encompassing_vol(shifted_data), "shift": shift, "dim": self.chgcar.dim, } atomic_densities.append(d) self.atomic_densities = atomic_densities
def __init__( self, path=None, atomic_densities_path=None, run_chargemol=True, ): """ Initializes the Chargemol Analysis. Args: path (str): Path to the CHGCAR, POTCAR, AECCAR0, and AECCAR files. Note that it doesn't matter if the files gzip'd or not. Default: None (current working directory). atomic_densities_path (str|None): Path to the atomic densities directory required by Chargemol. If None, Pymatgen assumes that this is defined in a "DDEC6_ATOMIC_DENSITIES_DIR" environment variable. Only used if run_chargemol is True. Default: None. run_chargemol (bool): Whether to run the Chargemol analysis. If False, the existing Chargemol output files will be read from path. Default: True. """ if not path: path = os.getcwd() if run_chargemol and not (which("Chargemol_09_26_2017_linux_parallel") or which("Chargemol_09_26_2017_linux_serial") or which("chargemol"), ): raise OSError( "ChargemolAnalysis requires the Chargemol executable to be in the path." " Please download the library at https://sourceforge.net/projects/ddec/files" "and follow the instructions.") if atomic_densities_path == "": atomic_densities_path = os.getcwd() self._atomic_densities_path = atomic_densities_path self._chgcarpath = self._get_filepath(path, "CHGCAR") self._potcarpath = self._get_filepath(path, "POTCAR") self._aeccar0path = self._get_filepath(path, "AECCAR0") self._aeccar2path = self._get_filepath(path, "AECCAR2") if run_chargemol and not (self._chgcarpath and self._potcarpath and self._aeccar0path and self._aeccar2path): raise FileNotFoundError( "CHGCAR, AECCAR0, AECCAR2, and POTCAR are all needed for Chargemol." ) if self._chgcarpath: self.chgcar = Chgcar.from_file(self._chgcarpath) self.structure = self.chgcar.structure self.natoms = self.chgcar.poscar.natoms else: self.chgcar = None self.structure = None self.natoms = None warnings.warn( "No CHGCAR found. Some properties may be unavailable.", UserWarning) if self._potcarpath: self.potcar = Potcar.from_file(self._potcarpath) else: warnings.warn( "No POTCAR found. Some properties may be unavailable.", UserWarning) self.aeccar0 = Chgcar.from_file( self._aeccar0path) if self._aeccar0path else None self.aeccar2 = Chgcar.from_file( self._aeccar2path) if self._aeccar2path else None if run_chargemol: self._execute_chargemol() else: self._from_data_dir(chargemol_output_path=path)
def __init__(self, chgcar_filename, potcar_filename=None, chgref_filename=None, parse_atomic_densities=False): """ Initializes the Bader caller. Args: chgcar_filename (str): The filename of the CHGCAR. potcar_filename (str): Optional: the filename of the corresponding POTCAR file. Used for calculating the charge transfer. If None, the get_charge_transfer method will raise a ValueError. chgref_filename (str): Optional. The filename of the reference CHGCAR, which calculated by AECCAR0 + AECCAR2. (See http://theory.cm.utexas.edu/henkelman/code/bader/ for details.) parse_atomic_densities (bool): Optional. turns on atomic partition of the charge density charge densities are atom centered """ if not BADEREXE: raise RuntimeError( "BaderAnalysis requires the executable bader to be in the path." " Please download the library at http://theory.cm.utexas" ".edu/vasp/bader/ and compile the executable.") self.chgcar = Chgcar.from_file(chgcar_filename) self.potcar = Potcar.from_file(potcar_filename) \ if potcar_filename is not None else None self.natoms = self.chgcar.poscar.natoms chgcarpath = os.path.abspath(chgcar_filename) chgrefpath = os.path.abspath(chgref_filename) if chgref_filename else None self.reference_used = True if chgref_filename else False self.parse_atomic_densities = parse_atomic_densities with ScratchDir(".") as temp_dir: with zopen(chgcarpath, 'rt') as f_in: with open("CHGCAR", "wt") as f_out: shutil.copyfileobj(f_in, f_out) args = [BADEREXE, "CHGCAR"] if chgref_filename: with zopen(chgrefpath, 'rt') as f_in: with open("CHGCAR_ref", "wt") as f_out: shutil.copyfileobj(f_in, f_out) args += ['-ref', 'CHGCAR_ref'] if parse_atomic_densities: args += ['-p', 'all_atom'] rs = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True) stdout, stderr = rs.communicate() if rs.returncode != 0: raise RuntimeError("bader exited with return code %d. " "Please check your bader installation." % rs.returncode) try: self.version = float(stdout.split()[5]) except: self.version = -1 # Unknown if self.version < 1.0: warnings.warn('Your installed version of Bader is outdated, ' 'calculation of vacuum charge may be incorrect.') data = [] with open("ACF.dat") as f: raw = f.readlines() headers = ('x', 'y', 'z', 'charge', 'min_dist', 'atomic_vol') raw.pop(0) raw.pop(0) while True: l = raw.pop(0).strip() if l.startswith("-"): break vals = map(float, l.split()[1:]) data.append(dict(zip(headers, vals))) for l in raw: toks = l.strip().split(":") if toks[0] == "VACUUM CHARGE": self.vacuum_charge = float(toks[1]) elif toks[0] == "VACUUM VOLUME": self.vacuum_volume = float(toks[1]) elif toks[0] == "NUMBER OF ELECTRONS": self.nelectrons = float(toks[1]) self.data = data if self.parse_atomic_densities: # convert the charge denisty for each atom spit out by Bader into Chgcar objects for easy parsing atom_chgcars = [Chgcar.from_file("BvAt{}.dat".format(str(i).zfill(4))) for i in range(1, len(self.chgcar.structure) + 1)] atomic_densities = [] # For each atom in the structure for atom, loc, chg in zip(self.chgcar.structure, self.chgcar.structure.frac_coords, atom_chgcars): # Find the index of the atom in the charge density atom index = np.round(np.multiply(loc, chg.dim)) data = chg.data['total'] # Find the shift vector in the array shift = (np.divide(chg.dim, 2) - index).astype(int) # Shift the data so that the atomic charge density to the center for easier manipulation shifted_data = np.roll(data, shift, axis=(0, 1, 2)) # Slices a central window from the data array def slice_from_center(data, xwidth, ywidth, zwidth): x, y, z = data.shape startx = x // 2 - (xwidth // 2) starty = y // 2 - (ywidth // 2) startz = z // 2 - (zwidth // 2) return data[startx:startx + xwidth, starty:starty + ywidth, startz:startz + zwidth] # Finds the central encompassing volume which holds all the data within a precision def find_encompassing_vol(data,prec=1e-3): total = np.sum(data) for i in range(np.max(data.shape)): sliced_data = slice_from_center(data,i,i,i) if total - np.sum(sliced_data) < 0.1: return sliced_data return None d = { "data": find_encompassing_vol(shifted_data), "shift": shift, "dim": self.chgcar.dim } atomic_densities.append(d) self.atomic_densities = atomic_densities
def from_file(filename): cc = Chgcar.from_file(filename) return Pathfinder(cc)