def set_potcar(self, mapping=None, functional='PBE'): """ set the potcar: symbol to potcar type mapping """ symbols = self.poscar.site_symbols mapped_symbols = [] if mapping: for sym in symbols: mapped_symbols.append(mapping[sym]) elif self.mappings_override: for sym in symbols: if sym in self.mappings_override.keys(): mapped_symbols.append(self.mappings_override[sym]) else: mapped_symbols.append(sym) else: mapped_symbols = symbols if functional: func=functional else: func=self.functional self.potcar = Potcar(symbols=mapped_symbols, functional=func) pass
def setup(self): """ setup static jobs for all the calibrate objects copies CONTCAR to POSCAR sets NSW = 0 """ for cal in self.cal_objs: for i, jdir in enumerate(cal.old_job_dir_list): job_dir = self.job_dir + os.sep \ + jdir.replace(os.sep, '_').replace('.', '_') \ + os.sep + 'STATIC' logger.info('setting up job in {}'.format(job_dir)) cal.incar = Incar.from_file(jdir + os.sep + 'INCAR') cal.incar['EDIFF'] = '1E-6' cal.incar['NSW'] = 0 cal.potcar = Potcar.from_file(jdir + os.sep + 'POTCAR') cal.kpoints = Kpoints.from_file(jdir + os.sep + 'KPOINTS') contcar_file = jdir + os.sep + 'CONTCAR' if os.path.isfile(contcar_file): logger.info('setting poscar file from {}' .format(contcar_file)) cal.poscar = Poscar.from_file(contcar_file) cal.add_job(job_dir=job_dir) else: logger.critical("""CONTCAR doesnt exist. Setting up job using input set in the old calibration directory""") cal.poscar = Poscar.from_file(jdir + os.sep + 'POSCAR') cal.add_job(job_dir=job_dir)
def __init__(self, name, incar, poscar, potcar, kpoints, qadapter=None, **kwargs ): """ default INCAR from config_dict """ self.name = name self.incar = Incar.from_dict(incar.as_dict()) self.poscar = Poscar.from_dict(poscar.as_dict()) self.potcar = Potcar.from_dict(potcar.as_dict()) self.kpoints = Kpoints.from_dict(kpoints.as_dict()) self.extra = kwargs if qadapter is not None: self.qadapter = qadapter.from_dict(qadapter.to_dict()) else: self.qadapter = None config_dict = {} config_dict['INCAR'] = self.incar.as_dict() config_dict['POSCAR'] = self.poscar.as_dict() #caution the key and the value are not always the same config_dict['POTCAR'] = self.potcar.as_dict() #dict(zip(self.potcar.as_dict()['symbols'], #self.potcar.as_dict()['symbols'])) config_dict['KPOINTS'] = self.kpoints.as_dict() #self.user_incar_settings = self.incar.as_dict() DictVaspInputSet.__init__(self, name, config_dict, ediff_per_atom=False, **kwargs)
def setUp(self): if "PMG_VASP_PSP_DIR" not in os.environ: test_potcar_dir = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "test_files")) os.environ["PMG_VASP_PSP_DIR"] = test_potcar_dir filepath = os.path.join(test_dir, 'POTCAR') self.potcar = Potcar.from_file(filepath)
def test_potcar_map(self): fe_potcar = zopen(PymatgenTest.TEST_FILES_DIR / "POT_GGA_PAW_PBE" / "POTCAR.Fe_pv.gz").read().decode("utf-8") # specify V instead of Fe - this makes sure the test won't pass if the # code just grabs the POTCAR from the config file (the config file would # grab the V POTCAR) potcar = Potcar(["V"], sym_potcar_map={"V": fe_potcar}) self.assertEqual(potcar.symbols, ["Fe_pv"], "Wrong symbols read in for POTCAR")
def __init__(self, name, incar, poscar, kpoints, potcar=None, qadapter=None, script_name='submit_script', vis_logger=None, reuse_path=None, test=False, **kwargs): """ default INCAR from config_dict """ self.name = name self.test = test #print (test) self.incar_init = Incar.from_dict(incar.as_dict()) self.poscar_init = Poscar.from_dict(poscar.as_dict()) if not self.test: self.potcar_init = Potcar.from_dict(potcar.as_dict()) if not isinstance(kpoints, str): self.kpoints_init = Kpoints.from_dict(kpoints.as_dict()) #print (kpoints) else: self.kpoints_init = kpoints self.reuse_path = reuse_path # complete reuse paths self.extra = kwargs if qadapter is not None: self.qadapter = qadapter.from_dict(qadapter.to_dict()) else: self.qadapter = None self.script_name = script_name config_dict = {} config_dict['INCAR'] = self.incar_init.as_dict() config_dict['POSCAR'] = self.poscar_init.as_dict() # caution the key and the value are not always the same if not self.test: config_dict['POTCAR'] = self.potcar_init.as_dict() # dict(zip(self.potcar.as_dict()['symbols'], # self.potcar.as_dict()['symbols'])) if not isinstance(kpoints, str): #print (self.kpoints_init) config_dict['KPOINTS'] = self.kpoints_init.as_dict() else: # need to find a way to dictify this kpoints string more # appropriately config_dict['KPOINTS'] = {'kpts_hse': self.kpoints_init} # self.user_incar_settings = self.incar.as_dict() DictSet.__init__(self, poscar.structure, config_dict) #**kwargs) if vis_logger: self.logger = vis_logger else: self.logger = logger
def generate_potcar(struct, dirname='.'): """ Generate POTCAR according to input structure via pymatgen's POTCAR module """ avail_pot = " ".join(Potcar.FUNCTIONAL_CHOICES) tip = """ Available Pseudo-potentials are: """ tip += avail_pot + '\n' warn_tip(1, tip) print('your choice ?') wait_sep() in_str = wait() assert in_str.upper() in avail_pot potcar = Potcar([el.value for el in struct.types_of_specie], functional=in_str) potcar.write_file(os.path.join(dirname, "POTCAR"))
def collect_inputs(self, root_path="./"): incar = Incar.from_file(root_path + "/INCAR") kpoints = Kpoints.from_file(root_path + "/KPOINTS") potcar = Potcar.from_file(root_path + "/POTCAR") potcar_array = {} for atom in potcar: potcar_array[atom.keywords["TITEL"]] = atom.keywords["VRHFIN"] input_dict = {"INCAR": incar.as_dict(), "KPOINTS": kpoints.as_dict(), "Pseudos": potcar_array} return input_dict
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 write_potcar(self): # NEED check if correct # TODO: # Every time when changing running environment, need uncomment the following two lines # os.system('pmg config --add PMG_VASP_PSP_DIR G:/pseudo-potential/') potcar_file_name = os.path.join(self._save_to_path, 'POTCAR') # Vasp functional set as potpaw_GGA Potcar(symbols=self.elements, functional='PW91').write_file(potcar_file_name)
def from_dict(cls, d): incar = Incar.from_dict(d["incar"]) poscar = Poscar.from_dict(d["poscar"]) potcar = Potcar.from_dict(d["potcar"]) kpoints = Kpoints.from_dict(d["kpoints"]) qadapter = None if d["qadapter"] is not None: qadapter = CommonAdapter.from_dict(d["qadapter"]) return MPINTVaspInputSet(d["name"], incar, poscar, potcar, kpoints, qadapter, **d["kwargs"])
def setUp(self): filepath = self.TEST_FILES_DIR / 'INCAR' incar = Incar.from_file(filepath) filepath = self.TEST_FILES_DIR / 'POSCAR' poscar = Poscar.from_file(filepath, check_for_POTCAR=False) if "PMG_VASP_PSP_DIR" not in os.environ: os.environ["PMG_VASP_PSP_DIR"] = str(self.TEST_FILES_DIR) filepath = self.TEST_FILES_DIR / 'POTCAR' potcar = Potcar.from_file(filepath) filepath = self.TEST_FILES_DIR / 'KPOINTS.auto' kpoints = Kpoints.from_file(filepath) self.vinput = VaspInput(incar, kpoints, poscar, potcar)
def setUp(self): filepath = self.TEST_FILES_DIR / 'INCAR' incar = Incar.from_file(filepath) filepath = self.TEST_FILES_DIR / 'POSCAR' poscar = Poscar.from_file(filepath,check_for_POTCAR=False) if "PMG_VASP_PSP_DIR" not in os.environ: os.environ["PMG_VASP_PSP_DIR"] = str(self.TEST_FILES_DIR) filepath = self.TEST_FILES_DIR / 'POTCAR' potcar = Potcar.from_file(filepath) filepath = self.TEST_FILES_DIR / 'KPOINTS.auto' kpoints = Kpoints.from_file(filepath) self.vinput = VaspInput(incar, kpoints, poscar, potcar)
def setUp(self): filepath = PymatgenTest.TEST_FILES_DIR / "INCAR" incar = Incar.from_file(filepath) filepath = PymatgenTest.TEST_FILES_DIR / "POSCAR" poscar = Poscar.from_file(filepath, check_for_POTCAR=False) if "PMG_VASP_PSP_DIR" not in os.environ: os.environ["PMG_VASP_PSP_DIR"] = str(PymatgenTest.TEST_FILES_DIR) filepath = PymatgenTest.TEST_FILES_DIR / "POTCAR" potcar = Potcar.from_file(filepath) filepath = PymatgenTest.TEST_FILES_DIR / "KPOINTS.auto" kpoints = Kpoints.from_file(filepath) self.vinput = VaspInput(incar, kpoints, poscar, potcar)
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 set_potcar(self, mapping=None, functional='PBE'): """ set the potcar: symbol to potcar type mapping """ symbols = self.poscar.site_symbols mapped_symbols = [] if mapping: for sym in symbols: mapped_symbols.append(mapping[sym]) elif self.mappings_override: for sym in symbols: if sym in self.mappings_override.keys(): mapped_symbols.append(self.mappings_override[sym]) else: mapped_symbols.append(sym) else: mapped_symbols = symbols if functional: func = functional else: func = self.functional self.potcar = Potcar(symbols=mapped_symbols, functional=func)
def from_dict(cls, d): incar = Incar.from_dict(d["incar"]) poscar = Poscar.from_dict(d["poscar"]) potcar = Potcar.from_dict(d["potcar"]) kpoints = Kpoints.from_dict(d["kpoints"]) qadapter = None if d["qadapter"] is not None: qadapter = CommonAdapter.from_dict(d["qadapter"]) script_name = d["script_name"] return MPINTVaspInputSet(d["name"], incar, poscar, potcar, kpoints, qadapter, script_name=script_name, vis_logger=logging.getLogger(d["logger"]), **d["kwargs"])
def get_nelec_LHFCALC(self, dir): pos = Poscar.from_file(os.path.join(dir, 'POSCAR')) pot = Potcar.from_file(os.path.join(dir, 'POTCAR')) incar = Incar.from_file(os.path.join(dir, 'INCAR')) kpt = Kpoints.from_file(os.path.join(dir, 'KPOINTS')) if 'LHFCALC' in incar: LHFCALC = incar['LHFCALC'] else: LHFCALC = False natoms = pos.natoms nelec_atom = [i.nelectrons for i in pot] nelec_elt = np.array(natoms) * np.array(nelec_atom) nelec_sum = nelec_elt.sum() return (nelec_sum, LHFCALC)
def potcar_from_linklist(cls, poscar_data, linklist): """ Assemble pymatgen Potcar object from a list of VaspPotcarData instances Reads pseudo-potential from the passed list connecting each element with it's potential and creates the complete Potcar file according to the element ordering fodun in the passed poscar data object. :param poscar_data: input structure for VASP calculations :type poscar: :class:`aiida_cusp.data.inputs.VaspPoscarData` :param linklist: dictionary mapping element names to VaspPotcarData instances :type linklist: `dict` :returns: pymatgen Potcar data instance with containing the concatenated pseudo-potential information for all elements defined in the linklist :rtype: :class:`~pymatgen.io.vasp.inputs.Potcar` """ # initialize empty Potcar object complete_potcar = Potcar() # file empty potcar with potential in order of elements found in the # passed structure data site_symbols = poscar_data.get_poscar().site_symbols for site_symbol in site_symbols: try: potential_pointer = linklist[site_symbol] except KeyError: raise VaspPotcarDataError( "Found no potential in passed " "potential-element map for " "site symbol '{}'".format(site_symbol)) potential_file = potential_pointer.load_potential_file_node() potential_contents = potential_file.get_content() potcar_single = PotcarSingle(potential_contents) complete_potcar.append(potcar_single) return complete_potcar
def from_dict(cls, d): incar = Incar.from_dict(d["incar"]) poscar = Poscar.from_dict(d["poscar"]) potcar = Potcar.from_dict(d["potcar"]) kpoints = Kpoints.from_dict(d["kpoints"]) cal = Calibrate(incar, poscar, potcar, kpoints, system=d["system"], is_matrix = d["is_matrix"], Grid_type = d["Grid_type"], parent_job_dir=d["parent_job_dir"], job_dir=d["job_dir"], qadapter=d.get("qadapter"), job_cmd=d["job_cmd"], wait=d["wait"], turn_knobs=d["turn_knobs"]) cal.job_dir_list = d["job_dir_list"] cal.job_ids = d["job_ids"] return cal
def setUp(self): filepath = os.path.join(test_dir, 'INCAR') incar = Incar.from_file(filepath) filepath = os.path.join(test_dir, 'POSCAR') poscar = Poscar.from_file(filepath,check_for_POTCAR=False) if "PMG_VASP_PSP_DIR" not in os.environ: test_potcar_dir = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "test_files")) os.environ["PMG_VASP_PSP_DIR"] = test_potcar_dir filepath = os.path.join(test_dir, 'POTCAR') potcar = Potcar.from_file(filepath) filepath = os.path.join(test_dir, 'KPOINTS.auto') kpoints = Kpoints.from_file(filepath) self.vinput = VaspInput(incar, kpoints, poscar, potcar)
def from_dict(cls, d): incar = Incar.from_dict(d["incar"]) poscar = Poscar.from_dict(d["poscar"]) potcar = Potcar.from_dict(d["potcar"]) kpoints = Kpoints.from_dict(d["kpoints"]) cal = Calibrate(incar, poscar, potcar, kpoints, system=d["system"], is_matrix=d["is_matrix"], Grid_type=d["Grid_type"], parent_job_dir=d["parent_job_dir"], job_dir=d["job_dir"], qadapter=d.get("qadapter"), job_cmd=d["job_cmd"], wait=d["wait"], turn_knobs=d["turn_knobs"]) cal.job_dir_list = d["job_dir_list"] cal.job_ids = d["job_ids"] return cal
def setUp(self): filepath = os.path.join(test_dir, "INCAR") incar = Incar.from_file(filepath) filepath = os.path.join(test_dir, "POSCAR") poscar = Poscar.from_file(filepath) if "VASP_PSP_DIR" not in os.environ: test_potcar_dir = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "test_files") ) os.environ["VASP_PSP_DIR"] = test_potcar_dir filepath = os.path.join(test_dir, "POTCAR") potcar = Potcar.from_file(filepath) filepath = os.path.join(test_dir, "KPOINTS.auto") kpoints = Kpoints.from_file(filepath) self.vinput = VaspInput(incar, kpoints, poscar, potcar)
def test_charged_defect_incar(self): with ScratchDir('.'): make_vasp_defect_files(self.defects, self.path) cr_def_path = glob.glob(os.path.join(self.path, 'vac*Cr'))[0] incar_loc = os.path.join(cr_def_path, 'charge_-1') try: potcar = Potcar.from_file(os.path.join(incar_loc, "POTCAR")) except: potcar = None if potcar: incar = Incar.from_file(os.path.join(incar_loc, "INCAR")) self.assertIsNotNone(incar.pop('NELECT', None)) self.assertTrue( self.neutral_def_incar_min.items() <= incar.items()) self.assertTrue(set(self.def_keys).issubset(incar))
def test_sbt(self): from scipy.special import spherical_jn as jn cr = CoreRegion(Potcar.from_file("POTCAR")) r = cr.pps['Ga'].grid f = cr.pps['Ga'].aewaves[0] - cr.pps['Ga'].pswaves[0] ks, res = pawpyc.spherical_bessel_transform(1e6, 0, r, f) k = ks[180] vals = jn(0, r * k) * f * r integral = np.trapz(vals, r) print(integral) print(ks[180]) print(res[180]) assert_almost_equal(integral, res[180], decimal=3) perc = ks**2 * res**2 perc = np.cumsum(perc) perc /= np.max(perc)
def get_encut_from_potcar(fldr): potcar_file = os.path.join(fldr,"POTCAR") if not os.path.exists(potcar_file): logger.warning("Not POTCAR in {} to parse ENCUT".format(fldr)) error_msg = ": Failure, No POTCAR file." return (None, error_msg) #Further processing is not useful try: potcar = Potcar.from_file(potcar_file) except: logger.warning("Couldn't parse {}".format(potcar_file)) error_msg = ": Failure, couldn't read POTCAR file." return (None, error_msg) encut = max(ptcr_sngl.enmax for ptcr_sngl in potcar) return (encut, None)
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 setup(self): """ setup solvation jobs for the calibrate objects copies WAVECAR and sets the solvation params in the incar file also dumps system.json file in each directory for the database crawler mind: works only for cal objects that does only single calculations """ for cal in self.cal_objs: jdir = cal.old_job_dir_list[0] cal.poscar = Poscar.from_file(jdir + os.sep + 'POSCAR') cal.potcar = Potcar.from_file(jdir + os.sep + 'POTCAR') cal.kpoints = Kpoints.from_file(jdir + os.sep + 'KPOINTS') cal.incar = Incar.from_file(jdir + os.sep + 'INCAR') cal.incar['LSOL'] = '.TRUE.' syms = [site.specie.symbol for site in cal.poscar.structure] zvals = {p.symbol: p.nelectrons for p in cal.potcar} nelectrons = sum([zvals[a[0]] * len(tuple(a[1])) for a in itertools.groupby(syms)]) keys = [k for k in self.sol_params.keys() if self.sol_params[k]] prod_list = [self.sol_params.get(k) for k in keys] for params in itertools.product(*tuple(prod_list)): job_dir = self.job_dir + os.sep \ + cal.old_job_dir_list[0].replace(os.sep, '_').replace('.', '_') \ + os.sep + 'SOL' for i, k in enumerate(keys): if k == 'NELECT': cal.incar[k] = params[i] + nelectrons else: cal.incar[k] = params[i] job_dir = job_dir + os.sep + k + os.sep + str( cal.incar[k]).replace('.', '_') if not os.path.exists(job_dir): os.makedirs(job_dir) with open(job_dir + os.sep + 'system.json', 'w') as f: json.dump(dict(list(zip(keys, params))), f) wavecar_file = cal.old_job_dir_list[0] + os.sep + 'WAVECAR' if os.path.isfile(wavecar_file): shutil.copy(wavecar_file, job_dir + os.sep + 'WAVECAR') cal.add_job(job_dir=job_dir) else: logger.critical('WAVECAR doesnt exist. Aborting ...') sys.exit(0)
def setup(self): """ setup solvation jobs for the calibrate objects copies WAVECAR and sets the solvation params in the incar file also dumps system.json file in each directory for the database crawler mind: works only for cal objects that does only single calculations """ for cal in self.cal_objs: jdir = cal.old_job_dir_list[0] cal.poscar = Poscar.from_file(jdir + os.sep + 'POSCAR') cal.potcar = Potcar.from_file(jdir + os.sep + 'POTCAR') cal.kpoints = Kpoints.from_file(jdir + os.sep + 'KPOINTS') cal.incar = Incar.from_file(jdir + os.sep + 'INCAR') cal.incar['LSOL'] = '.TRUE.' syms = [site.specie.symbol for site in cal.poscar.structure] zvals = {p.symbol: p.nelectrons for p in cal.potcar} nelectrons = sum([ zvals[a[0]] * len(tuple(a[1])) for a in itertools.groupby(syms) ]) keys = [k for k in self.sol_params.keys() if self.sol_params[k]] prod_list = [self.sol_params.get(k) for k in keys] for params in itertools.product(*tuple(prod_list)): job_dir = self.job_dir + os.sep \ + cal.old_job_dir_list[0].replace(os.sep, '_').replace('.', '_') \ + os.sep + 'SOL' for i, k in enumerate(keys): if k == 'NELECT': cal.incar[k] = params[i] + nelectrons else: cal.incar[k] = params[i] job_dir = job_dir + os.sep + k + os.sep + str( cal.incar[k]).replace('.', '_') if not os.path.exists(job_dir): os.makedirs(job_dir) with open(job_dir + os.sep + 'system.json', 'w') as f: json.dump(dict(list(zip(keys, params))), f) wavecar_file = cal.old_job_dir_list[0] + os.sep + 'WAVECAR' if os.path.isfile(wavecar_file): shutil.copy(wavecar_file, job_dir + os.sep + 'WAVECAR') cal.add_job(job_dir=job_dir) else: logger.critical('WAVECAR doesnt exist. Aborting ...') sys.exit(0)
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 __init__(self, name, incar, poscar, kpoints, potcar=None, qadapter=None, script_name='submit_script', vis_logger=None, reuse_path=None, test=False, **kwargs): """ default INCAR from config_dict """ self.name = name self.test = test self.incar_init = Incar.from_dict(incar.as_dict()) self.poscar_init = Poscar.from_dict(poscar.as_dict()) if not self.test: self.potcar_init = Potcar.from_dict(potcar.as_dict()) if not isinstance(kpoints, str): self.kpoints_init = Kpoints.from_dict(kpoints.as_dict()) else: self.kpoints_init = kpoints self.reuse_path = reuse_path # complete reuse paths self.extra = kwargs if qadapter is not None: self.qadapter = qadapter.from_dict(qadapter.to_dict()) else: self.qadapter = None self.script_name = script_name config_dict = {} config_dict['INCAR'] = self.incar_init.as_dict() config_dict['POSCAR'] = self.poscar_init.as_dict() # caution the key and the value are not always the same if not self.test: config_dict['POTCAR'] = self.potcar_init.as_dict() # dict(zip(self.potcar.as_dict()['symbols'], # self.potcar.as_dict()['symbols'])) if not isinstance(kpoints, str): config_dict['KPOINTS'] = self.kpoints_init.as_dict() else: # need to find a way to dictify this kpoints string more # appropriately config_dict['KPOINTS'] = {'kpts_hse':self.kpoints_init} # self.user_incar_settings = self.incar.as_dict() DictSet.__init__(self, poscar.structure, config_dict) #**kwargs) if vis_logger: self.logger = vis_logger else: self.logger = logger
def setup(self): """ setup static jobs for the calibrate objects copies CONTCAR to POSCAR sets NSW = 0 write system.json file for database crawler """ d = {} for cal in self.cal_objs: for i, jdir in enumerate(cal.old_job_dir_list): job_dir = self.job_dir + os.sep \ + jdir.replace(os.sep, '_').replace('.', '_') + \ os.sep + 'STATIC' cal.incar = Incar.from_file(jdir + os.sep + 'INCAR') cal.incar['EDIFF'] = '1E-6' cal.incar['NSW'] = 0 cal.potcar = Potcar.from_file(jdir + os.sep + 'POTCAR') cal.kpoints = Kpoints.from_file(jdir + os.sep + 'KPOINTS') contcar_file = jdir + os.sep + 'CONTCAR' if os.path.isfile(contcar_file): cal.poscar = Poscar.from_file(contcar_file) if cal in self.cal_slabs or cal in self.cal_interfaces: try: d['hkl'] = cal.system['hkl'] except: logger.critical("""the calibrate object doesnt have a system set for calibrating""") if cal in self.cal_interfaces: try: d['ligand'] = cal.system['ligand']['name'] except: logger.critical("""the calibrate object doesnt have a system set for calibrating""") if not os.path.exists(job_dir): os.makedirs(job_dir) if d: with open(job_dir + os.sep + 'system.json', 'w') as f: json.dump(d, f) cal.add_job(job_dir=job_dir) else: logger.critical("""CONTCAR doesnt exist. Setting up job using input set in the old calibration directory""") cal.poscar = Poscar.from_file(jdir + os.sep + 'POSCAR') cal.add_job(job_dir=job_dir)
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 _get_potcar_symbols(POTCAR_input: str) -> list: """ will return the name of the species in the POTCAR Args: POTCAR_input(str): string to potcar file Returns: list of the names of the species in string format """ potcar = Potcar.from_file(POTCAR_input) for pot in potcar: if pot.potential_type != "PAW": raise IOError( "Lobster only works with PAW! Use different POTCARs") if potcar.functional != "PBE": raise IOError("We only have BASIS options for PBE so far") Potcar_names = [name["symbol"] for name in potcar.spec] return Potcar_names
def __init__(self, name, incar, poscar, potcar, kpoints, qadapter=None, script_name='submit_script', vis_logger=None, **kwargs): """ default INCAR from config_dict """ self.name = name self.incar = Incar.from_dict(incar.as_dict()) self.poscar = Poscar.from_dict(poscar.as_dict()) self.potcar = Potcar.from_dict(potcar.as_dict()) self.kpoints = Kpoints.from_dict(kpoints.as_dict()) self.extra = kwargs if qadapter is not None: self.qadapter = qadapter.from_dict(qadapter.to_dict()) else: self.qadapter = None self.script_name = script_name config_dict = {} config_dict['INCAR'] = self.incar.as_dict() config_dict['POSCAR'] = self.poscar.as_dict() # caution the key and the value are not always the same config_dict['POTCAR'] = self.potcar.as_dict() # dict(zip(self.potcar.as_dict()['symbols'], # self.potcar.as_dict()['symbols'])) config_dict['KPOINTS'] = self.kpoints.as_dict() # self.user_incar_settings = self.incar.as_dict() DictVaspInputSet.__init__(self, name, config_dict, ediff_per_atom=False, **kwargs) if vis_logger: self.logger = vis_logger else: self.logger = logger
def from_files( struct="CONTCAR", wavecar="WAVECAR", cr="POTCAR", vr="vasprun.xml", setup_projectors=False, ): """ Construct a Wavefunction object from file paths. Arguments: struct (str): VASP POSCAR or CONTCAR file path wavecar (str): VASP WAVECAR file path cr (str): VASP POTCAR file path vr (str): VASP vasprun file path outcar (str): VASP OUTCAR file path setup_projectors (bool, False): Whether to set up the core region components of the wavefunctions. Pawpyseed will set up the projectors automatically when they are first needed, so this generally can be left as False. Returns: Wavefunction object """ for fname in [struct, wavecar, cr, vr]: if not os.path.isfile(fname): raise FileNotFoundError(f"File {fname} does not exist.") vr = Vasprun(vr) dim = np.array( [vr.parameters["NGX"], vr.parameters["NGY"], vr.parameters["NGZ"]] ) symprec = vr.parameters["SYMPREC"] pwf = pawpyc.PWFPointer(wavecar, vr) return Wavefunction( Poscar.from_file(struct).structure, pwf, CoreRegion(Potcar.from_file(cr)), dim, symprec, setup_projectors, )
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 generate(q=0,runtype='relax',functional='PBE',soc=False,relaxcell=False): """ Generate INCAR file. Parameters ---------- [optional] q (int): net charge of system. Default=0. [optional] runtype (str): type of calculation: relax(default)/dos/bands/dielectric [optional] functional (str): type of functional: PBE(default)/SCAN+rVV10 [optional] soc (bool): to include spin-orbit coupling? Default=False. [optional] relaxcell (bool): to relax cell lattice parameters? Default=Fase. """ dir_sub = os.getcwd() poscar = Poscar.from_file(os.path.join(dir_sub,"POSCAR")) potcar = Potcar.from_file(os.path.join(dir_sub,"POTCAR")) wavecar = os.path.exists(os.path.join(dir_sub,"WAVECAR")) ## just a boolean (does file exist) chgcar = os.path.exists(os.path.join(dir_sub,"CHGCAR")) ## just a boolean (does file exist) inc = IncarSettings(functional.split("+"),runtype,int(q),poscar,potcar,wavecar,chgcar) inc.setparams() if soc: inc.mag(ncl=True) inc.soc() if relaxcell: inc.ionicrelax(isif=3) inc.parallel(ncore=1) inc.output(lcharg=True) if runtype == 'dielectric': inc.dielectric() inc.startup(isym=None) inc.parallel(ncore=None) inc.output(lvtot=None,lvhar=None) inc.stripNone() with open(os.path.join(dir_sub,"INCAR"),'w') as f: f.write(Incar.get_string(inc.params,sort_keys=False))
def test_init(self): self.assertEqual(self.potcar.symbols, ["Fe", "P", "O"], "Wrong symbols read in for POTCAR") potcar = Potcar(["Fe_pv", "O"]) self.assertEqual(potcar[0].enmax, 293.238)
def setUp(self): if "PMG_VASP_PSP_DIR" not in os.environ: os.environ["PMG_VASP_PSP_DIR"] = str(self.TEST_FILES_DIR) filepath = self.TEST_FILES_DIR / 'POTCAR' self.potcar = Potcar.from_file(filepath)
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 __init__(self, incar, poscar, potcar, kpoints, system=None, is_matrix = False, Grid_type = 'A', parent_job_dir='.',job_dir='Job', qadapter=None, job_cmd='qsub', wait=True, mappings_override = None, functional="PBE", turn_knobs=OrderedDict( [ ('ENCUT',[]), ('KPOINTS',[])] ), checkpoint_file=None, cal_logger=None): """ Calibrate constructor Args: incar (Incar object): input INCAR poscar (Poscar object): input POSCAR potcar (Potcar object): input POTCAR kpoints: input KPOINTS system: system info as a dictionary, slab or interface example: system={'hkl':[1,1,1], 'ligand':None}, is_matrix (bool): whether the jobs are dependent on each other Grid_type: kpoints grid_type parent_job_dir: the directory from which all the jobs are launched job_dir: job directory qadapter: adapter for the batch system job_cmd: command to be used for submitting the job. If qadapter is specified then job_cmd is ignored wait: whther to wait for the job to finish. If the job is being submitted to the queue then there is no need for waiting turn_knobs: an ordered dictionary of parmaters and the corresponding values mappings_override: override symbol mapping in potcar eg:- {'S':'S_sv'} functional: exchange-correlation functional Note: input structure if needed will be obtained from the provided poscar object """ self.name = datetime.datetime.now().isoformat() self.system = system self.parent_job_dir = os.path.abspath(parent_job_dir) self.job_dir = job_dir self.incar = incar self.poscar = poscar self.potcar = potcar if poscar: self.potcar = Potcar(symbols=poscar.site_symbols, functional=functional) self.kpoints = kpoints if incar: self.incar_orig = incar.as_dict() if poscar: self.poscar_orig = poscar.as_dict() if self.potcar: self.potcar_orig = self.potcar.as_dict() if kpoints: self.kpoints_orig = kpoints.as_dict() self.qadapter = qadapter self.job_dir_list = [] self.jobs = [] self.job_ids = [] self.handlers = [] self.job_cmd = job_cmd self.n_atoms = 0 self.turn_knobs = turn_knobs self.response_to_knobs = {} self.sorted_response_to_knobs = {} for k, v in turn_knobs.items(): self.response_to_knobs[k] = {} self.sorted_response_to_knobs[k] = {} self.is_matrix = is_matrix self.Grid_type = Grid_type self.wait = wait self.cal_log = [] self.mappings_override = mappings_override self.functional = functional self.checkpoint_file = checkpoint_file if cal_logger: self.logger = cal_logger else: self.logger = logger
def test_write(self): tempfname = "POTCAR.testing" self.potcar.write_file(tempfname) p = Potcar.from_file(tempfname) self.assertEqual(p.symbols, self.potcar.symbols) os.remove(tempfname)
def test_to_from_dict(self): d = self.potcar.as_dict() potcar = Potcar.from_dict(d) self.assertEqual(potcar.symbols, ["Fe", "P", "O"])
def assimilate(self, path): files = os.listdir(path) try: files_to_parse = {} if "relax1" in files and "relax2" in files: for filename in ("INCAR", "POTCAR", "POSCAR"): search_str = os.path.join(path, "relax1", filename + "*") files_to_parse[filename] = glob.glob(search_str)[0] for filename in ("CONTCAR", "OSZICAR"): search_str = os.path.join(path, "relax2", filename + "*") files_to_parse[filename] = glob.glob(search_str)[-1] else: for filename in ( "INCAR", "POTCAR", "CONTCAR", "OSZICAR", "POSCAR", "DYNMAT" ): files = glob.glob(os.path.join(path, filename + "*")) if len(files) < 1: continue if len(files) == 1 or filename == "INCAR" or \ filename == "POTCAR" or filename == "DYNMAT": files_to_parse[filename] = files[-1]\ if filename == "POTCAR" else files[0] elif len(files) > 1: """ This is a bit confusing, since there maybe be multiple steps. By default, assimilate will try to find a file simply named filename, filename.bz2, or filename.gz. Failing which it will try to get a relax2 from a custodian double relaxation style run if possible. Or else, a random file is chosen. """ for fname in files: if fnmatch.fnmatch(os.path.basename(fname), "{}(\.gz|\.bz2)*" .format(filename)): files_to_parse[filename] = fname break if fname == "POSCAR" and \ re.search("relax1", fname): files_to_parse[filename] = fname break if (fname in ("CONTCAR", "OSZICAR") and re.search("relax2", fname)): files_to_parse[filename] = fname break files_to_parse[filename] = fname poscar, contcar, incar, potcar, oszicar, dynmat = [None]*6 if 'POSCAR' in files_to_parse: poscar = Poscar.from_file(files_to_parse["POSCAR"]) if 'CONTCAR' in files_to_parse: contcar = Poscar.from_file(files_to_parse["CONTCAR"]) if 'INCAR' in files_to_parse: incar = Incar.from_file(files_to_parse["INCAR"]) if 'POTCAR' in files_to_parse: potcar = Potcar.from_file(files_to_parse["POTCAR"]) if 'OSZICAR' in files_to_parse: oszicar = Oszicar(files_to_parse["OSZICAR"]) if 'DYNMAT' in files_to_parse: dynmat = Dynmat(files_to_parse["DYNMAT"]) param = {"hubbards":{}} if poscar is not None and incar is not None and "LDAUU" in incar: param["hubbards"] = dict(zip(poscar.site_symbols, incar["LDAUU"])) param["is_hubbard"] = ( incar.get("LDAU", False) and sum(param["hubbards"].values()) > 0 ) if incar is not None else False param["run_type"] = None if incar is not None: param["run_type"] = "GGA+U" if param["is_hubbard"] else "GGA" param["history"] = _get_transformation_history(path) param["potcar_spec"] = potcar.spec if potcar is not None else None energy = oszicar.final_energy if oszicar is not None else 1e10 structure = contcar.structure if contcar is not None\ else poscar.structure initial_vol = poscar.structure.volume if poscar is not None else \ None final_vol = contcar.structure.volume if contcar is not None else \ None delta_volume = None if initial_vol is not None and final_vol is not None: delta_volume = (final_vol / initial_vol - 1) data = {"filename": path, "delta_volume": delta_volume} if dynmat is not None: data['phonon_frequencies'] = dynmat.get_phonon_frequencies() if self._inc_structure: entry = ComputedStructureEntry( structure, energy, parameters=param, data=data ) else: entry = ComputedEntry( structure.composition, energy, parameters=param, data=data ) return entry except Exception as ex: logger.debug("error in {}: {}".format(path, ex)) return None
def setUp(self): self.potcar = Potcar.from_file(test_dir+"/POTCAR") self.zval_dict = {'Ba': 10.0, 'Ti': 10.0, 'O': 6.0} self.ions = ions self.structures = structures
def __init__(self, incar, poscar, potcar, kpoints, system=None, is_matrix=False, Grid_type='A', parent_job_dir='.', job_dir='Job', qadapter=None, job_cmd='qsub', wait=True, mappings_override=None, functional="PBE", database=None, magnetism=None, mag_init=None, reuse=None, reuse_override=None, reuse_incar=None, solvation=None, turn_knobs=OrderedDict([('ENCUT', []), ('KPOINTS', [])]), checkpoint_file=None, finer_kpoint=None, cal_logger=None): """ Calibrate constructor Args: incar (Incar object): input INCAR poscar (Poscar object): input POSCAR potcar (Potcar object): input POTCAR kpoints (Kpoints object): input KPOINTS system: system info as a dictionary, slab or interface example: system={'hkl':[1,1,1], 'ligand':None}, is_matrix (bool): whether the jobs are dependent on each other Grid_type (str): kpoints grid_type parent_job_dir (str): the directory from which all the jobs are launched job_dir (str): job directory created for each job in the parent_job_dir qadapter (?): adapter for the batch system job_cmd (str): command to be used for submitting the job. If qadapter is specified then job_cmd is ignored wait (bool): whther to wait for the job to finish. If the job is being submitted to the queue then there is no need for waiting turn_knobs (dict): an ordered dictionary of parmaters and the corresponding values mappings_override (dict): override symbol mapping in potcar eg:- {'S':'S_sv'} functional (str): exchange-correlation functional database (str): A work in progress, will be a database_name.yaml file for defaults specific to a database workflow that will have defaults for INCAR: cutoff, convergence for relaxation and continuation jobs KPOINTS: for relaxation, band structure jobs POTCAR: database specific For now defaults to None, if set to 'twod' activates twod set of directives reuse (list or bool): list of filenames for reuse Eg: ['CHGCAR', 'WAVECAR'] 'CONTCAR' is copied by default and if found empty warning is issued. Use the following flag for override only if you know what you are doing 'True' for just copying the CONTCAR file reuse_override (bool): whether to override the missing CONTCAR for a reuse calc magnetism (str): specifies magnetism calculation to be used implemented are 'AntiFerroMagnetism' and 'Magntic Anisotropy Energy' solvation (bool): whether to activate a solvation job, sets LSOL=True for now Calibrate jobs represent the engine configuration of mpinterfaces, where the fuel (input file sources) and driving method (kind of calculation) are decided . The Engine itself is instrument.py which creates the input set configured in Calibrate. Current fueling methods: 1. simplest test case involving a single job: - specify the incar, kpoints, poscar, potcar (aka the VASP 4) explicitly as pymatgen objects - turn_knobs = {} , is_matrix = False 2. test case for calibration of parameters: - specify an initial configuration for the VASP 4 - specify parameters to calibrate via turn_knobs, set is_matrix = True only if number of parameters > 1 3. Database production case: (possibly most used) - specify initial configuration for the VASP 4 based on a database.yaml - specify an input.yaml that details the workflow Note: input structure if needed will be obtained from the provided poscar object """ self.name = datetime.datetime.now().isoformat() self.system = system self.parent_job_dir = os.path.abspath(parent_job_dir) self.job_dir = job_dir self.incar = incar self.poscar = poscar self.potcar = potcar if poscar: self.potcar = Potcar(symbols=poscar.site_symbols, functional=functional) self.kpoints = kpoints if incar: self.incar_orig = incar.as_dict() if poscar: self.poscar_orig = poscar.as_dict() if self.potcar: self.potcar_orig = self.potcar.as_dict() if kpoints: self.kpoints_orig = kpoints.as_dict() self.qadapter = qadapter self.job_dir_list = [] self.jobs = [] self.job_ids = [] self.handlers = [] self.job_cmd = job_cmd self.n_atoms = 0 self.turn_knobs = turn_knobs self.response_to_knobs = {} self.sorted_response_to_knobs = {} for k, v in turn_knobs.items(): self.response_to_knobs[k] = {} self.sorted_response_to_knobs[k] = {} self.is_matrix = is_matrix self.Grid_type = Grid_type self.wait = wait self.cal_log = [] self.mappings_override = mappings_override self.database = database self.magnetism = magnetism self.mag_init = mag_init self.solvation = solvation self.reuse = reuse self.reuse_incar = reuse_incar self.reuse_override = reuse_override self.reuse_paths = None # list object communicated to instrument self.finer_kpoint = finer_kpoint self.functional = functional self.checkpoint_file = checkpoint_file if cal_logger: self.logger = cal_logger else: self.logger = logger
class Calibrate(PMGSONable): """ The base class for creating vasp work flows for calibrating the input parameters for different systems A wrapper around Custodian """ LOG_FILE = "calibrate.json" def __init__(self, incar, poscar, potcar, kpoints, system=None, is_matrix = False, Grid_type = 'A', parent_job_dir='.',job_dir='Job', qadapter=None, job_cmd='qsub', wait=True, mappings_override = None, functional="PBE", turn_knobs=OrderedDict( [ ('ENCUT',[]), ('KPOINTS',[])] ), checkpoint_file=None, cal_logger=None): """ Calibrate constructor Args: incar (Incar object): input INCAR poscar (Poscar object): input POSCAR potcar (Potcar object): input POTCAR kpoints: input KPOINTS system: system info as a dictionary, slab or interface example: system={'hkl':[1,1,1], 'ligand':None}, is_matrix (bool): whether the jobs are dependent on each other Grid_type: kpoints grid_type parent_job_dir: the directory from which all the jobs are launched job_dir: job directory qadapter: adapter for the batch system job_cmd: command to be used for submitting the job. If qadapter is specified then job_cmd is ignored wait: whther to wait for the job to finish. If the job is being submitted to the queue then there is no need for waiting turn_knobs: an ordered dictionary of parmaters and the corresponding values mappings_override: override symbol mapping in potcar eg:- {'S':'S_sv'} functional: exchange-correlation functional Note: input structure if needed will be obtained from the provided poscar object """ self.name = datetime.datetime.now().isoformat() self.system = system self.parent_job_dir = os.path.abspath(parent_job_dir) self.job_dir = job_dir self.incar = incar self.poscar = poscar self.potcar = potcar if poscar: self.potcar = Potcar(symbols=poscar.site_symbols, functional=functional) self.kpoints = kpoints if incar: self.incar_orig = incar.as_dict() if poscar: self.poscar_orig = poscar.as_dict() if self.potcar: self.potcar_orig = self.potcar.as_dict() if kpoints: self.kpoints_orig = kpoints.as_dict() self.qadapter = qadapter self.job_dir_list = [] self.jobs = [] self.job_ids = [] self.handlers = [] self.job_cmd = job_cmd self.n_atoms = 0 self.turn_knobs = turn_knobs self.response_to_knobs = {} self.sorted_response_to_knobs = {} for k, v in turn_knobs.items(): self.response_to_knobs[k] = {} self.sorted_response_to_knobs[k] = {} self.is_matrix = is_matrix self.Grid_type = Grid_type self.wait = wait self.cal_log = [] self.mappings_override = mappings_override self.functional = functional self.checkpoint_file = checkpoint_file if cal_logger: self.logger = cal_logger else: self.logger = logger def setup(self): """ set up the jobs for the given turn_knobs dict is_matrix = True implies that the params in the dict are interrelated. Otherwise calcs corresponding to each dict key is independent """ if self.is_matrix: self.setup_matrix_job() else: self._setup() def _setup(self, turn_knobs=None): """ invoke the set up methods corresponding to the dict keys any key other than KPOINTS, VOLUME and POTCAR are treated as INCAR parameters Args: turn_knobs: knobs aka paramters to be tuned Note: poscar jobs setup through the VOLUME is only for backward compatibility, use POSCAR key in the turn_knobs to tune poscars """ if turn_knobs is None: turn_knobs = self.turn_knobs if any(turn_knobs.values()): for k, v in turn_knobs.items(): if k == 'KPOINTS' and v: self.setup_kpoints_jobs(kpoints_list = v) elif k == 'VOLUME' and v: self.setup_poscar_jobs(scale_list = v) elif k == 'POTCAR' and v: self.setup_potcar_jobs(mappings = v) elif k == 'POSCAR' and v: self.setup_poscar_jobs(poscar_list=v) else: self.setup_incar_jobs(k, v) else: self.logger.warn('knobs not set, running a single job') self.add_job(name='single_job', job_dir=self.job_dir) def setup_matrix_job(self): """ set up jobs where the dict keys are interrelated mind: its an ordered dict, the order in which the keys are specified determines the nested directory structure """ orig_job_dir = self.job_dir job_dir = self.job_dir n_items = len(self.turn_knobs.items()) keys = self.turn_knobs.keys() self._setup(turn_knobs=dict([(keys[0], self.turn_knobs[keys[0]])])) self.recursive_jobs(n_items, keys, 0) #restore self.job_dir = orig_job_dir def recursive_jobs(self,n, keys, i): """ recursively setup the jobs: used by setup_matrix_job Args: n: total number of knobs aka parameters to be tuned keys: list of knobs i.e parameter names i: ith knob """ job_dir = self.job_dir + os.sep + self.key_to_name(keys[i]) if i == n-1 and i != 0: for val in self.turn_knobs[keys[i]]: self.job_dir = job_dir + os.sep + self.val_to_name(val) self.logger.info('setting jobs in the directory: '+self.job_dir) self._setup(turn_knobs=dict([(keys[i], [val])])) self.add_job(name=job_dir, job_dir=self.job_dir) else: for val in self.turn_knobs[keys[i]]: self.job_dir = job_dir + os.sep + self.val_to_name(val) self.logger.info('setting jobs in the directory: '+self.job_dir) self._setup(turn_knobs=dict([(keys[i], [val])])) self.recursive_jobs(n,keys,i+1) def key_to_name(self, key): """ convenient string mapping for the keys in the turn_knobs dict Args: key: key to the knob dict Returns: an appropriate string representation of the key so that the name doesnt clash with the filenames """ if key == 'KPOINTS': return 'KPTS' elif key == 'POTCAR_map' or key == 'POTCAR_functional': return 'POT' elif key == 'POSCAR': return 'POS' else: return key def val_to_name(self, val): """ convert a value to a string so that it can be used for naming the job directory the decimal points in floats are replaced with underscore character if the value is of type list, kpoint_to_name method is used since only kpoint values are expected to be of type list if the values is of type dict then potcar_to_name method is invoked Args: val: knob value to be converted into an appropriate string representation Returns: a string filename for the value """ if type(val) == float: return re.sub('\.','_',str(val)) elif type(val) == list: return self.kpoint_to_name(val, 'M') elif type(val) == dict: return self.potcar_to_name(val) elif isinstance(val, Poscar): return val.comment else: return str(val) def kpoint_to_name(self, kpoint, grid_type): """ get a string representation for the given kpoint Args: kpoint: an iterable grid_type: grid_type used for the KPOINTS Returns: string representation for kpoint eg: Monkhorst Pack 2 2 2 will be named 2x2x2 """ if grid_type == 'M' or grid_type == 'G': return str(kpoint[0]) + 'x' + str(kpoint[1]) + 'x' \ + str(kpoint[2]) else: return str(kpoint) def potcar_to_name(self, mapping,functional): """ convert a symbol mapping and functional to a name that can be used for setting up the potcar jobs Args: mapping: example:- if mapping = {'Pt':'Pt_pv', 'Si':'Si_GW'} then the name will be PBE_Pt_pv_Si_GW with self.functional="PBE" Returns: string """ if mapping: l = [v for k,v in mapping.items()] return '_'.join(self.functional, l) elif functional: return '_'.join(functional) else: return '_'.join(functional,l) def set_incar(self, param, val): """ set the incar paramter, param = val """ self.incar[param] = val def set_poscar(self, scale=None, poscar=None): """ perturbs given structure by volume scaling factor or takes user defined variants of Poscar Args: scale : Volume Scaling parameter poscar : Poscar object of user defined structure set the poscar: volume scaled by the scale factor """ if scale is not None: structure = Poscar.from_dict(self.poscar_orig).structure volume = structure.volume structure.scale_lattice(scale *volume) self.poscar = Poscar(structure) elif poscar is not None: self.poscar = poscar def set_potcar(self, mapping=None, functional='PBE'): """ set the potcar: symbol to potcar type mapping """ symbols = self.poscar.site_symbols mapped_symbols = [] if mapping: for sym in symbols: mapped_symbols.append(mapping[sym]) elif self.mappings_override: for sym in symbols: if sym in self.mappings_override.keys(): mapped_symbols.append(self.mappings_override[sym]) else: mapped_symbols.append(sym) else: mapped_symbols = symbols if functional: func=functional else: func=self.functional self.potcar = Potcar(symbols=mapped_symbols, functional=func) def set_kpoints(self, kpoint): """ set the kpoint """ if self.Grid_type == 'M': self.kpoints = Kpoints.monkhorst_automatic(kpts = kpoint) elif self.Grid_type == 'A': self.kpoints = Kpoints.automatic(subdivisions = kpoint) elif self.Grid_type == 'G': self.kpoints = Kpoints.gamma_automatic(kpts = kpoint) elif self.Grid_type == '3DD': self.kpoints = Kpoints.automatic_density_by_vol(structure=\ self.poscar.structure, kppvol=kpoint) elif self.Grid_type == 'band': self.kpoints = Kpoints.automatic_linemode(divisions=kpoint,\ ibz=HighSymmKpath(self.poscar.structure)) name = self.kpoint_to_name(kpoint, self.Grid_type) job_dir = self.job_dir +os.sep+ self.key_to_name('KPOINTS') \ + os.sep + name return job_dir def setup_incar_jobs(self, param, val_list): """ set up incar jobs,, calls set_incar to set the value to param Args: param: Name of INCAR parameter val_list: List of values to vary for the param """ if val_list: for val in val_list: self.logger.info('setting INCAR parameter ' + param + ' = '\ + str(val)) self.set_incar(param, val) if not self.is_matrix: job_dir = self.job_dir+ os.sep + \ param + os.sep + self.val_to_name(val) self.add_job(name=job_dir, job_dir=job_dir) else: self.logger.warn('incar list empty') def setup_kpoints_jobs(self, kpoints_list = None): """ setup the kpoint jobs """ if kpoints_list: for kpoint in kpoints_list: job_dir = self.set_kpoints(kpoint) if not self.is_matrix: self.add_job(name=job_dir, job_dir=job_dir) else: self.logger.warn('kpoints_list empty') def setup_poscar_jobs(self, scale_list=None, poscar_list=None): """ for scaling the latice vectors of the original structure, scale_list is volume scaling factor list """ if scale_list: for scale in scale_list: self.set_poscar(scale=scale) self.set_potcar() job_dir = self.job_dir+ os.sep + 'POS' +\ os.sep + 'VOLUME_'+str(scale) if not self.is_matrix: self.add_job(name=job_dir, job_dir=job_dir) elif poscar_list: for poscar in poscar_list: self.set_poscar(poscar=poscar) self.set_potcar() poskey = str(poscar.structure.composition.reduced_formula) \ + '_'+ str(int(poscar.structure.lattice.volume)) \ + '_' + ''.join((poscar.comment).split()) job_dir = self.job_dir+ os.sep +'POS' +\ os.sep + poskey if not self.is_matrix: self.add_job(name=job_dir, job_dir=job_dir) def setup_potcar_jobs(self, mappings,functional_list): """ take a list of symbol mappings and setup the potcar jobs """ if functional_list: for func in functional_list: self.set_potcar(functional=func) if not self.is_matrix: job_dir = self.job_dir+ os.sep \ + self.key_to_name('POTCAR') \ + os.sep + self.potcar_to_name(func) self.add_job(name=job_dir, job_dir=job_dir) elif mappings: for mapping in mappings: self.set_potcar(mapping) if not self.is_matrix: job_dir = self.job_dir+ os.sep \ + self.key_to_name('POTCAR') \ + os.sep + self.potcar_to_name(mapping) self.add_job(name=job_dir, job_dir=job_dir) def add_job(self, name='noname', job_dir='.'): """ add a single job using the current incar, poscar, potcar and kpoints """ vis = MPINTVaspInputSet(name, self.incar, self.poscar, self.potcar, self.kpoints, self.qadapter, vis_logger=self.logger) #the job command can be overrridden in the run method job = MPINTVaspJob(self.job_cmd, name=name, final = True, parent_job_dir=self.parent_job_dir, job_dir=job_dir, vis=vis, wait=self.wait, vjob_logger = self.logger) self.job_dir_list.append(os.path.abspath(job_dir)) self.jobs.append(job) def run(self, job_cmd=None): """ run the vasp jobs through custodian if the job list is empty, run a single job with the initial input set """ for j in self.jobs: if job_cmd is not None: j.job_cmd = job_cmd else: j.job_cmd = self.job_cmd c_params = {'jobs': [j.as_dict() for j in self.jobs], 'handlers': [h.as_dict() for h in self.handlers], 'max_errors': 5} c = Custodian(self.handlers, self.jobs, max_errors=5) c.run() for j in self.jobs: self.cal_log.append({"job": j.as_dict(), 'job_id': j.job_id, "corrections": [], 'final_energy': None}) self.job_ids.append(j.job_id) if self.checkpoint_file: dumpfn(self.cal_log, self.checkpoint_file, cls=MontyEncoder, indent=4) else: dumpfn(self.cal_log, Calibrate.LOG_FILE, cls=MontyEncoder, indent=4) def as_dict(self): qadapter = None system = None if self.qadapter: qadapter = self.qadapter.to_dict() if self.system is not None: system = self.system d = dict(incar=self.incar.as_dict(), poscar=self.poscar.as_dict(), potcar=self.potcar.as_dict(), kpoints=self.kpoints.as_dict(), system=system, is_matrix = self.is_matrix, Grid_type = self.Grid_type, parent_job_dir=self.parent_job_dir, job_dir=self.job_dir, qadapter=qadapter, job_cmd=self.job_cmd, wait=self.wait, turn_knobs=self.turn_knobs, job_dir_list=self.job_dir_list, job_ids = self.job_ids) d["@module"] = self.__class__.__module__ d["@class"] = self.__class__.__name__ #d['calibrate'] = self.__class__.__name__ return d @classmethod def from_dict(cls, d): incar = Incar.from_dict(d["incar"]) poscar = Poscar.from_dict(d["poscar"]) potcar = Potcar.from_dict(d["potcar"]) kpoints = Kpoints.from_dict(d["kpoints"]) cal = Calibrate(incar, poscar, potcar, kpoints, system=d["system"], is_matrix = d["is_matrix"], Grid_type = d["Grid_type"], parent_job_dir=d["parent_job_dir"], job_dir=d["job_dir"], qadapter=d.get("qadapter"), job_cmd=d["job_cmd"], wait=d["wait"], turn_knobs=d["turn_knobs"]) cal.job_dir_list = d["job_dir_list"] cal.job_ids = d["job_ids"] return cal
def assimilate(self, path): files = os.listdir(path) try: files_to_parse = {} if "relax1" in files and "relax2" in files: for filename in ("INCAR", "POTCAR", "POSCAR"): search_str = os.path.join(path, "relax1", filename + "*") files_to_parse[filename] = glob.glob(search_str)[0] for filename in ("CONTCAR", "OSZICAR"): search_str = os.path.join(path, "relax2", filename + "*") files_to_parse[filename] = glob.glob(search_str)[-1] else: for filename in ( "INCAR", "POTCAR", "CONTCAR", "OSZICAR", "POSCAR", "DYNMAT" ): files = sorted(glob.glob(os.path.join(path, filename + "*"))) if len(files) < 1: continue if len(files) == 1 or filename == "INCAR" or \ filename == "POTCAR" or filename == "DYNMAT": files_to_parse[filename] = files[-1]\ if filename == "POTCAR" else files[0] elif len(files) > 1: # Since multiple files are ambiguous, we will always # use the first one for POSCAR and the last one # alphabetically for CONTCAR and OSZICAR. if filename == "POSCAR": files_to_parse[filename] = files[0] else: files_to_parse[filename] = files[-1] warnings.warn( "%d files found. %s is being parsed." % (len(files), files_to_parse[filename])) poscar, contcar, incar, potcar, oszicar, dynmat = [None]*6 if 'POSCAR' in files_to_parse: poscar = Poscar.from_file(files_to_parse["POSCAR"]) if 'CONTCAR' in files_to_parse: contcar = Poscar.from_file(files_to_parse["CONTCAR"]) if 'INCAR' in files_to_parse: incar = Incar.from_file(files_to_parse["INCAR"]) if 'POTCAR' in files_to_parse: potcar = Potcar.from_file(files_to_parse["POTCAR"]) if 'OSZICAR' in files_to_parse: oszicar = Oszicar(files_to_parse["OSZICAR"]) if 'DYNMAT' in files_to_parse: dynmat = Dynmat(files_to_parse["DYNMAT"]) param = {"hubbards":{}} if poscar is not None and incar is not None and "LDAUU" in incar: param["hubbards"] = dict(zip(poscar.site_symbols, incar["LDAUU"])) param["is_hubbard"] = ( incar.get("LDAU", False) and sum(param["hubbards"].values()) > 0 ) if incar is not None else False param["run_type"] = None if incar is not None: param["run_type"] = "GGA+U" if param["is_hubbard"] else "GGA" # param["history"] = _get_transformation_history(path) param["potcar_spec"] = potcar.spec if potcar is not None else None energy = oszicar.final_energy if oszicar is not None else 1e10 structure = contcar.structure if contcar is not None\ else poscar.structure initial_vol = poscar.structure.volume if poscar is not None else \ None final_vol = contcar.structure.volume if contcar is not None else \ None delta_volume = None if initial_vol is not None and final_vol is not None: delta_volume = (final_vol / initial_vol - 1) data = {"filename": path, "delta_volume": delta_volume} if dynmat is not None: data['phonon_frequencies'] = dynmat.get_phonon_frequencies() if self._inc_structure: entry = ComputedStructureEntry( structure, energy, parameters=param, data=data ) else: entry = ComputedEntry( structure.composition, energy, parameters=param, data=data ) return entry except Exception as ex: logger.debug("error in {}: {}".format(path, ex)) return None
def test_write(self): tempfname = Path("POTCAR.testing") self.potcar.write_file(tempfname) p = Potcar.from_file(tempfname) self.assertEqual(p.symbols, self.potcar.symbols) tempfname.unlink()
class Calibrate(MSONable): """ The base class for creating vasp work flows for calibrating the input parameters for different systems A wrapper around Custodian """ LOG_FILE = "calibrate.json" def __init__(self, incar, poscar, potcar, kpoints, system=None, is_matrix=False, Grid_type='A', parent_job_dir='.', job_dir='Job', qadapter=None, job_cmd='qsub', wait=True, mappings_override=None, functional="PBE", database=None, magnetism=None, mag_init=None, reuse=None, reuse_override=None, reuse_incar=None, solvation=None, turn_knobs=OrderedDict([('ENCUT', []), ('KPOINTS', [])]), checkpoint_file=None, finer_kpoint=None, cal_logger=None): """ Calibrate constructor Args: incar (Incar object): input INCAR poscar (Poscar object): input POSCAR potcar (Potcar object): input POTCAR kpoints (Kpoints object): input KPOINTS system: system info as a dictionary, slab or interface example: system={'hkl':[1,1,1], 'ligand':None}, is_matrix (bool): whether the jobs are dependent on each other Grid_type (str): kpoints grid_type parent_job_dir (str): the directory from which all the jobs are launched job_dir (str): job directory created for each job in the parent_job_dir qadapter (?): adapter for the batch system job_cmd (str): command to be used for submitting the job. If qadapter is specified then job_cmd is ignored wait (bool): whther to wait for the job to finish. If the job is being submitted to the queue then there is no need for waiting turn_knobs (dict): an ordered dictionary of parmaters and the corresponding values mappings_override (dict): override symbol mapping in potcar eg:- {'S':'S_sv'} functional (str): exchange-correlation functional database (str): A work in progress, will be a database_name.yaml file for defaults specific to a database workflow that will have defaults for INCAR: cutoff, convergence for relaxation and continuation jobs KPOINTS: for relaxation, band structure jobs POTCAR: database specific For now defaults to None, if set to 'twod' activates twod set of directives reuse (list or bool): list of filenames for reuse Eg: ['CHGCAR', 'WAVECAR'] 'CONTCAR' is copied by default and if found empty warning is issued. Use the following flag for override only if you know what you are doing 'True' for just copying the CONTCAR file reuse_override (bool): whether to override the missing CONTCAR for a reuse calc magnetism (str): specifies magnetism calculation to be used implemented are 'AntiFerroMagnetism' and 'Magntic Anisotropy Energy' solvation (bool): whether to activate a solvation job, sets LSOL=True for now Calibrate jobs represent the engine configuration of mpinterfaces, where the fuel (input file sources) and driving method (kind of calculation) are decided . The Engine itself is instrument.py which creates the input set configured in Calibrate. Current fueling methods: 1. simplest test case involving a single job: - specify the incar, kpoints, poscar, potcar (aka the VASP 4) explicitly as pymatgen objects - turn_knobs = {} , is_matrix = False 2. test case for calibration of parameters: - specify an initial configuration for the VASP 4 - specify parameters to calibrate via turn_knobs, set is_matrix = True only if number of parameters > 1 3. Database production case: (possibly most used) - specify initial configuration for the VASP 4 based on a database.yaml - specify an input.yaml that details the workflow Note: input structure if needed will be obtained from the provided poscar object """ self.name = datetime.datetime.now().isoformat() self.system = system self.parent_job_dir = os.path.abspath(parent_job_dir) self.job_dir = job_dir self.incar = incar self.poscar = poscar self.potcar = potcar if poscar: self.potcar = Potcar(symbols=poscar.site_symbols, functional=functional) self.kpoints = kpoints if incar: self.incar_orig = incar.as_dict() if poscar: self.poscar_orig = poscar.as_dict() if self.potcar: self.potcar_orig = self.potcar.as_dict() if kpoints: self.kpoints_orig = kpoints.as_dict() self.qadapter = qadapter self.job_dir_list = [] self.jobs = [] self.job_ids = [] self.handlers = [] self.job_cmd = job_cmd self.n_atoms = 0 self.turn_knobs = turn_knobs self.response_to_knobs = {} self.sorted_response_to_knobs = {} for k, v in turn_knobs.items(): self.response_to_knobs[k] = {} self.sorted_response_to_knobs[k] = {} self.is_matrix = is_matrix self.Grid_type = Grid_type self.wait = wait self.cal_log = [] self.mappings_override = mappings_override self.database = database self.magnetism = magnetism self.mag_init = mag_init self.solvation = solvation self.reuse = reuse self.reuse_incar = reuse_incar self.reuse_override = reuse_override self.reuse_paths = None # list object communicated to instrument self.finer_kpoint = finer_kpoint self.functional = functional self.checkpoint_file = checkpoint_file if cal_logger: self.logger = cal_logger else: self.logger = logger def setup(self): """ set up the jobs for the given turn_knobs dict is_matrix = True implies that the params in the dict are interrelated. Otherwise calcs corresponding to each dict key is independent """ if self.is_matrix: self.setup_matrix_job() else: self._setup() def _setup(self, turn_knobs=None): """ invoke the set up methods corresponding to the dict keys any key other than KPOINTS, VOLUME and POTCAR are treated as INCAR parameters Args: turn_knobs: knobs aka paramters to be tuned Note: poscar jobs setup through the VOLUME is only for backward compatibility, use POSCAR key in the turn_knobs to tune poscars """ if turn_knobs is None: turn_knobs = self.turn_knobs if any(turn_knobs.values()): for k, v in turn_knobs.items(): if k == 'KPOINTS' and v: self.setup_kpoints_jobs(kpoints_list=v) elif k == 'VOLUME' and v: self.setup_poscar_jobs(scale_list=v) elif k == 'POTCAR' and v: self.setup_potcar_jobs(mappings=v, functional_list=None) elif k == 'POTCAR_functional' and v: self.setup_potcar_jobs(mappings=None, functional_list=v) elif k == 'POSCAR' and v: self.setup_poscar_jobs(poscar_list=v) else: self.setup_incar_jobs(k, v) else: self.logger.warn('knobs not set, running a single job') self.add_job(name='single_job', job_dir=self.job_dir) def setup_matrix_job(self): """ set up jobs where the dict keys are interrelated mind: its an ordered dict, the order in which the keys are specified determines the nested directory structure """ orig_job_dir = self.job_dir job_dir = self.job_dir n_items = len(list(self.turn_knobs.items())) keys = list(self.turn_knobs.keys()) self._setup(turn_knobs=dict([(keys[0], self.turn_knobs[keys[0]])])) self.recursive_jobs(n_items, keys, 0) # restore self.job_dir = orig_job_dir def recursive_jobs(self, n, keys, i): """ recursively setup the jobs: used by setup_matrix_job Args: n: total number of knobs aka parameters to be tuned keys: list of knobs i.e parameter names i: ith knob """ #### Testing #### # Orig # job_dir = self.job_dir + os.sep + self.key_to_name(keys[i]) job_dir = '__'.join( [self.job_dir.split('/')[-1], self.key_to_name(keys[i])]) #### Testing #### if i == n - 1 and i != 0: for val in self.turn_knobs[keys[i]]: ## ## self.job_dir = job_dir + os.sep + self.val_to_name(val) self.job_dir = '__'.join([job_dir, self.val_to_name(val)]) self.logger.info( 'setting jobs in the directory: ' + self.job_dir) self._setup(turn_knobs=dict([(keys[i], [val])])) self.add_job(name=job_dir, job_dir=self.job_dir) else: for val in self.turn_knobs[keys[i]]: ## ## self.job_dir = job_dir + os.sep + self.val_to_name(val) self.job_dir = '__'.join([job_dir, self.val_to_name(val)]) self.logger.info( 'setting jobs in the directory: ' + self.job_dir) self._setup(turn_knobs=dict([(keys[i], [val])])) self.recursive_jobs(n, keys, i + 1) def key_to_name(self, key): """ convenient string mapping for the keys in the turn_knobs dict Args: key: key to the knob dict Returns: an appropriate string representation of the key so that the name doesnt clash with the filenames """ if key == 'KPOINTS': return 'KPTS' elif key == 'POTCAR_map' or key == 'POTCAR_functional': return 'POT' elif key == 'POSCAR': return 'POS' else: return key def val_to_name(self, val): """ convert a value to a string so that it can be used for naming the job directory the decimal points in floats are replaced with underscore character if the value is of type list, kpoint_to_name method is used since only kpoint values are expected to be of type list if the values is of type dict then potcar_to_name method is invoked Args: val: knob value to be converted into an appropriate string representation Returns: a string filename for the value """ if isinstance(val, float): return re.sub('\.', '_', str(val)) elif isinstance(val, list): return self.kpoint_to_name(val, 'M') elif isinstance(val, dict): return self.potcar_to_name(mapping=val) elif isinstance(val, Poscar): name = str(val.structure.composition.reduced_formula) \ + '_' + str(int(val.structure.lattice.volume)) \ + '_' + ''.join((val.comment).split()) return name.replace('\\', '_').replace('(', '_').replace(')', '_') else: return str(val) def kpoint_to_name(self, kpoint, grid_type): """ get a string representation for the given kpoint Args: kpoint: an iterable grid_type: grid_type used for the KPOINTS Returns: string representation for kpoint eg: Monkhorst Pack 2 2 2 will be named 2x2x2 """ if grid_type == 'M' or grid_type == 'G': kpoint = [str(k).replace('.', '_') for k in kpoint] return 'x'.join(kpoint) else: return str(kpoint) def potcar_to_name(self, mapping=None, functional=None): """ convert a symbol mapping and functional to a name that can be used for setting up the potcar jobs Args: mapping: example:- if mapping = {'Pt':'Pt_pv', 'Si':'Si_GW'} then the name will be PBE_Pt_pv_Si_GW with self.functional="PBE" Returns: string """ if mapping: l = [v for k, v in mapping.items()] return '_'.join(l) elif functional: return '_'.join(functional) else: return '_'.join(self.functional) def set_incar(self, param, val): """ set the incar paramter, param = val """ self.incar[param] = val def set_poscar(self, scale=None, poscar=None): """ perturbs given structure by volume scaling factor or takes user defined variants of Poscar Args: scale : Volume Scaling parameter poscar : Poscar object of user defined structure set the poscar: volume scaled by the scale factor """ if scale is not None: structure = Poscar.from_dict(self.poscar_orig).structure volume = structure.volume structure.scale_lattice(scale * volume) self.poscar = Poscar(structure) elif poscar is not None: self.poscar = poscar def set_potcar(self, mapping=None, functional='PBE'): """ set the potcar: symbol to potcar type mapping """ symbols = self.poscar.site_symbols mapped_symbols = [] if mapping: for sym in symbols: mapped_symbols.append(mapping[sym]) elif self.mappings_override: for sym in symbols: if sym in list(self.mappings_override.keys()): mapped_symbols.append(self.mappings_override[sym]) else: mapped_symbols.append(sym) else: mapped_symbols = symbols if functional: func = functional else: func = self.functional self.potcar = Potcar(symbols=mapped_symbols, functional=func) def set_kpoints(self, kpoint=None, poscar=None, ibzkpth=None): """ set the kpoint """ # useful to check if a poscar is supplied from setup_poscar_jobs (most often the case) # or this is a single poscar use case if not poscar: poscar = self.poscar # splitting into two if elif branches means fewer if statements to check on # a run # Most general method of setting the k-points for # different grid types # NOTE: requires that at least one k-points value be passed # as a turn - knobs list value # this is not true for values that may be caculated out of # a database # use this part only if this is a non-database run for example # for k-points calibration if not self.database: if self.Grid_type == 'M': self.kpoints = Kpoints.monkhorst_automatic(kpts=kpoint) elif self.Grid_type == 'A': self.kpoints = Kpoints.automatic(subdivisions=kpoint) elif self.Grid_type == 'G': self.kpoints = Kpoints.gamma_automatic(kpts=kpoint) elif self.Grid_type == '3D_vol': self.kpoints = Kpoints.automatic_density_by_vol(structure=poscar.structure, kppvol=kpoint) elif self.Grid_type == 'bulk_bands_pbe': self.kpoints = Kpoints.automatic_linemode(divisions=kpoint, ibz=HighSymmKpath( poscar.structure)) elif self.Grid_type == 'D': self.kpoints = Kpoints.automatic_density(structure=poscar.structure,kppa=kpoint) elif self.Grid_type == 'Finer_G_Mesh': # kpoint is the scaling factor and self.kpoints is the old kpoint mesh self.logger.info('Setting Finer G Mesh for {0} by scale {1}'.format(kpoint, self.finer_kpoint)) self.kpoints = Kpoints.gamma_automatic(kpts = \ [i * self.finer_kpoint for i in kpoint]) self.logger.info('Finished scaling operation of k-mesh') # applicable for database runs # future constructs or settinsg can be activated via a yaml file # database yaml file or better still the input deck from its speification # decides what combination of input calibrate constructor settings to use # one of them being the grid_type tag elif self.database == 'twod': # set of kpoints settings according to the 2D database profile # the actual settings of k-points density # will in future come from any database input file set if self.Grid_type == 'hse_bands_2D_prep': kpoint_dict = Kpoints.automatic_gamma_density(poscar.structure, 200).as_dict() kpoint_dict['kpoints'][0][2] = 1 # remove z kpoints self.kpoints = Kpoints.from_dict(kpoint_dict) elif self.Grid_type == 'hse_bands_2D': # can at most return the path to the correct kpoints file # needs kpoints to be written out in instrument in a different way # not using the Kpoints object self.kpoints = get_2D_hse_kpoints(poscar.structure, ibzkpth) elif self.Grid_type == 'bands_2D': kpoint_dict = Kpoints.automatic_linemode(divisions=20, ibz=HighSymmKpath(poscar.structure)).as_dict() self.kpoints = Kpoints.from_dict(kpoint_dict) elif self.Grid_type == 'relax_2D': # general relaxation settings for 2D kpoint_dict = Kpoints.automatic_gamma_density(poscar.structure, 1000).as_dict() kpoint_dict['kpoints'][0][2] = 1 self.kpoints = Kpoints.from_dict(kpoint_dict) elif self.Grid_type == 'relax_3D': # general relaxation settings for 3D kpoint_dict = Kpoints.automatic_gamma_density( poscar.structure, 1000) self.kpoints = Kpoints.from_dict(kpoint_dict) def setup_incar_jobs(self, param, val_list): """ set up incar jobs,, calls set_incar to set the value to param Args: param: Name of INCAR parameter val_list: List of values to vary for the param """ if val_list != ['2D_default']: for val in val_list: self.logger.info('setting INCAR parameter ' + param + ' = ' + str(val)) self.set_incar(param, val) if not self.is_matrix: job_dir = self.job_dir + os.sep + \ param + os.sep + self.val_to_name(val) self.add_job(name=job_dir, job_dir=job_dir) else: self.logger.warn('incar list empty') def setup_kpoints_jobs(self, kpoints_list=None): """ setup the kpoint jobs """ if kpoints_list: for kpoint in kpoints_list: self.set_kpoints(kpoint) if not self.is_matrix: job_dir = self.job_dir + os.sep + self.key_to_name( 'KPOINTS') \ + os.sep + self.kpoint_to_name(kpoint, self.Grid_type) self.add_job(name=job_dir, job_dir=job_dir) else: self.logger.warn('kpoints_list empty') def setup_poscar_jobs(self, scale_list=None, poscar_list=None): """ for scaling the latice vectors of the original structure, scale_list is volume scaling factor list """ if scale_list: for scale in scale_list: self.set_poscar(scale=scale) self.set_potcar() if not self.is_matrix: job_dir = self.job_dir + os.sep + 'POS' + \ os.sep + 'VOLUME_' + str(scale) self.add_job(name=job_dir, job_dir=job_dir) elif poscar_list: for pos in poscar_list: # if it is a twod_database run or any general standard database run, # the incar, kpoints and potcar follow a standard input set # which will be activated by the twod_database tag set to true # NOTE: this implementation means that the first turn_knobs tag # needs to be the poscar objects list # the database tag will be set to the name of the yaml file with the # standard input deck definition for that database # this incar dict provided as the init can be general format # based on the chosen functional, cutoff # so self.incar is a vdW incar for re-relaxation in vdW, gga for every # other calculation or LDA+U for LSDA+U calculations incar_dict = self.incar.as_dict() if self.reuse: # if this is a true list minimally, ['CONTCAR'] # it is to be ensured that the poscar list is a # list of paths as opposed to list of poscar objects by the turn knobs # values # Here pos is the path and r in each self.reuse is the name of the file(s) # to be reused # in a reuse calculation the following are possible: # update incar (Solvation calculations) or reset incar (HSE calculations) # reset kpoints file with IBZKPT # copy a CHGCAR or WAVECAR or both perhaps try: # first setup of POSCAR initial, INCAR, KPOINTS poscar = Poscar.from_file(pos + os.sep + 'CONTCAR') self.logger.info('Read previous relaxed CONTCAR file from {}'. format(pos)) # check if it is KPOINTS altering job like HSE if self.Grid_type == 'hse_bands_2D_prep': # HSE prep calcualtions # reset the INCAR file with a magmom only if exists try: incar_dict = { 'MAGMOM': get_magmom_string(poscar)} except: incar_dict = {} incar_dict = get_2D_incar_hse_prep(incar_dict) self.set_kpoints(poscar=poscar) self.logger.info( 'updated input set for HSE 2D prep calcaultion') elif self.Grid_type == 'hse_bands_2D': # HSE calculation # reset the incar and kpoints file builds # on the preceding calculations (prep calculation) # IBZKPT try: incar_dict = { 'MAGMOM': get_magmom_string(poscar)} except: incar_dict = {} incar_dict = get_2D_incar_hse(incar_dict) self.set_kpoints(poscar=poscar, ibzkpth=pos + os.sep + 'IBZKPT') self.logger.info('updated input set for HSE calcaultion\ using IBZKPT from {0}'.format(pos + os.sep + 'IBZKPT')) elif self.Grid_type == 'hse_bands': # general HSE bands pass elif self.Grid_type == 'Finer_G_Mesh': self.logger.info('updating to Finer G Mesh') kpoint = Kpoints.from_file(pos+os.sep+'KPOINTS') self.set_kpoints(kpoint=kpoint.kpts[0]) else: # use the same kpoints file and build from the old # incar self.kpoints = Kpoints.from_file( pos + os.sep + 'KPOINTS') # decide on how to use incar, use same one or # update or afresh if self.reuse_incar == 'old': incar_dict = Incar.from_file( pos + os.sep + 'INCAR').as_dict() elif self.reuse_incar == 'update': # way to go for cutoff updates, convergence, etc. # but retain the old functional incar_dict.update(Incar.from_file(pos + os.sep + 'INCAR'). as_dict()) else: # use a fresh incar as specified by the init # way to go for example for LDAU or other # major removals done to INCAR # but always retain the MAGMOM if present old_incar_dict = Incar.from_file( pos + os.sep + 'INCAR').as_dict() if 'MAGMOM' in old_incar_dict.keys(): incar_dict['MAGMOM'] = old_incar_dict[ 'MAGMOM'] else: incar_dict = incar_dict if isinstance(self.reuse, list): reuse_paths = [ pos + os.sep + r for r in self.reuse] self.reuse_paths = reuse_paths # Magnetism use cases, updates to be made to the INCAR (MAE) # and poscar (AFM) # MAE and AFM if self.magnetism == 'MAE': # remove vdW tags for MAE calculations vdW_tags = ('GGA', 'AGGAC', 'LUSE_VDW', 'PARAM1', 'PARAM2') for key in vdW_tags: if key in incar_dict: del incar_dict[key] self.logger.info( 'updating input set for MAE calculation') self.mag_init = Outcar( pos + os.sep + 'OUTCAR').total_mag nbands = 2 * \ Vasprun(pos + os.sep + 'vasprun.xml').parameters['NBANDS'] # u_value = Vasprun(pos+os.sep+'vasprun.xml').incar['LDAUU'] # u_value = 4.0 self.logger.info( "updating mag mom with value {0}".format(self.mag_init)) self.logger.info( "updating NBANDS with {0}".format(nbands)) incar_dict.update({'NBANDS': nbands, 'LSORBIT': True, 'EDIFF': 1e-08, 'ICHARG': 11, 'LMAXMIX': 4, 'LCHARG': False, 'ISYM': 0, 'NSW': 0, 'ISPIN': 2, 'IBRION': -1, 'LORBIT': 11, 'MAGMOM': get_magmom_mae(poscar, self.mag_init) }) # incar_dict.update({'LDAUU': u_value}) elif self.magnetism == 'AFM': self.logger.info( 'updating INCAR and POSCAR for AFM calculation') afm, poscar = get_magmom_afm(poscar, self.database) incar_dict.update({'MAGMOM': afm}) except: # check what to do if the previous calculation being reused is not # actuall done .. system exit or adopt a user override # with POSCAR self.logger.warn( 'Empty relaxed CONTCAR file .. Probably job not done') if not self.reuse_override: self.logger.warn( 'You can set reuse_override to continue with POSCAR file, exiting now ..') sys.exit(0) else: self.logger.info('Using old Poscar for rerun') poscar = Poscar.from_file(pos + os.sep + 'POSCAR') # case for non - reuse else: poscar = pos # temporary: magnetism only set if twod flag is activated if self.database == 'twod': incar_dict.update( {'MAGMOM': get_magmom_string(poscar)}) self.set_kpoints(poscar=poscar) self.incar = Incar.from_dict(incar_dict) self.set_poscar(poscar=poscar) self.set_potcar() if not self.is_matrix: job_dir = self.job_dir + os.sep + 'POS' + \ os.sep + self.val_to_name(poscar) self.add_job(name=job_dir, job_dir=job_dir) def setup_potcar_jobs(self, mappings, functional_list): """ take a list of symbol mappings and setup the potcar jobs """ if functional_list: for func in functional_list: self.set_potcar(functional=func) if not self.is_matrix: job_dir = self.job_dir + os.sep \ + self.key_to_name('POTCAR') \ + os.sep + self.potcar_to_name(func) self.add_job(name=job_dir, job_dir=job_dir) elif mappings: for mapping in mappings: self.set_potcar(mapping) if not self.is_matrix: job_dir = self.job_dir + os.sep \ + self.key_to_name('POTCAR') \ + os.sep + self.potcar_to_name(mapping) self.add_job(name=job_dir, job_dir=job_dir) def add_job(self, name='noname', job_dir='.'): """ add a single job using the current incar, poscar, potcar and kpoints """ vis = MPINTVaspInputSet(name, self.incar, self.poscar, self.potcar, self.kpoints, self.qadapter, vis_logger=self.logger, reuse_path=self.reuse_paths) # the job command can be overrridden in the run method job = MPINTVaspJob(self.job_cmd, name=name, final=True, parent_job_dir=self.parent_job_dir, job_dir=job_dir, vis=vis, wait=self.wait, vjob_logger=self.logger) self.job_dir_list.append(os.path.abspath(job_dir)) self.jobs.append(job) def run(self, job_cmd=None): """ run the vasp jobs through custodian if the job list is empty, run a single job with the initial input set """ for j in self.jobs: if job_cmd is not None: j.job_cmd = job_cmd else: j.job_cmd = self.job_cmd c_params = {'jobs': [j.as_dict() for j in self.jobs], 'handlers': [h.as_dict() for h in self.handlers], 'max_errors': 5} c = Custodian(self.handlers, self.jobs, max_errors=5) c.run() for j in self.jobs: self.cal_log.append({"job": j.as_dict(), 'job_id': j.job_id, "corrections": [], 'final_energy': None}) self.job_ids.append(j.job_id) if self.checkpoint_file: dumpfn(self.cal_log, self.checkpoint_file, cls=MontyEncoder, indent=4) else: dumpfn(self.cal_log, Calibrate.LOG_FILE, cls=MontyEncoder, indent=4) def as_dict(self): qadapter = None system = None if self.qadapter: qadapter = self.qadapter.to_dict() if self.system is not None: system = self.system d = dict(incar=self.incar.as_dict(), poscar=self.poscar.as_dict(), potcar=self.potcar.as_dict(), kpoints=self.kpoints.as_dict(), system=system, is_matrix=self.is_matrix, Grid_type=self.Grid_type, parent_job_dir=self.parent_job_dir, job_dir=self.job_dir, qadapter=qadapter, job_cmd=self.job_cmd, wait=self.wait, turn_knobs=self.turn_knobs, job_dir_list=self.job_dir_list, job_ids=self.job_ids) d["@module"] = self.__class__.__module__ d["@class"] = self.__class__.__name__ # d['calibrate'] = self.__class__.__name__ return d @classmethod def from_dict(cls, d): incar = Incar.from_dict(d["incar"]) poscar = Poscar.from_dict(d["poscar"]) potcar = Potcar.from_dict(d["potcar"]) kpoints = Kpoints.from_dict(d["kpoints"]) cal = Calibrate(incar, poscar, potcar, kpoints, system=d["system"], is_matrix=d["is_matrix"], Grid_type=d["Grid_type"], parent_job_dir=d["parent_job_dir"], job_dir=d["job_dir"], qadapter=d.get("qadapter"), job_cmd=d["job_cmd"], wait=d["wait"], turn_knobs=d["turn_knobs"]) cal.job_dir_list = d["job_dir_list"] cal.job_ids = d["job_ids"] return cal