def test_image_num(self): module_dir = os.path.dirname(os.path.abspath(__file__)) test_file_dir = os.path.join(module_dir, "..", "..", "..", "test_files", "path_finder") start_s = Poscar.from_file(os.path.join(test_file_dir, 'LFP_POSCAR_s')).structure end_s = Poscar.from_file(os.path.join(test_file_dir, 'LFP_POSCAR_e')).structure mid_s = start_s.interpolate(end_s, nimages=2, interpolate_lattices=False)[1] chg = Chgcar.from_file(os.path.join(test_file_dir, 'LFP_CHGCAR.gz')) moving_cation_specie = Element('Li') relax_sites = [] for site_i, site in enumerate(start_s.sites): if site.specie == moving_cation_specie: relax_sites.append(site_i) pf = NEBPathfinder(start_s, end_s, relax_sites=relax_sites, v=ChgcarPotential(chg).get_v(), n_images=(8 * 3)) images = [] for i, image in enumerate(pf.images): if i % 3 == 0: images.append(image) self.assertEqual(len(images), 9) pf_mid = NEBPathfinder(start_s, end_s, relax_sites=relax_sites, v=ChgcarPotential(chg).get_v(), n_images=10, mid_struct=mid_s) moving_site = relax_sites[0] dists = [s1.sites[moving_site].distance(s2.sites[moving_site]) for s1, s2 in zip(pf.images[:-1], pf.images[1:])] # check that all the small distances are about equal self.assertTrue(abs(min(dists)-max(dists))/mean(dists) < 0.02)
def find_path(self, end_structure, images, sites=[], charge=None): from pymatgen.analysis.path_finder import NEBPathfinder from pymatgen.analysis.path_finder import FreeVolumePotential from pymatgen.analysis.path_finder import ChgcarPotential init_struct = self.poscar.structure final_struct = end_structure if charge is None: dim = [50, 50, 120] v = FreeVolumePotential(final_struct, dim) obj = NEBPathfinder(init_struct, final_struct, sites, v.get_v(), n_images=images) obj.interpolate() new_path = obj.images else: v = ChgcarPotential(charge) obj = NEBPathfinder(init_struct, final_struct, [80, 81, 82], v.get_v(), n_images=images) obj.interpolate() new_path = obj.images joblist = [] for index, item in enumerate(new_path): filepath = os.path.join(self.foldname, '{0:02d}'.format(index)) joblist.append(easyjob(item, filepath)) return joblist
def _get_pathfinder_from_hop(self, migration_path, n_images=20): # get migration pathfinder objects which contains the paths ipos = migration_path.isite.frac_coords epos = migration_path.esite.frac_coords mpos = migration_path.esite.frac_coords start_struct = self.base_aeccar.structure.copy() end_struct = self.base_aeccar.structure.copy() mid_struct = self.base_aeccar.structure.copy() # the moving ion is always inserted on the zero index start_struct.insert(0, self.migrating_specie, ipos, properties=dict(magmom=0)) end_struct.insert(0, self.migrating_specie, epos, properties=dict(magmom=0)) mid_struct.insert(0, self.migrating_specie, mpos, properties=dict(magmom=0)) chgpot = ChgcarPotential(self.base_aeccar, normalize=False) npf = NEBPathfinder( start_struct, end_struct, relax_sites=[0], v=chgpot.get_v(), n_images=n_images, mid_struct=mid_struct, ) return npf
def test_image_num(self): os.path.dirname(os.path.abspath(__file__)) test_file_dir = os.path.join(PymatgenTest.TEST_FILES_DIR, "path_finder") start_s = Poscar.from_file(os.path.join(test_file_dir, "LFP_POSCAR_s")).structure end_s = Poscar.from_file(os.path.join(test_file_dir, "LFP_POSCAR_e")).structure chg = Chgcar.from_file(os.path.join(test_file_dir, "LFP_CHGCAR.gz")) moving_cation_specie = Element("Li") relax_sites = [] for site_i, site in enumerate(start_s.sites): if site.specie == moving_cation_specie: relax_sites.append(site_i) pf = NEBPathfinder( start_s, end_s, relax_sites=relax_sites, v=ChgcarPotential(chg).get_v(), n_images=(8 * 3), ) images = [] for i, image in enumerate(pf.images): if i % 3 == 0: images.append(image) self.assertEqual(len(images), 9) moving_site = relax_sites[0] dists = [ s1.sites[moving_site].distance(s2.sites[moving_site]) for s1, s2 in zip(pf.images[:-1], pf.images[1:]) ] # check that all the small distances are about equal self.assertTrue(abs(min(dists) - max(dists)) / mean(dists) < 0.02)
def test_image_num(self): module_dir = os.path.dirname(os.path.abspath(__file__)) test_file_dir = os.path.join(module_dir, "..", "..", "..", "test_files", "path_finder") start_s = Poscar.from_file(os.path.join(test_file_dir, 'LFP_POSCAR_s')).structure end_s = Poscar.from_file(os.path.join(test_file_dir, 'LFP_POSCAR_e')).structure chg = Chgcar.from_file(os.path.join(test_file_dir, 'LFP_CHGCAR.gz')) moving_cation_specie = Element('Li') relax_sites = [] for site_i, site in enumerate(start_s.sites): if site.specie == moving_cation_specie: relax_sites.append(site_i) pf = NEBPathfinder(start_s, end_s, relax_sites=relax_sites, v=ChgcarPotential(chg).get_v(), n_images=(8 * 3)) images = [] for i, image in enumerate(pf.images): if i % 3 == 0: images.append(image) self.assertEqual(len(images), 9)
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 run_task(self, fw_spec): n_images = self["n_images"] end_points_combo = self["end_points_combo"] # checks if format of end_points_combo is correct and if so # get two desired end_points indexes for end point structures try: combo = end_points_combo.split("+") if len(combo) == 2: c = [int(combo[0]), int(combo[1])] else: raise ValueError( "NEBPathfinder requires exactly two end points") except: raise ValueError("{} end_points_combo input is incorrect".format( str(end_points_combo))) # get the database connection db_file = env_chk(self["db_file"], fw_spec) mmdb = VaspCalcDb.from_db_file(db_file, admin=True) mmdb.collection = mmdb.db["approx_neb"] wf_uuid = self["approx_neb_wf_uuid"] # get end_points and task_id of host from approx_neb collection approx_neb_doc = mmdb.collection.find_one({"wf_uuid": wf_uuid}, { "end_points": 1, "host.task_id": 1, "_id": 0 }) end_points = approx_neb_doc["end_points"] task_id = approx_neb_doc["host"]["task_id"] # get potential gradient, v, from host chgcar mmdb.collection = mmdb.db["tasks"] host_chgcar = mmdb.get_chgcar(task_id) v_chgcar = ChgcarPotential(host_chgcar) host_v = v_chgcar.get_v() # get start and end point structures from end_points start_struct = Structure.from_dict( end_points[c[0]]["output"]["structure"]) end_struct = Structure.from_dict( end_points[c[1]]["output"]["structure"]) # checks if inserted site indexes match inserted_site_indexes = end_points[c[0]]["inserted_site_indexes"] if inserted_site_indexes != end_points[c[1]]["inserted_site_indexes"]: raise ValueError( "Inserted site indexes of end point structures must match for NEBPathfinder" ) # applies NEBPathFinder to interpolate and get images to store in # pathfinder_output. neb_pf = NEBPathfinder( start_struct, end_struct, relax_sites=inserted_site_indexes, v=host_v, n_images=n_images + 1, ) # note NEBPathfinder currently returns n_images+1 images (rather than n_images) # and the first and last images generated are very similar to the end points # provided so they are discarded pathfinder_output = { "images": [structure.as_dict() for structure in neb_pf.images[1:-1]], "relax_site_indexes": inserted_site_indexes, } # stores images generated by NEBPathFinder in approx_neb collection # pathfinder field which is a nested dictionary using # end_points_combo as a key. mmdb.collection = mmdb.db["approx_neb"] pf_subdoc = mmdb.collection.find_one({"wf_uuid": wf_uuid}, { "pathfinder": 1, "_id": 0 }) if "pathfinder" not in pf_subdoc.keys(): pf_subdoc = {} else: pf_subdoc = pf_subdoc["pathfinder"] pf_subdoc.update({end_points_combo: pathfinder_output}) mmdb.collection.update_one( {"wf_uuid": wf_uuid}, { "$set": { "pathfinder": pf_subdoc, "last_updated": datetime.utcnow() } }, ) return FWAction( stored_data={ "wf_uuid": wf_uuid, "end_points_combo": c, "pathfinder": pathfinder_output, })