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 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 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 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_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_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_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 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 __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 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 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 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 __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
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 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 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
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()
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)