def random_purturbation_index(): if is_pbc(): struct=readstructure(crystal=True,molecule=False) natom=len(struct) d=np.zeros(2,dtype=int) while d[0]==d[1]: d=np.random.randint(0,natom,2) coord=struct.frac_coords coord[[d[0], d[1]], :] = coord[[d[1], d[0]], :] tmp_struct=Structure(struct.lattice,struct.species,coord) fname='swap_'+str(d[0]+1)+'_'+str(d[1]+1)+'.vasp' proc_str="Saving data to "+ fname +" File ..." procs(proc_str,0,sp='-->>') tmp_struct.to(filename=fname,fmt='poscar') else: struct=readstructure(crystal=False,molecule=True) # print(struct) coord=struct.cart_coords natom=len(struct) d=np.zeros(2,dtype=int) while d[0]==d[1]: d=np.random.randint(0,natom,2) coord[[d[0], d[1]], :] = coord[[d[1], d[0]], :] tmp_struct=Molecule(struct.species,coord) fname='swap_'+str(d[0]+1)+'_'+str(d[1]+1)+'.xyz' proc_str="Saving data to "+ fname +" File ..." procs(proc_str,0,sp='-->>') tmp_struct.to(filename=fname,fmt='xyz') return
def random_disturbing_lat(): struct=readstructure(crystal=True,molecule=False) print("input the maximum displacement and with fix diagonal ") print("or non-diagonal element like this: 0.01 T F") print("it means maximum displacement is 0.01 ") print("the diagonal element will be fixed,") print("while random disturbing will be add to non-digaonal element") wait_sep() in_str="" while in_str=="": in_str=input().strip().split() # print(in_str) maxdelta=float(in_str[0]) diag=in_str[1].lower()=='t' nondiag=in_str[2].lower()=='t' # print(diag) # print(nondiag) stress_eps = np.random.random(6) * 2 * maxdelta - maxdelta if diag: stress_eps[:3] = 0 if nondiag: stress_eps[-3:] = 0 new_lattice=deform_cell(struct,stress_eps) tmp_struct=Structure(new_lattice,struct.species,struct.frac_coords) fname='random.vasp' proc_str="Saving data to "+ fname +" File ..." procs(proc_str,0,sp='-->>') tmp_struct.to(filename=fname,fmt='poscar') return
def poscar_elong(poscar_in, poscar_out, elong, shift_center=True): with open(poscar_in, 'r') as fin: lines = list(fin) if lines[7][0].upper() != 'C': raise RuntimeError("only works for Cartesian POSCAR") sboxz = lines[4].split() boxz = np.array([float(sboxz[0]), float(sboxz[1]), float(sboxz[2])]) boxzl = np.linalg.norm(boxz) elong_ratio = elong / boxzl boxz = boxz * (1. + elong_ratio) lines[4] = '%.16e %.16e %.16e\n' % (boxz[0], boxz[1], boxz[2]) if shift_center: poscar_str = "".join(lines) st = Structure.from_str(poscar_str, fmt='poscar') cart_coords = st.cart_coords z_mean = cart_coords[:, 2].mean() z_shift = st.lattice.c / 2 - z_mean cart_coords[:, 2] = cart_coords[:, 2] + z_shift nst = Structure(st.lattice, st.species, coords=cart_coords, coords_are_cartesian=True) nst.to('poscar', poscar_out) else: with open(poscar_out, 'w') as fout: fout.write("".join(lines))
def split(structs, fnames, atom_index, in_str, SplitDistance, NumberSplitSite, ProperDist=3.5, DenseFrac=0.75): # len(structs)==1 ''' split one or several layers from bulk or structures Args: structs: list of Structure obj. fnames: list of str for input file name. atom_index: list, which atom will be split . SplitDistance: float, the largest distance for splitting. NumberSplitSite: int, how many structures to be constructured during split Returns: None Output: split_xxxx.dat Num_xxx.vasp ''' col_head = "#%(key1)+12s %(key2)+12s" % { 'key1': 'index', 'key2': 'distance/Ang' } for struct, fname in zip(structs, fnames): DensityN = int(NumberSplitSite * DenseFrac) SparseN = NumberSplitSite - DensityN + 1 dist = ProperDist / (DensityN - 1) SplitDistanceArray = np.zeros(NumberSplitSite + 1) for Nsite in range(DensityN): SplitDistanceArray[Nsite] = (Nsite) * dist dist = (SplitDistance - ProperDist) / SparseN for Nsite in range(SparseN): SplitDistanceArray[Nsite + DensityN] = ProperDist + (Nsite + 1) * dist coords = struct.cart_coords for Nsite in range(NumberSplitSite + 1): coords = struct.cart_coords for atom in atom_index: coords[atom, 2] = coords[atom, 2] + SplitDistanceArray[Nsite] tmp_struct = Structure(struct.lattice, struct.species, coords, coords_are_cartesian=True) filename = str(Nsite) + '_' + fname + '.vasp' tmp_struct.to(filename=filename, fmt='poscar') data = np.zeros((NumberSplitSite + 1, 2)) for i, j in enumerate(SplitDistanceArray): data[i][0] = i data[i][1] = j ret = DataIO(data, col_head=col_head, fmt_all="%12d %12.6f" + '\n') filename = 'split_' + fname + '.dat' ret.write(filename)
def output_clusters(self, fmt, periodic=None): """ Outputs the unique unit cell clusters from the graph Args: fmt (str): output format for pymatgen structures set up from clusters periodic (Boolean): Whether to output only periodic clusters Outputs: CLUS_*."fmt": A cluster structure file for each cluster in the graph """ if fmt == "poscar": tail = "vasp" else: tail = fmt site_sets = [] for cluster in self.clusters: if periodic: if cluster.periodic > 0: site_sets.append( frozenset([ int(node.labels["UC_index"]) for node in cluster.nodes ])) else: site_sets.append( frozenset([ int(node.labels["UC_index"]) for node in cluster.nodes ])) site_sets = set(site_sets) for index, site_list in enumerate(site_sets): cluster_structure = Structure(lattice=self.structure.lattice, species=[], coords=[]) symbols = [species for species in self.structure.symbol_set] if "X" in set(symbols): symbols.remove("X") symbols.append("X0+") for symbol in symbols: for site in site_list: site = self.structure.sites[site] if site.species_string == symbol: cluster_structure.append(symbol, site.coords, coords_are_cartesian=True) cluster_structure.to(fmt=fmt, filename="CLUS_" + str(index) + "." + tail)
def to_pos(name: str): s = IMolecule.from_file(name) arr = ''' 6.0189552714770000 0.0000000000000000 0.0000000000000000 -3.0094776357390001 5.2125681693410000 0.0000000000000000 0.0000000000000000 0.0000000000000000 17.3716845998769998 '''.split() arr = np.array(arr) arr.reshape((3, 3)) lat = Lattice(arr) s = Structure(lat, s.species, s.cart_coords, coords_are_cartesian=True) s.to('POSCAR', name.replace('xyz', 'vasp')) print('ok')
def make_atom_poscar_dirs(path: Path, elems: List[Element] = None): elems = [str(e) for e in elems] if elems else mags.keys() for element in elems: d = path / element Path(d).mkdir() structure = Structure(Lattice.cubic(10), [element], [[0.5] * 3]) structure.to(fmt="POSCAR", filename=str(d / "POSCAR")) nupdown = mags[element] prior_info = { "incar": { "ISPIN": 2, "NUPDOWN": nupdown, "NELM": 300 }, "is_cluster": True } (d / "prior_info.yaml").write_text(yaml.dump(prior_info))
def random_disturbing_pos(): def random_move_one_atom(coords, mu=0.1, sigma=0.01): index = random.randint(0, len(coords) - 1) radius = np.abs(np.random.normal(mu, sigma)) theta_x = 2 * np.pi * np.random.random_sample() theta_y = 2 * np.pi * np.random.random_sample() theta_z = 2 * np.pi * np.random.random_sample() vector = apply_rotation([1, 0, 0], theta_x, theta_y, theta_z) coords[index] += vector*radius return coords print("input the maximum displacement(<0.25 in Angstrom)") wait_sep() in_str="" while in_str=="": in_str=input().strip() epsilon=float(in_str) assert epsilon < 0.3 if is_pbc(): struct=readstructure(crystal=True,molecule=False) coords=struct.cart_coords for iatom in range(len(struct)): coords=random_move_one_atom(coords,mu=epsilon) tmp_struct=Structure(struct.lattice,struct.species,coords,coords_are_cartesian=True) fname='random.vasp' proc_str="Saving data to "+ fname +" File ..." procs(proc_str,0,sp='-->>') tmp_struct.to(filename=fname,fmt='poscar') else: struct=readstructure(crystal=False,molecule=True) coords=struct.cart_coords for iatom in range(len(struct)): coords=random_move_one_atom(coords,mu=epsilon) tmp_struct=Molecule(struct.species,coords) fname='random.xyz' proc_str="Saving data to "+ fname +" File ..." procs(proc_str,0,sp='-->>') tmp_struct.to(filename=fname,fmt='xyz') return
def circle_strain(structs, fnames, C11, C12, C22, C66, sigma, nps=37): for struct, fname in zip(structs, fnames): if not struct.lattice.is_orthogonal: warn_tip(0, "{} is not orthogonal, skip it !!!".format(fname)) continue new_struct = move_to_zcenter(struct) orig_struct = new_struct.copy() new_struct = new_struct.copy() natom = orig_struct.num_sites lat = orig_struct.lattice.matrix.copy() pos = orig_struct.frac_coords phi = np.linspace(0, 360, nps) * np.pi / 180 vzz = C12 / C22 temp_num = (C11 * C22 - C12**2) / (C22 * C66) d1 = C11 / C22 + 1.0 - temp_num d2 = -(2.0 * C12 / C22 - temp_num) d3 = C11 / C22 F = sigma * C22 / (C11 * C22 - C12**2.0) Poisson=(vzz*(np.cos(phi))**4.0-d1*(np.cos(phi))**2.0*(np.sin(phi))**2.0+vzz*(np.sin(phi))**4.0)/\ ((np.cos(phi))**4.0+d2*(np.cos(phi))**2.0*(np.sin(phi))**2.0+d3*(np.sin(phi))**4.0) eps_theta = F * ((np.cos(phi))**4 + d2 * (np.cos(phi))**2.0 * (np.sin(phi))**2.0 + d3 * (np.sin(phi))**4.0) t = sympy.Symbol('t', real=True) e = sympy.Symbol('e', real=True) v = sympy.Symbol('v', real=True) eprim = sympy.Matrix([[e + 1, 0], [0, 1 - e * v]]) R = sympy.Matrix([[sympy.cos(t), -sympy.sin(t)], [sympy.sin(t), sympy.cos(t)]]) eps_mat = R * eprim * R.adjugate() for k in range(len(phi)): cur__phi = phi[k] * 180 / np.pi Rot = eps_mat.subs({e: eps_theta[k], v: Poisson[k], t: phi[k]}) filename = str(k) + "_" + fname + '.vasp' final_lat = np.matrix(np.eye(3)) final_lat[0, 0] = Rot[0, 0] final_lat[0, 1] = Rot[0, 1] final_lat[1, 0] = Rot[1, 0] final_lat[1, 1] = Rot[1, 1] lat_new = lat * final_lat tmp_struct = Structure(lat_new, new_struct.species, pos) tmp_struct.to(filename=filename, fmt='poscar')
def cry2cif(filename, to="cif", center=False, sortx=False, sortz=False, b_dum=50, c_dum=50, istruct=-1): """ Read a CRYSTAL output file and return the structure in a cif or POSCAR format. Args: filename (str): crystal output filename to (str): 'cif' or 'vasp', format of the output file (default is cif) center (bool): if True, the slab or nanotube is translated to the center of the box (default is False) sortx (bool): Nanotube : if True, atoms are sorted along x axes (default is False). sortz (bool): slab : if True, atoms are sorted along z axes (default is False). b_dum (float): dummy lattice paramters b in angstrom for nanotubes (default 50 A) c_dum (float): dummy lattice paramters c in angstrom for nanotubes and slabs (default 50 A) istruct (int): structure to be extracted """ cryout = CrystalOutfile(filename) print("title : ", cryout.title) if cryout.group: print("group : ", cryout.group) # print("Number of structure read: ", len(cryout.structures)) if istruct == -1: print("structure : Final structure") structure = cryout.final_structure else: print("structure : Structure %d" % istruct) structure = cryout.get_structure(istruct) print("# atom : ", structure.num_sites) print("composition: ", structure.composition) print("Cell parameters:") print("a : %10.4f" % structure.lattice.a) print("b : %10.4f" % structure.lattice.b) print("c : %10.4f" % structure.lattice.c) print("alpha : %10.4f" % structure.lattice.alpha) print("beta : %10.4f" % structure.lattice.beta) print("gamma : %10.4f" % structure.lattice.gamma) # ---------------------------------------------------------- # New b and c axes # ---------------------------------------------------------- if cryout.slab: frac_coords = structure.frac_coords frac_coords[:, 2] *= structure.lattice.c / c_dum matrix = structure.lattice.matrix.copy() matrix[2, 2] = c_dum structure = Structure(Lattice(matrix), structure.species, frac_coords) if cryout.nanotube: frac_coords = structure.frac_coords frac_coords[:, 1] *= structure.lattice.c / c_dum frac_coords[:, 2] *= structure.lattice.b / b_dum matrix = structure.lattice.matrix.copy() matrix[1, 1] = b_dum matrix[2, 2] = c_dum structure = Structure(Lattice(matrix), structure.species, frac_coords) # ---------------------------------------------------------- # move slab or nanotube to the center of the box # ---------------------------------------------------------- if center: if cryout.slab: coords = structure.frac_coords.copy() coords[:, 2] += .5 structure = Structure(structure.lattice, structure.species, coords) elif cryout.nanotube: coords = structure.frac_coords coords += .5 structure = Structure(structure.lattice, structure.species, coords) # ---------------------------------------------------------- # sort atom along x or z axis for slab # ---------------------------------------------------------- if sortz: isort = 2 elif sortx: isort = 0 axes = {2: "z", 0: "x"} if sortz or sortx: print("\nSort atoms along %s" % axes[isort]) data = zip(structure.species, structure.frac_coords) data = sorted(data, key=lambda d: d[-1][isort], reverse=True) species = [d[0] for d in data] coords = [d[1] for d in data] structure = Structure(structure.lattice, species, coords) # ---------------------------------------------------------- # export in the given format # ---------------------------------------------------------- basename, _ = os.path.splitext(filename) if to.lower() == "cif": ext = ".cif" elif to.lower() == "vasp": to = "POSCAR" ext = ".vasp" else: to = "POSCAR" ext = ".vasp" structure.to(to, filename=basename + ext)
matplotlib.rcParams.update({'font.size': 18}) fig_size = [15, 12] plt.rcParams["figure.figsize"] = fig_size # In[ ]: # Create beta-CsCl structure a = 6.923 #Angstrom latt = Lattice.cubic(a) structure = Structure(latt, ["Cs", "Cs", "Cs", "Cs", "Cl", "Cl", "Cl", "Cl"], [[0, 0, 0], [0.5, 0.5, 0], [0, 0.5, 0.5], [0.5, 0, 0.5], [0.5, 0.5, 0.5], [0, 0, 0.5], [0, 0.5, 0], [0.5, 0, 0]]) temp_cif = NamedTemporaryFile(delete=False) structure.to("cif", temp_cif.name) xu_cif = CIFFile(temp_cif.name) xu_crystal = Crystal(name="b-CsCl", lat=xu_cif.SGLattice()) temp_cif.close() two_theta = numpy.arange(0, 80, 0.01) powder = xru.simpack.smaterials.Powder(xu_crystal, 1) pm = xru.simpack.PowderModel(powder, I0=100) intensities = pm.simulate(two_theta) plt.plot(two_theta,intensities) plt.xlim(0,80) ax = plt.axes() ax.xaxis.set_major_locator(ticker.MultipleLocator(5)) ax.yaxis.set_major_formatter(ticker.FormatStrFormatter('%.4f')) plt.title("XRD Pattern for " + xu_crystal.name)
def constrained_opt_run(cls, vasp_cmd, lattice_direction, initial_strain, atom_relax=True, max_steps=20, algo="bfgs", **vasp_job_kwargs): """ Returns a generator of jobs for a constrained optimization run. Typical use case is when you want to approximate a biaxial strain situation, e.g., you apply a defined strain to a and b directions of the lattice, but allows the c-direction to relax. Some guidelines on the use of this method: i. It is recommended you do not use the Auto kpoint generation. The grid generated via Auto may fluctuate with changes in lattice param, resulting in numerical noise. ii. Make sure your EDIFF/EDIFFG is properly set in your INCAR. The optimization relies on these values to determine convergence. Args: vasp_cmd (str): Command to run vasp as a list of args. For example, if you are using mpirun, it can be something like ["mpirun", "pvasp.5.2.11"] lattice_direction (str): Which direction to relax. Valid values are "a", "b" or "c". initial_strain (float): An initial strain to be applied to the lattice_direction. This can usually be estimated as the negative of the strain applied in the other two directions. E.g., if you apply a tensile strain of 0.05 to the a and b directions, you can use -0.05 as a reasonable first guess for initial strain. atom_relax (bool): Whether to relax atomic positions. max_steps (int): The maximum number of runs. Defaults to 20 ( highly unlikely that this limit is ever reached). algo (str): Algorithm to use to find minimum. Default is "bfgs", which is fast, but can be sensitive to numerical noise in energy calculations. The alternative is "bisection", which is more robust but can be a bit slow. The code does fall back on the bisection when bfgs gives a non-sensical result, e.g., negative lattice params. \*\*vasp_job_kwargs: Passthrough kwargs to VaspJob. See :class:`custodian.vasp.jobs.VaspJob`. Returns: Generator of jobs. At the end of the run, an "EOS.txt" is written which provides a quick look at the E vs lattice parameter. """ nsw = 99 if atom_relax else 0 incar = Incar.from_file("INCAR") # Set the energy convergence criteria as the EDIFFG (if present) or # 10 x EDIFF (which itself defaults to 1e-4 if not present). if incar.get("EDIFFG") and incar.get("EDIFFG") > 0: etol = incar["EDIFFG"] else: etol = incar.get("EDIFF", 1e-4) * 10 if lattice_direction == "a": lattice_index = 0 elif lattice_direction == "b": lattice_index = 1 else: lattice_index = 2 energies = {} for i in range(max_steps): if i == 0: settings = [ {"dict": "INCAR", "action": {"_set": {"ISIF": 2, "NSW": nsw}}}] structure = Poscar.from_file("POSCAR").structure x = structure.lattice.abc[lattice_index] backup = True else: backup = False v = Vasprun("vasprun.xml") structure = v.final_structure energy = v.final_energy lattice = structure.lattice x = lattice.abc[lattice_index] energies[x] = energy if i == 1: x *= (1 + initial_strain) else: # Sort the lattice parameter by energies. min_x = min(energies.keys(), key=lambda e: energies[e]) sorted_x = sorted(energies.keys()) ind = sorted_x.index(min_x) if ind == 0: other = ind + 1 elif ind == len(sorted_x) - 1: other = ind - 1 else: other = ind + 1 \ if energies[sorted_x[ind + 1]] \ < energies[sorted_x[ind - 1]] \ else ind - 1 if abs(energies[min_x] - energies[sorted_x[other]]) < etol: logger.info("Stopping optimization! Final %s = %f" % (lattice_direction, min_x)) break if ind == 0 and len(sorted_x) > 2: # Lowest energy lies outside of range of lowest value. # we decrease the lattice parameter in the next # iteration to find a minimum. This applies only when # there are at least 3 values. x = sorted_x[0] - abs(sorted_x[1] - sorted_x[0]) logger.info("Lowest energy lies below bounds. " "Setting %s = %f." % (lattice_direction, x)) elif ind == len(sorted_x) - 1 and len(sorted_x) > 2: # Lowest energy lies outside of range of highest value. # we increase the lattice parameter in the next # iteration to find a minimum. This applies only when # there are at least 3 values. x = sorted_x[-1] + abs(sorted_x[-1] - sorted_x[-2]) logger.info("Lowest energy lies above bounds. " "Setting %s = %f." % (lattice_direction, x)) else: if algo.lower() == "bfgs" and len(sorted_x) >= 4: try: # If there are more than 4 data points, we will # do a quadratic fit to accelerate convergence. x1 = list(energies.keys()) y1 = [energies[j] for j in x1] z1 = np.polyfit(x1, y1, 2) pp = np.poly1d(z1) from scipy.optimize import minimize result = minimize( pp, min_x, bounds=[(sorted_x[0], sorted_x[-1])]) if (not result.success) or result.x[0] < 0: raise ValueError( "Negative lattice constant!") x = result.x[0] logger.info("BFGS minimized %s = %f." % (lattice_direction, x)) except ValueError as ex: # Fall back on bisection algo if the bfgs fails. logger.info(str(ex)) x = (min_x + sorted_x[other]) / 2 logger.info("Falling back on bisection %s = %f." % (lattice_direction, x)) else: x = (min_x + sorted_x[other]) / 2 logger.info("Bisection %s = %f." % (lattice_direction, x)) lattice = lattice.matrix lattice[lattice_index] = lattice[lattice_index] / \ np.linalg.norm(lattice[lattice_index]) * x s = Structure(lattice, structure.species, structure.frac_coords) fname = "POSCAR.%f" % x s.to(filename=fname) incar_update = {"ISTART": 1, "NSW": nsw, "ISIF": 2} settings = [ {"dict": "INCAR", "action": {"_set": incar_update}}, {"file": fname, "action": {"_file_copy": {"dest": "POSCAR"}}}] logger.info("Generating job = %d with parameter %f!" % (i + 1, x)) yield VaspJob(vasp_cmd, final=False, backup=backup, suffix=".static.%f" % x, settings_override=settings, **vasp_job_kwargs) with open("EOS.txt", "wt") as f: f.write("# %s energy\n" % lattice_direction) for k in sorted(energies.keys()): f.write("%f %f\n" % (k, energies[k]))
def run_mcsqs( structure: Structure, clusters: Dict[int, float], scaling: Union[int, List[int]], search_time: float = 0.01, ) -> Optional[Sqs]: """ Helper function for calling mcsqs with different arguments Args: structure (Structure): disordered pymatgen Structure object clusters (dict): dictionary of cluster interactions with entries in the form number of atoms: cutoff in angstroms scaling (int or list): scaling factor to determine supercell. Two options are possible: a. (preferred) Scales number of atoms, e.g., for a structure with 8 atoms, scaling=4 would lead to a 32 atom supercell b. An sequence of three scaling factors, e.g., [2, 1, 1], which specifies that the supercell should have dimensions 2a x b x c search_time (int): The time spent looking for the ideal SQS in minutes Returns: Tuple of Pymatgen structure, which is an SQS of the input structure, and the mcsqs objective function """ num_atoms = len(structure) if structure.is_ordered: raise ValueError("Pick a disordered structure") with ScratchDir("."): if isinstance(scaling, (int, float)): if scaling % 1: raise ValueError( "Scaling should be an integer, not {}".format(scaling)) mcsqs_find_sqs_cmd = ["mcsqs", "-n {}".format(scaling * num_atoms)] else: # Set supercell to identity (will make supercell with pymatgen) with open("sqscell.out", "w") as f: f.write("1\n" "1 0 0\n" "0 1 0\n" "0 0 1\n") structure = structure * scaling mcsqs_find_sqs_cmd = ["mcsqs", "-rc", "-n {}".format(num_atoms)] structure.to(filename="rndstr.in") # Generate clusters mcsqs_generate_clusters_cmd = ["mcsqs"] for num in clusters: mcsqs_generate_clusters_cmd.append("-" + str(num) + "=" + str(clusters[num])) # Run mcsqs to find clusters p = subprocess.Popen(mcsqs_generate_clusters_cmd) p.communicate() # Run mcsqs to find sqs structure p = subprocess.Popen(mcsqs_find_sqs_cmd) try: p.communicate(timeout=search_time * 60) raise Exception("mcsqs exited before timeout reached") except subprocess.TimeoutExpired: p.kill() p.communicate() if os.path.exists("bestsqs.out") and os.path.exists( "bestcorr.out"): # Convert output sqs structure to cif file p = subprocess.Popen("str2cif < bestsqs.out > bestsqs.cif", shell=True) p.communicate() # Get objective function with open('bestcorr.out', 'r') as f: lines = f.readlines() objective_function = float(lines[-1].split('=')[-1].strip()) return Sqs(bestsqs=Structure.from_file("bestsqs.cif"), objective_function=objective_function) else: raise TimeoutError("Cluster expansion took too long.")
def constrained_opt_run(cls, vasp_cmd, lattice_direction, initial_strain, atom_relax=True, max_steps=20, algo="bfgs", **vasp_job_kwargs): """ Returns a generator of jobs for a constrained optimization run. Typical use case is when you want to approximate a biaxial strain situation, e.g., you apply a defined strain to a and b directions of the lattice, but allows the c-direction to relax. Some guidelines on the use of this method: i. It is recommended you do not use the Auto kpoint generation. The grid generated via Auto may fluctuate with changes in lattice param, resulting in numerical noise. ii. Make sure your EDIFF/EDIFFG is properly set in your INCAR. The optimization relies on these values to determine convergence. Args: vasp_cmd (str): Command to run vasp as a list of args. For example, if you are using mpirun, it can be something like ["mpirun", "pvasp.5.2.11"] lattice_direction (str): Which direction to relax. Valid values are "a", "b" or "c". initial_strain (float): An initial strain to be applied to the lattice_direction. This can usually be estimated as the negative of the strain applied in the other two directions. E.g., if you apply a tensile strain of 0.05 to the a and b directions, you can use -0.05 as a reasonable first guess for initial strain. atom_relax (bool): Whether to relax atomic positions. max_steps (int): The maximum number of runs. Defaults to 20 ( highly unlikely that this limit is ever reached). algo (str): Algorithm to use to find minimum. Default is "bfgs", which is fast, but can be sensitive to numerical noise in energy calculations. The alternative is "bisection", which is more robust but can be a bit slow. The code does fall back on the bisection when bfgs gives a non-sensical result, e.g., negative lattice params. \*\*vasp_job_kwargs: Passthrough kwargs to VaspJob. See :class:`custodian.vasp.jobs.VaspJob`. Returns: Generator of jobs. At the end of the run, an "EOS.txt" is written which provides a quick look at the E vs lattice parameter. """ nsw = 99 if atom_relax else 0 incar = Incar.from_file("INCAR") # Set the energy convergence criteria as the EDIFFG (if present) or # 10 x EDIFF (which itself defaults to 1e-4 if not present). if incar.get("EDIFFG") and incar.get("EDIFFG") > 0: etol = incar["EDIFFG"] else: etol = incar.get("EDIFF", 1e-4) * 10 if lattice_direction == "a": lattice_index = 0 elif lattice_direction == "b": lattice_index = 1 else: lattice_index = 2 energies = {} for i in range(max_steps): if i == 0: settings = [{ "dict": "INCAR", "action": { "_set": { "ISIF": 2, "NSW": nsw } } }] structure = Poscar.from_file("POSCAR").structure x = structure.lattice.abc[lattice_index] backup = True else: backup = False v = Vasprun("vasprun.xml") structure = v.final_structure energy = v.final_energy lattice = structure.lattice x = lattice.abc[lattice_index] energies[x] = energy if i == 1: x *= (1 + initial_strain) else: # Sort the lattice parameter by energies. min_x = min(energies.keys(), key=lambda e: energies[e]) sorted_x = sorted(energies.keys()) ind = sorted_x.index(min_x) if ind == 0: other = ind + 1 elif ind == len(sorted_x) - 1: other = ind - 1 else: other = ind + 1 \ if energies[sorted_x[ind + 1]] \ < energies[sorted_x[ind - 1]] \ else ind - 1 if abs(energies[min_x] - energies[sorted_x[other]]) < etol: logger.info("Stopping optimization! Final %s = %f" % (lattice_direction, min_x)) break if ind == 0 and len(sorted_x) > 2: # Lowest energy lies outside of range of lowest value. # we decrease the lattice parameter in the next # iteration to find a minimum. This applies only when # there are at least 3 values. x = sorted_x[0] - abs(sorted_x[1] - sorted_x[0]) logger.info("Lowest energy lies below bounds. " "Setting %s = %f." % (lattice_direction, x)) elif ind == len(sorted_x) - 1 and len(sorted_x) > 2: # Lowest energy lies outside of range of highest value. # we increase the lattice parameter in the next # iteration to find a minimum. This applies only when # there are at least 3 values. x = sorted_x[-1] + abs(sorted_x[-1] - sorted_x[-2]) logger.info("Lowest energy lies above bounds. " "Setting %s = %f." % (lattice_direction, x)) else: if algo.lower() == "bfgs" and len(sorted_x) >= 4: try: # If there are more than 4 data points, we will # do a quadratic fit to accelerate convergence. x1 = list(energies.keys()) y1 = [energies[j] for j in x1] z1 = np.polyfit(x1, y1, 2) pp = np.poly1d(z1) from scipy.optimize import minimize result = minimize(pp, min_x, bounds=[(sorted_x[0], sorted_x[-1])]) if (not result.success) or result.x[0] < 0: raise ValueError( "Negative lattice constant!") x = result.x[0] logger.info("BFGS minimized %s = %f." % (lattice_direction, x)) except ValueError as ex: # Fall back on bisection algo if the bfgs fails. logger.info(str(ex)) x = (min_x + sorted_x[other]) / 2 logger.info( "Falling back on bisection %s = %f." % (lattice_direction, x)) else: x = (min_x + sorted_x[other]) / 2 logger.info("Bisection %s = %f." % (lattice_direction, x)) lattice = lattice.matrix lattice[lattice_index] = lattice[lattice_index] / \ np.linalg.norm(lattice[lattice_index]) * x s = Structure(lattice, structure.species, structure.frac_coords) fname = "POSCAR.%f" % x s.to(filename=fname) incar_update = {"ISTART": 1, "NSW": nsw, "ISIF": 2} settings = [{ "dict": "INCAR", "action": { "_set": incar_update } }, { "file": fname, "action": { "_file_copy": { "dest": "POSCAR" } } }] logger.info("Generating job = %d with parameter %f!" % (i + 1, x)) yield VaspJob(vasp_cmd, final=False, backup=backup, suffix=".static.%f" % x, settings_override=settings, **vasp_job_kwargs) with open("EOS.txt", "wt") as f: f.write("# %s energy\n" % lattice_direction) for k in sorted(energies.keys()): f.write("%f %f\n" % (k, energies[k]))
import math from pymatgen import Structure, Molecule, Lattice import os os.chdir( '/home/jinho93/oxides/perobskite/lanthanum-aluminate/slab/gulp/nonstochio/La-vac/two' ) m = Molecule.from_file('tail.xyz') l = Lattice.from_lengths_and_angles([11.46615, 15.2882, 50], [90, 90, 90]) s = Structure(l, m.species, m.cart_coords, coords_are_cartesian=True) s.make_supercell([[4, 0, 0], [0, 3, 0], [0, 0, 1]]) s.make_supercell([[1, 1, 0], [1, -1, 0], [0, 0, 1]]) s.sort() # ll = Lattice.from_lengths_and_angles([s.lattice.a / 2, s.lattice.b / 2, s.lattice.c], s.lattice.angles) ll = Lattice.from_lengths_and_angles(s.lattice.abc, s.lattice.angles) s = Structure(ll, s.species, s.cart_coords, coords_are_cartesian=True) indi = [] for i, site in enumerate(s.sites): if site.x + site.y < ll.b / math.sqrt(2): indi.append(i) # s.remove_sites(indi) # s = Structure(ll, s.species, s.cart_coords, coords_are_cartesian=True) # s.to('POSCAR', 'POSCAR') s.to('POSCAR', 'CONTCAR')
for i, struct in enumerate([struct1, struct2]): position, ev = get_fragment_position_and_orientation(struct, [1] * 18) print_xyz(coordinates=get_dipole_in_basis( np.array(coordinates_monomer).T, ev_dipole, ev).T + np.array([position] * 18), symbols=symbols_monomer) print('Molecule {}\n---------------'.format(i + 1)) params = get_rotation_angles(ev) print('orientation: {} {} {}'.format(*params)) print('position', position) # store structure in file struct_c.to(filename='naphtalene.cif') struct_c.to(filename='POSCAR') scale_factor = 50 molecule = Molecule( states=[State(label='gs', energy=0.0), State(label='s1', energy=1.0)], transition_moment={ ('s1', 'gs'): dipole * scale_factor, ('s2', 'gs'): dipole2 * scale_factor }, # transition dipole moment of the molecule (Debye) ) system = crystal_system(conditions={}, molecules=[molecule], scaled_site_coordinates=[position],
#%% from pymatgen import Structure, Molecule from pymatgen.io.xyz import XYZ import os os.chdir( '/home/jinho93/new/oxides/perobskite/lanthanum-aluminate/periodic_step/vasp/stengel/013/from_gulp/all' ) os.chdir( '/home/jinho93/new/oxides/perobskite/lanthanum-aluminate/periodic_step/gulp/013/all' ) xyz = XYZ.from_file('lao.xyz') tmp = Structure.from_file('POSCAR') for m in xyz.all_molecules[:1]: s = Structure(tmp.lattice, m.species, m.cart_coords, coords_are_cartesian=True) prim = s.get_primitive_structure(1e-1) if len(prim.sites) < 540: print(len(prim.sites)) out_str = s s.to('POSCAR', 'NPOSCAR') print(len(s.sites)) print(len(prim.sites))
# twod file path twod_file = '../test_files/POSCAR_2D' sub = Structure.from_file(sub_file) twod = Structure.from_file(twod_file) # provide input match constraints as a dictionary match_constraints = { 'max_area': 90, 'max_mismatch': 0.06, 'max_angle_diff': 2, 'r1r2_tol': 0.04, 'separation': 2.2, 'nlayers_substrate': 1, 'nlayers_2d': 1, 'sd_layers': 0, 'best_match': 'area' } ######################################### # Lowest area (default) or minimum uv mismatched lattice match # is returned based on 'best_match' option in match_constraints # best_match should be either 'area' or 'mismatch' iface, n_sub, z_ub = transformations.run_lat_match(sub, twod, match_constraints) if iface is None: print('Current twod lattice matches with substrate. No changes needed!') else: strct = Structure(iface.lattice, iface.species, iface.frac_coords) strct.to(filename='../test_files/POSCAR_iface', fmt='poscar')
def run_mcsqs( structure: Structure, clusters: Dict[int, float], scaling: Union[int, List[int]] = 1, search_time: float = 60, directory: Optional[str] = None, instances: Optional[int] = None, temperature: Union[int, float] = 1, wr: float = 1, wn: float = 1, wd: float = 0.5, tol: float = 1e-3, ) -> Sqs: """ Helper function for calling mcsqs with different arguments Args: structure (Structure): Disordered pymatgen Structure object clusters (dict): Dictionary of cluster interactions with entries in the form number of atoms: cutoff in angstroms scaling (int or list): Scaling factor to determine supercell. Two options are possible: a. (preferred) Scales number of atoms, e.g., for a structure with 8 atoms, scaling=4 would lead to a 32 atom supercell b. A sequence of three scaling factors, e.g., [2, 1, 1], which specifies that the supercell should have dimensions 2a x b x c Defaults to 1. search_time (float): Time spent looking for the ideal SQS in minutes (default: 60) directory (str): Directory to run mcsqs calculation and store files (default: None runs calculations in a temp directory) instances (int): Specifies the number of parallel instances of mcsqs to run (default: number of cpu cores detected by Python) temperature (int or float): Monte Carlo temperature (default: 1), "T" in atat code wr (int or float): Weight assigned to range of perfect correlation match in objective function (default = 1) wn (int or float): Multiplicative decrease in weight per additional point in cluster (default: 1) wd (int or float): Exponent of decay in weight as function of cluster diameter (default: 0.5) tol (int or float): Tolerance for matching correlations (default: 1e-3) Returns: Tuple of Pymatgen structure SQS of the input structure, the mcsqs objective function, list of all SQS structures, and the directory where calculations are run """ num_atoms = len(structure) if structure.is_ordered: raise ValueError("Pick a disordered structure") if instances is None: # os.cpu_count() can return None if detection fails instances = os.cpu_count() original_directory = os.getcwd() if not directory: directory = tempfile.mkdtemp() os.chdir(directory) if isinstance(scaling, (int, float)): if scaling % 1: raise ValueError("Scaling should be an integer, not {}".format(scaling)) mcsqs_find_sqs_cmd = ["mcsqs", "-n {}".format(scaling * num_atoms)] else: # Set supercell to identity (will make supercell with pymatgen) with open("sqscell.out", "w") as f: f.write("1\n1 0 0\n0 1 0\n0 0 1\n") structure = structure * scaling mcsqs_find_sqs_cmd = ["mcsqs", "-rc", "-n {}".format(num_atoms)] structure.to(filename="rndstr.in") # Generate clusters mcsqs_generate_clusters_cmd = ["mcsqs"] for num in clusters: mcsqs_generate_clusters_cmd.append("-" + str(num) + "=" + str(clusters[num])) # Run mcsqs to find clusters p = Popen(mcsqs_generate_clusters_cmd) p.communicate() # Generate SQS structures add_ons = [ "-T {}".format(temperature), "-wr {}".format(wr), "-wn {}".format(wn), "-wd {}".format(wd), "-tol {}".format(tol), ] mcsqs_find_sqs_processes = [] if instances and instances > 1: # if multiple instances, run a range of commands using "-ip" for i in range(instances): instance_cmd = ["-ip {}".format(i + 1)] cmd = mcsqs_find_sqs_cmd + add_ons + instance_cmd p = Popen(cmd) mcsqs_find_sqs_processes.append(p) else: # run normal mcsqs command cmd = mcsqs_find_sqs_cmd + add_ons p = Popen(cmd) mcsqs_find_sqs_processes.append(p) try: for idx, p in enumerate(mcsqs_find_sqs_processes): p.communicate(timeout=search_time * 60) if instances and instances > 1: p = Popen(["mcsqs", "-best"]) p.communicate() if os.path.exists("bestsqs.out") and os.path.exists("bestcorr.out"): return _parse_sqs_path(".") raise RuntimeError("mcsqs exited before timeout reached") except TimeoutExpired: for p in mcsqs_find_sqs_processes: p.kill() p.communicate() # Find the best sqs structures if instances and instances > 1: if not os.path.exists("bestcorr1.out"): raise RuntimeError( "mcsqs did not generate output files, " "is search_time sufficient or are number of instances too high?" ) p = Popen(["mcsqs", "-best"]) p.communicate() if os.path.exists("bestsqs.out") and os.path.exists("bestcorr.out"): sqs = _parse_sqs_path(".") return sqs os.chdir(original_directory) raise TimeoutError("Cluster expansion took too long.")
def cry2cif(filename, to="cif", center=False, sortx=False, sortz=False, b_dum=50, c_dum=50, istruct=-1): """ Read a CRYSTAL output file and return the structure in a cif or POSCAR format. Args: filename (str): crystal output filename to (str): 'cif' or 'vasp', format of the output file (default is cif) center (bool): if True, the slab or nanotube is translated to the center of the box (default is False) sortx (bool): Nanotube : if True, atoms are sorted along x axes (default is False). sortz (bool): slab : if True, atoms are sorted along z axes (default is False). b_dum (float): dummy lattice paramters b in angstrom for nanotubes (default 50 A) c_dum (float): dummy lattice paramters c in angstrom for nanotubes and slabs (default 50 A) istruct (int): structure to be extracted """ cryout = CrystalOutfile(filename) print("title : ", cryout.title) if cryout.group: print("group : ", cryout.group) # print("Number of structure read: ", len(cryout.structures)) if istruct == -1: print("structure : Final structure") structure = cryout.final_structure else: print("structure : Structure %d" % istruct) structure = cryout.get_structure(istruct) print("# atom : ", structure.num_sites) print("composition: ", structure.composition) print("Cell parameters:") print("a : %10.4f" % structure.lattice.a) print("b : %10.4f" % structure.lattice.b) print("c : %10.4f" % structure.lattice.c) print("alpha : %10.4f" % structure.lattice.alpha) print("beta : %10.4f" % structure.lattice.beta) print("gamma : %10.4f" % structure.lattice.gamma) # ---------------------------------------------------------- # New b and c axes # ---------------------------------------------------------- if cryout.slab: frac_coords = structure.frac_coords frac_coords[:, 2] *= structure.lattice.c / c_dum matrix = structure.lattice.matrix matrix[2, 2] = c_dum structure = Structure(Lattice(matrix), structure.species, frac_coords) if cryout.nanotube: frac_coords = structure.frac_coords frac_coords[:, 1] *= structure.lattice.c / c_dum frac_coords[:, 2] *= structure.lattice.b / b_dum matrix = structure.lattice.matrix matrix[1, 1] = b_dum matrix[2, 2] = c_dum structure = Structure(Lattice(matrix), structure.species, frac_coords) # ---------------------------------------------------------- # move slab or nanotube to the center of the box # ---------------------------------------------------------- if center: if cryout.slab: coords = structure.frac_coords.copy() coords[:, 2] += .5 structure = Structure(structure.lattice, structure.species, coords) elif cryout.nanotube: coords = structure.frac_coords coords += .5 structure = Structure(structure.lattice, structure.species, coords) # ---------------------------------------------------------- # sort atom along x or z axis for slab # ---------------------------------------------------------- if sortz: isort = 2 elif sortx: isort = 0 axes = {2: "z", 0: "x"} if sortz or sortx: print("\nSort atoms along %s" % axes[isort]) data = zip(structure.species, structure.coords) data = sorted(data, key=lambda d: d[-1][isort], reverse=True) species = [d[0] for d in data] coords = [d[1] for d in data] structure = Structure(structure.lattice, species, coords) # ---------------------------------------------------------- # export in the given format # ---------------------------------------------------------- basename, _ = os.path.splitext(filename) if to.lower() == "cif": ext = ".cif" elif to.lower() == "vasp": to = "POSCAR" ext = ".vasp" else: to = "POSCAR" ext = ".vasp" structure.to(to, filename=basename + ext)
#%% from pymatgen import Molecule, Structure import os import numpy as np from pymatgen.core.lattice import Lattice os.chdir('/home/jinho93/oxides/perobskite/lanthanum-aluminate/slab/nvt.para.6/island/len-19/step1') s = Molecule.from_file('POSCAR.xyz') sp = [] coords = [] for i in s.sites: if i.x < 7 and i.y < 7: sp.append(i.specie) coords.append(i.coords) new = Structure(Lattice(np.identity(3) * 7.648200),sp, coords, coords_are_cartesian=True) new.to('POSCAR', 'ini_pot/reduced') # %% #%% from pymatgen.command_line.gulp_caller import GulpIO gio = GulpIO() gio.buckingham_input(new, keywords=['conv'], uc=False) # %%
def ripple(structs, fnames, supercell, strain_range, atom_index, auto_fix, margin_dist=1.0): ''' build ripple structures and strained ones Args: structs: list of Structure obj. fnames: list of str for input file name. supercell: list, used to build supercell along x or y direction. strain_range: list of strain atom_index: int , specify which atom will be constrained auto_fix: boolean, whether decide which atoms to be constrained automaticly margin_dist: float, Returns: None Output: 0.04000_wo_xxxx.vasp 0.04000_w_xxxx.vasp ''' for struct, fname in zip(structs, fnames): if not struct.lattice.is_orthogonal: warn_tip(0, "{} is not orthogonal, skip it !!!".format(fname)) continue struct_sc = struct.copy() struct_sc.make_supercell(supercell) natom = struct_sc.num_sites min_z = np.min(struct_sc.cart_coords[:, 2]) tmp_coords = np.ones((struct_sc.num_sites, 1)) * min_z cart_coords = struct_sc.cart_coords cart_coords[:, 2] = cart_coords[:, 2] - tmp_coords.T + 0.01 frac_coords_new = np.dot( cart_coords, np.linalg.inv(struct_sc.lattice.matrix.copy())) for i_strain in strain_range: new_lat_matrix = struct_sc.lattice.matrix.copy() new_lat_matrix[direction, direction] = struct_sc.lattice.matrix[ direction, direction] * (1 - i_strain) # structure only applied with in-plan strain filename = "%10.5f" % (i_strain) + '_wo_' + fname + '.vasp' struct_wo_ripple = Structure(new_lat_matrix, struct_sc.species, frac_coords_new) struct_wo_ripple.to(filename=filename.strip(), fmt='poscar') frac_coords_new_cp = struct_wo_ripple.frac_coords.copy() cart_coords_new_cp = struct_wo_ripple.cart_coords.copy() nz = 0 selective_dynamics = [[True for col in range(3)] for row in range(natom)] z_shift = np.zeros((natom, 3)) for i_atom in range(natom): #To do : z_shit should support more function z_shift[i_atom, 2] = 40 * (2 * i_strain - 10 * i_strain**2) * np.sin( cart_coords_new_cp[i_atom, direction] * np.pi / new_lat_matrix[direction, direction]) if cart_coords_new_cp[i_atom, direction] < nz or cart_coords_new_cp[ i_atom, direction] > new_lat_matrix[ direction, direction] - nz: z_shift[i_atom, 2] = 0.0 if auto_fix: if struct_wo_ripple[i_atom].coords[direction]<margin_dist or \ struct_wo_ripple[i_atom].coords[direction] > new_lat_matrix[direction,direction]-margin_dist: selective_dynamics[i_atom] = [False, False, False] else: if i_atom in atom_index: selective_dynamics[i_atom] = [False, False, False] struct_w_ripple=Structure(new_lat_matrix,struct_sc.species,cart_coords_new_cp+z_shift,coords_are_cartesian=True,\ site_properties={'selective_dynamics':selective_dynamics}) # structure applied with in-plan strain and ripple filename = "%10.5f" % (i_strain) + '_w_' + fname + '.vasp' struct_w_ripple.to(filename=filename.strip(), fmt='poscar')
# ax1.set_xlim((120,140)) # %% from pymatgen.io.vasp import Xdatcar xdat = Xdatcar.from_file('XDATCAR') xdat.structures[70].to('POSCAR', 'NEWPOSCAR') # %% from pymatgen import Structure coords = [] s: Structure for s in xdat.structures[70:]: coords.append(s.frac_coords) coords = np.array(coords) #%% aver = np.sum(coords, axis=0) / coords.shape[0] print(aver.shape) poscar = Structure.from_file('POSCAR') new = Structure(poscar.lattice, poscar.species, aver) new.to('POSCAR', 'NEWPOSCAR') # np.sum(coords) # %% new = dip[loop] new = new[280:] plt.scatter(range(len(new)), new) # %%