def test_get_endpoints_from_index(self): endpoints = get_endpoints_from_index(structure=self.structure, site_indices=[0, 1]) ep_0 = endpoints[0].as_dict() ep_1 = endpoints[1].as_dict() ep_0_expect = Structure.from_file(get_path("POSCAR_ep0", dirname="io_files")).as_dict() ep_1_expect = Structure.from_file(get_path("POSCAR_ep1", dirname="io_files")).as_dict() self.assertEqual(ep_0, ep_0_expect) self.assertEqual(ep_1, ep_1_expect)
def run_task(self, fw_spec): label = self["label"] assert label in ["parent", "ep0", "ep1" ] or "neb" in label, "Unknown label!" d_img = float(self.get("d_img", 0.7)) # Angstrom wf_name = fw_spec["wf_name"] src_dir = os.path.abspath(".") dest_dir = os.path.join(fw_spec["_fw_env"]["run_dest_root"], wf_name, label) shutil.copytree(src_dir, dest_dir) # Update fw_spec based on the type of calculations. if "neb" in label: # Update all relaxed images. subs = glob.glob("[0-2][0-9]") nimages = len(subs) concar_list = [f"{i:02}/CONTCAR" for i in range(nimages)[1:-1]] images = [Structure.from_file(contcar) for contcar in concar_list] # Update the two ending "images". images.insert(0, Structure.from_file("00/POSCAR")) images.append( Structure.from_file("{:02}/POSCAR".format(nimages - 1))) images = [s.as_dict() for s in images] neb = fw_spec.get("neb") neb.append(images) update_spec = { "neb": neb, "_queueadapter": { "nnodes": str(len(images) - 2), "nodes": str(len(images) - 2), }, } # Use neb walltime if it is in fw_spec if fw_spec["neb_walltime"] is not None: update_spec["_queueadapter"].update( {"walltime": fw_spec.get("neb_walltime")}) elif label in ["ep0", "ep1"]: # Update relaxed endpoint structures. file = glob.glob("CONTCAR*")[0] ep = Structure.from_file(file, False) # One endpoint if fw_spec.get("incar_images" ): # "incar_images": pre-defined image number. update_spec = { label: ep.as_dict(), "_queueadapter": { "nnodes": fw_spec["incar_images"], "nodes": fw_spec["incar_images"], }, } else: # Calculate number of images if "IMAGES" tag is not provided. index = int(label[-1]) ep_1_dict = fw_spec.get( "ep{}".format(1 - index)) # Another endpoint try: ep_1 = Structure.from_dict(ep_1_dict) except Exception: ep_1 = ep_1_dict max_dist = max(get_endpoint_dist(ep, ep_1)) nimages = round(max_dist / d_img) or 1 update_spec = { label: ep, "_queueadapter": { "nnodes": int(nimages), "nodes": int(nimages) }, } # Use neb walltime if it is in fw_spec if fw_spec["neb_walltime"] is not None: update_spec["_queueadapter"].update( {"walltime": fw_spec.get("neb_walltime")}) else: # label == "parent" f = glob.glob("CONTCAR*")[0] s = Structure.from_file(f, False) ep0, ep1 = get_endpoints_from_index(s, fw_spec["site_indices"]) update_spec = { "parent": s.as_dict(), "ep0": ep0.as_dict(), "ep1": ep1.as_dict(), } # Clear current directory. for d in os.listdir(src_dir): try: os.remove(os.path.join(src_dir, d)) except Exception: shutil.rmtree(os.path.join(src_dir, d)) return FWAction(update_spec=update_spec)
def get_wf_neb_from_structure( structure, user_incar_settings=None, additional_spec=None, user_kpoints_settings=None, additional_cust_args=None, ): """ Obtain the CI-NEB workflow staring with a parent structure. This works only under the single vacancy diffusion mechanism. Workflow: (parent relaxation) --> Endpoints relaxation --> NEB_1 --> NEB_2 --> ... --> NEB_r (i) If parent is not relaxed: then parent relaxation--ep--neb(r) (r rounds of NEB) (ii) If parent is relaxed: ep--neb(r) (r rounds of NEB) Args: structure (Structure): The parent structure. user_incar_settings([dict]): Additional user_incar_settings. Note that the order of the list is set as: "parent", "ep_relax", "neb1", "neb2" etc., which contains at least three elements. The first dict is for parent structure relaxation, the second dict is for endpoints relaxation, and the rest are for NEB calculations. For example, [{}, {}, {"IOPT": 7}, {"IOPT": 1}]. Besides, user_incar_settings is used to determine how many NEB rounds will be. Default is [{}, {}, {}]. additional_spec (dict): User spec settings to overwrite default_spec. user_kpoints_settings ([dict]): Additional user_kpoints_settings, which contains at at least three elements, which is similar to user_incar_settings. For example, [{}, {}, {"grid_density": 100}] for the workflow from the parent structure relaxation, then the endpoint relaxation followed by one-round NEB simulation. Default values depend on the selected VaspInputSet. additional_cust_args ([dict]): Optional parameters for RunVaspCustodian, same structure with user_incar_settings and user_kpoints_settings. Returns: Workflow """ spec = _update_spec(additional_spec) site_indices = spec["site_indices"] is_optimized = spec["is_optimized"] wf_name = spec["wf_name"] endpoints = get_endpoints_from_index(structure, site_indices) # Default settings for "parent", "ep0" and "ep1". If is_optimized is False, spec["ep0"] and # spec["ep1"] will be updated after parent relaxation. spec["parent"] = structure.as_dict() spec["ep0"], spec["ep1"] = endpoints[0].as_dict(), endpoints[1].as_dict() # Assume one round NEB if user_incar_settings not provided. user_incar_settings = user_incar_settings or [{}, {}, {}] neb_round = len(user_incar_settings[2:]) user_kpoints_settings = user_kpoints_settings or [{ "grid_density": 1000 }] * (neb_round + 2) additional_cust_args = additional_cust_args or [{}] * (neb_round + 2) for incar in user_incar_settings[2:]: if incar.get("IMAGES"): # If "incar_images" shows up, the number of images is pre-defined spec["incar_images"] = incar["IMAGES"] break if is_optimized: # Start from endpoints neb_fws, rlx_fws = [], [] # Get neb fireworks. for n in range(neb_round): fw = NEBFW( spec=spec, neb_label=str(n + 1), from_images=False, user_incar_settings=user_incar_settings[n + 2], user_kpoints_settings=user_kpoints_settings[n + 2], additional_cust_args=additional_cust_args[n + 2], ) neb_fws.append(fw) # Get relax fireworks for label in ["ep0", "ep1"]: fw = NEBRelaxationFW( spec=spec, label=label, user_incar_settings=user_incar_settings[1], user_kpoints_settings=user_kpoints_settings[1], additional_cust_args=additional_cust_args[1], ) rlx_fws.append(fw) # Build fireworks link links = {rlx_fws[0]: [neb_fws[0]], rlx_fws[1]: [neb_fws[0]]} else: # Start from parent structure neb_fws, rlx_fws = [], [] # Get neb fireworks. for n in range(neb_round): fw = NEBFW( spec=spec, neb_label=str(n + 1), from_images=False, user_incar_settings=user_incar_settings[n + 2], user_kpoints_settings=user_kpoints_settings[n + 2], additional_cust_args=additional_cust_args[n + 2], ) neb_fws.append(fw) # Get relaxation fireworks. rlx_fws.append( NEBRelaxationFW( spec=spec, label="parent", user_incar_settings=user_incar_settings[0], user_kpoints_settings=user_kpoints_settings[0], additional_cust_args=additional_cust_args[0], )) for i, label in enumerate(["ep0", "ep1"]): fw = NEBRelaxationFW( spec=spec, label=label, user_incar_settings=user_incar_settings[1], user_kpoints_settings=user_kpoints_settings[1], additional_cust_args=additional_cust_args[1], ) rlx_fws.append(fw) # Build fireworks link links = { rlx_fws[0]: [rlx_fws[1], rlx_fws[2]], rlx_fws[1]: [neb_fws[0]], rlx_fws[2]: [neb_fws[0]], } # Put all fireworks together with link fws = rlx_fws + neb_fws if neb_round >= 2: for r in range(1, neb_round): links[neb_fws[r - 1]] = [neb_fws[r]] workflow = Workflow(fws, links_dict=links, name=wf_name) return workflow
def setUp(self): """ 1) Basic check for pymatgen configurations. 2) Setup all test workflow. """ super().setUp() # Structures used for test: parent = PymatgenTest.get_structure("Li2O") parent.remove_oxidation_states() parent.make_supercell(2) parent = parent.get_sorted_structure() ep0, ep1 = get_endpoints_from_index(parent, [0, 1]) neb_dir = [wf_dir / f"4/inputs/{i:02}/POSCAR" for i in range(5)] self.structures = [Structure.from_file(n) for n in neb_dir] test_yaml = wf_dir / "config/neb_unittest.yaml" with open(test_yaml) as stream: self.config = yaml.safe_load(stream) # Use scratch directory as destination directory for testing env = {"run_dest_root": self.scratch_dir} self.config["common_params"]["_fw_env"] = env # Config 1: The parent structure & two endpoint indexes provided; need # relaxation first. self.config_1 = copy.deepcopy(self.config) self.config_1["common_params"]["is_optimized"] = False self.config_1["common_params"]["wf_name"] = "NEB_test_1" # Config 2: The parent structure & two endpoint indexes provided; no # need to relax. self.config_2 = copy.deepcopy(self.config) del self.config_2["fireworks"][0] self.config_2["common_params"]["is_optimized"] = True self.config_2["common_params"]["wf_name"] = "NEB_test_2" # Config 3: Two endpoints provided; need to relax two endpoints. self.config_3 = copy.deepcopy(self.config) del self.config_3["fireworks"][0] self.config_3["common_params"]["is_optimized"] = False self.config_3["common_params"]["wf_name"] = "NEB_test_3" # Config 4: Two relaxed endpoints provided; no need to relax two # endpoints. self.config_4 = copy.deepcopy(self.config_3) del self.config_4["fireworks"][0] self.config_4["common_params"]["is_optimized"] = True self.config_4["common_params"]["wf_name"] = "NEB_test_4" # Config 5: All images including two endpoints are provided. self.config_5 = copy.deepcopy(self.config) del self.config_5["fireworks"][0:2] self.config_5["common_params"]["wf_name"] = "NEB_test_5" self.wf_1 = wf_nudged_elastic_band([parent], parent, self.config_1) self.wf_2 = wf_nudged_elastic_band([parent], parent, self.config_2) self.wf_3 = wf_nudged_elastic_band([ep0, ep1], parent, self.config_3) self.wf_4 = wf_nudged_elastic_band([ep0, ep1], parent, self.config_4) self.wf_5 = wf_nudged_elastic_band(self.structures, parent, self.config_5) # Workflow without the config file self.wf_6 = wf_nudged_elastic_band(self.structures, parent)