def get_trajectory(self): # outvars_after_computation_line = 0 for i in range(len(self.lines)): if len(self.lines[i].split()) > 0 and self.lines[i].split( )[0] == "-outvars:" and self.lines[i].split()[5] == "after": outvars_after_computation_line = i # self.trajectory_final = [] for i in range(outvars_after_computation_line, len(self.lines)): if len(self.lines[i].split()) > 0 and self.lines[i].split( )[0].split("_")[0] == "xangst": atm = [] # doesn't know name now atm.append( Atom("XXX", float(self.lines[i].split()[1]), float(self.lines[i].split()[2]), float(self.lines[i].split()[3]))) j = i + 1 while len(self.lines[j].split()) == 3: atm.append( Atom("XXX", float(self.lines[j].split()[0]), float(self.lines[j].split()[1]), float(self.lines[j].split()[2]))) j = j + 1 self.trajectory_final.append(atm) # outvars_before_computation_line = 0 for i in range(len(self.lines)): if len(self.lines[i].split()) > 0 and self.lines[i].split( )[0] == "-outvars:" and self.lines[i].split()[5] == "input": outvars_before_computation_line = i # self.trajectory_initial = [] for i in range(outvars_before_computation_line, len(self.lines)): if len(self.lines[i].split()) > 0 and self.lines[i].split( )[0].split("_")[0] == "xangst": atm = [] # doesn't know name now atm.append( Atom("XXX", float(self.lines[i].split()[1]), float(self.lines[i].split()[2]), float(self.lines[i].split()[3]))) j = i + 1 while len(self.lines[j].split()) == 3: atm.append( Atom("XXX", float(self.lines[j].split()[0]), float(self.lines[j].split()[1]), float(self.lines[j].split()[2]))) j = j + 1 self.trajectory_initial.append(atm)
def enlarge_atoms_new_cell(structure, new_cell): """ :return out: atoms: [ ["C", 0.00000, 0.000000, 0.0000], ["O", 0.00000, 0.500000, 0.0000], ... ] Note: will enlarge the atoms in the unit cell along both a, b, c and -a, -b, -c direction of the new_cell !!! but the cell is not redefined, the returned atoms is not used to form crystal, but to be tailored by redefine_lattice function to get atoms for the redfined lattice. The goal is to make sure when the new cell rotate in the 3D space, it will always be filled with atoms. """ from pymatflow.base.atom import Atom # cell = copy.deepcopy(structure.cell) a = np.linalg.norm(cell[0]) b = np.linalg.norm(cell[1]) c = np.linalg.norm(cell[2]) new_a = np.linalg.norm(new_cell[0]) new_b = np.linalg.norm(new_cell[1]) new_c = np.linalg.norm(new_cell[2]) n1 = np.ceil( np.max([new_a, new_b, new_c]) / a) * 2 # maybe times 2 is not needed n2 = np.ceil(np.max([new_a, new_b, new_c]) / b) * 2 n3 = np.ceil(np.max([new_a, new_b, new_c]) / c) * 2 n = [int(n1), int(n2), int(n3)] print("n1 n2 n3: %d %d %d\n" % (n1, n2, n3)) atoms = copy.deepcopy(structure.atoms) print("atoms.size(): %d\n" % len(atoms)) # build supercell: replica in three vector one by one for i in range(3): natom_now = len(atoms) for j in range(n[i] - 1): for atom in atoms[:natom_now]: x = atom.x + float(j + 1) * structure.cell[i][0] y = atom.y + float(j + 1) * structure.cell[i][1] z = atom.z + float(j + 1) * structure.cell[i][2] atoms.append(Atom(atom.name, x, y, z)) # replicate in the negative direction of structure.cell[i] for atom in atoms[:natom_now * n[i]]: x = atom.x - float(n[i]) * structure.cell[i][0] y = atom.y - float(n[i]) * structure.cell[i][1] z = atom.z - float(n[i]) * structure.cell[i][2] atoms.append(Atom(atom.name, x, y, z)) return [[atom.name, atom.x, atom.y, atom.z] for atom in atoms]
def get_structure_from_url(url): from pymatflow.structure.crystal import crystal from pymatflow.base.atom import Atom result = _get_from_url(url) out = [] for item in result["data"]: a = crystal() a.cell = item["unit_cell"] latcell = np.array(a.cell) convmat_frac_to_cartesian = latcell.T a.atoms = [] for atm in item["sites"]: name = atm.split()[0] cartesian = list( convmat_frac_to_cartesian.dot( np.array([ float(atm.split()[2]), float(atm.split()[3]), float(atm.split()[4]) ]))) a.atoms.append( Atom(name=name, x=cartesian[0], y=cartesian[1], z=cartesian[2])) out.append(a) return out
def build_supercell(self, n): """ :param n: [n1, n2, n3] :return out: { "cell": [[], [], []], "atoms": [ ["C", 0.00000, 0.000000, 0.0000], ["O", 0.00000, 0.500000, 0.0000], ... ] } Note: will not affect status of self """ # cell = copy.deepcopy(self.cell) for i in range(3): for j in range(3): cell[i][j] = n[i] * self.cell[i][j] atoms = copy.deepcopy(self.atoms) # build supercell: replica in three vector one by one for i in range(3): natom_now = len(atoms) for j in range(n[i] - 1): for atom in atoms[:natom_now]: x = atom.x + float(j + 1) * self.cell[i][0] y = atom.y + float(j + 1) * self.cell[i][1] z = atom.z + float(j + 1) * self.cell[i][2] atoms.append(Atom(atom.name, x, y, z)) return { "cell": cell, "atoms": [[atom.name, atom.x, atom.y, atom.z] for atom in atoms] }
def interpolate(initial, final, nimage, moving_atom): """ :param initial -> instance of pymatflow.structure.crystal.crystal() :param final -> instance of pymatflow.structure.crystal.crystal() :param nimage -> number of intermediate images(not including initial and final image) :param moving_atom -> the index of moving atoms (index starts from 0) :return images -> a list of instance of pymatflow.structure.crystal.crystal() as inter images Feature: only interpolate(linearly) the specified moving atoms. other atoms may have different positions between initial and final image, however we use the coordinate of the initial image as the cooresponding coordinate for the inter image. """ # use fractional coordinates to interpolate the images initial.natom = len(initial.atoms) final.natom = len(final.atoms) initial_frac = initial.get_fractional() final_frac = final.get_fractional() #images = [] images_frac = [] for i in range(nimage): #images.append(copy.deepcopy(initial)) images_frac.append(copy.deepcopy(initial_frac)) for i in moving_atom: dx = final_frac[i][1] - initial_frac[i][1] dy = final_frac[i][2] - initial_frac[i][2] dz = final_frac[i][3] - initial_frac[i][3] for j in range(nimage): x = initial_frac[i][1] + j * dx / (nimage + 1) y = initial_frac[i][2] + j * dy / (nimage + 1) z = initial_frac[i][3] + j * dz / (nimage + 1) #print("fractional x, y, z: %f %f %f\n" % (x, y, z)) images_frac[j][i][1] = x images_frac[j][i][2] = y images_frac[j][i][3] = z # transform from images_frac to images # convert frac to cartesian again images = [] latcell = np.array(initial.cell) convmat = latcell.T from pymatflow.base.atom import Atom from pymatflow.structure.crystal import crystal for i in range(nimage): img = crystal() img.atoms = [] img.cell = initial.cell for atom in images_frac[i]: cartesian = list(convmat.dot(np.array([atom[1], atom[2], atom[3]]))) img.atoms.append( Atom(name=atom[0], x=cartesian[0], y=cartesian[1], z=cartesian[2])) images.append(img) # return images
def enlarge_atoms(structure): """ :return out: atoms: [ ["C", 0.00000, 0.000000, 0.0000], ["O", 0.00000, 0.500000, 0.0000], ... ] Note: will enlarge the atoms in the unit cell along both a, b, c and -a, -b, -c direction. The goal is to make sure when the cell rotate in the 3D space, it will always be filled with atoms. """ from pymatflow.base.atom import Atom # cell = copy.deepcopy(structure.cell) a = np.linalg.norm(cell[0]) b = np.linalg.norm(cell[1]) c = np.linalg.norm(cell[2]) n1 = np.ceil(np.max([a, b, c]) / a) * 2 # maybe times 2 is not needed n2 = np.ceil(np.max([a, b, c]) / b) * 2 n3 = np.ceil(np.max([a, b, c]) / c) * 2 n = [int(n1), int(n2), int(n3)] print(n) atoms = copy.deepcopy(structure.atoms) # build supercell: replica in three vector one by one for i in range(3): natom_now = len(atoms) for j in range(n[i] - 1): for atom in atoms[:natom_now]: x = atom.x + float(j + 1) * structure.cell[i][0] y = atom.y + float(j + 1) * structure.cell[i][1] z = atom.z + float(j + 1) * structure.cell[i][2] atoms.append(Atom(atom.name, x, y, z)) # replicate in the negative direction of structure.cell[i] for atom in atoms[:natom_now * n[i]]: x = atom.x - float(n[i]) * structure.cell[i][0] y = atom.y - float(n[i]) * structure.cell[i][1] z = atom.z - float(n[i]) * structure.cell[i][2] atoms.append(Atom(atom.name, x, y, z)) return [[atom.name, atom.x, atom.y, atom.z] for atom in atoms]
def get_final_structure_from_aurl(aurl): """ Note: get final structure(relaxed using vasp) from aurl, aurl is in format like this: aflowlib.duke.edu:AFLOWDATA/LIB3_RAW/Bi_dRh_pvTi_sv/T0003.ABC:LDAU2 aflowlib.duke.edu:AFLOWDATA/ICSD_WEB/CUB/Cl1Na1_ICSD_622368 """ out = crystal() geometry = urlopen("http://" + aurl.replace(":AFLOWDATA", "/AFLOWDATA") + "/?geometry").read().decode("utf-8") a, b, c, alpha, beta, gamma = [ float(item) for item in geometry.replace("\n", "").split(",") ] alpha = alpha / 180 * np.pi beta = beta / 180 * np.pi gamma = gamma / 180 * np.pi out.cell = [] out.cell.append([a, 0, 0]) out.cell.append([np.cos(gamma) * b, np.sin(gamma) * b, 0]) new_c1 = np.cos(beta) new_c2 = (np.cos(alpha - np.cos(beta) * np.cos(gamma))) / np.sin(gamma) new_c3_square = 1.0 - new_c1 * new_c1 - new_c2 * new_c2 new_c3 = np.sqrt(new_c3_square) out.cell.append([new_c1 * c, new_c2 * c, new_c3 * c]) elements_list = [] composition = urlopen("http://" + aurl.replace(":AFLOWDATA", "/AFLOWDATA") + "/?composition").read().decode("utf-8") species = urlopen("http://" + aurl.replace(":AFLOWDATA", "/AFLOWDATA") + "/?species").read().decode("utf-8") for i, number in enumerate(composition.replace("\n", "").split(",")): for j in range(int(number)): elements_list.append(species.replace("\n", "").split(",")[i]) # convmat_frac_to_cartesian = np.array(out.cell).T out.atoms = [] positions_fractional = urlopen( "http://" + aurl.replace(":AFLOWDATA", "/AFLOWDATA") + "/?positions_fractional").read().decode("utf-8") for i, item in enumerate( positions_fractional.replace("\n", "").split(";")): cartesian = convmat_frac_to_cartesian.dot( [float(k) for k in item.split(",")]) out.atoms.append( Atom(name=elements_list[i], x=cartesian[0], y=cartesian[1], z=cartesian[2])) # return out
def get_xyz(self, xyzfile): """ get information to construct the structure from an xyz file """ #self.file = xyzfile self.file = os.path.abspath(xyzfile) # now self.file is an absolute path, this is much easier for later usage with open(self.file, 'r') as fin: self.natom = int(fin.readline()) fin.readline() i = 0 while i < self.natom: line = fin.readline() atom = Atom(line.split()[0], float(line.split()[1]), float(line.split()[2]), float(line.split()[3])) # get information about the fix of this atom for opt and md if len(line.split()) == 4: atom.fix = [False, False, False] elif line.split()[4][0] == '#': atom.fix = [False, False, False] # the first char after coordinates is # , so cannpt set T or F else: for j in range(3): if line.split()[j+4] == 'T': atom.fix[j] = True elif line.split()[j+4] == 'F': atom.fix[j] = False else: print("===============================\n") print("warning: while read xyz file\n") print("can only set T or F after coords\n") sys.exit(1) # end get the information about the fix of this atom for opt and md self.atoms.append(atom) i += 1 self.set_species_number() self.cell = self.get_cell(self.file)
def get_atoms(self, atoms): """ :params cell: [[a1, a2, a3], [b1, b2, b3], [c1, c2, c3]] in unit of Anstrom :params atoms (in cartesian coordinates and unit of Anstrom) [ ["C", 0.00000, 0.0000000, 0.0000], ["O", 1.12300, 3.3250000, 2.4893], .... ] """ self.atoms = [ Atom(atoms[i][0], atoms[i][1], atoms[i][2], atoms[i][3]) for i in range(len(atoms)) ]
def set_frac_min_to_zero(structure): """ :return an object of crystal() Note: set the fractional coordinate minimum to zero, this is a way of standardize the cif file """ from pymatflow.structure.crystal import crystal from pymatflow.base.atom import Atom # now calc the fractional coordinates atoms_frac = [] latcell = np.array(structure.cell) convmat = np.linalg.inv(latcell.T) for i in range(len(structure.atoms)): atom = [] atom.append(structure.atoms[i].name) atom = atom + list( convmat.dot( np.array([ structure.atoms[i].x, structure.atoms[i].y, structure.atoms[i].z ]))) atoms_frac.append(atom) # set the minimum of fractional coord to to 0 min_frac_x = min(atoms_frac[:][1]) min_frac_y = min(atoms_frac[:][2]) min_frac_z = min(atoms_frac[:][3]) for i in range(len(atoms_frac)): atoms_frac[i][1] -= min_frac_x atoms_frac[i][2] -= min_frac_y atoms_frac[i][3] -= min_frac_z # now convert coord of atom in atoms_frac_within_new_cell to cartesian out = crystal() out.atoms = [] out.cell = structure.cell latcell = np.array(out.cell) convmat_frac_to_cartesian = latcell.T for atom in atoms_frac: cartesian = list( convmat_frac_to_cartesian.dot(np.array([atom[1], atom[2], atom[3]]))) out.atoms.append( Atom(name=atom[0], x=cartesian[0], y=cartesian[1], z=cartesian[2])) # return out
def set_frac_within_zero_and_one(structure): """ :return an object of crystal() Note: set the fractional coordinate within the range of 0 and 1, this is a way of standardize the cif file """ from pymatflow.structure.crystal import crystal from pymatflow.base.atom import Atom # now calc the fractional coordinates atoms_frac = [] latcell = np.array(structure.cell) convmat = np.linalg.inv(latcell.T) for i in range(len(structure.atoms)): atom = [] atom.append(structure.atoms[i].name) atom = atom + list( convmat.dot( np.array([ structure.atoms[i].x, structure.atoms[i].y, structure.atoms[i].z ]))) atoms_frac.append(atom) # set the fractional coordinates within 0 and 1 for i in range(len(atoms_frac)): for j in range(1, 4): while atoms_frac[i][j] >= 1: atoms_frac[i][j] -= 1 while atoms_frac[i][j] < 0: atoms_frac[i][j] += 1 # now convert coord of atom in atoms_frac_within_new_cell to cartesian out = crystal() out.atoms = [] out.cell = structure.cell latcell = np.array(out.cell) convmat_frac_to_cartesian = latcell.T for atom in atoms_frac: cartesian = list( convmat_frac_to_cartesian.dot(np.array([atom[1], atom[2], atom[3]]))) out.atoms.append( Atom(name=atom[0], x=cartesian[0], y=cartesian[1], z=cartesian[2])) # return out
def inverse_cell_center(structure): """ make an inversion against the cell center :param structure: an instance of pymatflow.structure.crystal.crystal() """ # first transfer to fractional coordinate and inverse against [0.5, 0.5, 0.5] structure.natom = len(structure.atoms) frac = structure.get_fractional() for atom in frac: atom[1] = 0.5 * 2 - atom[1] atom[2] = 0.5 * 2 - atom[2] atom[3] = 0.5 * 2 - atom[3] # convert frac to cartesian again latcell = np.array(structure.cell) convmat = latcell.T from pymatflow.base.atom import Atom structure.atoms = [] for atom in frac: cartesian = list(convmat.dot(np.array([atom[1], atom[2], atom[3]]))) structure.atoms.append( Atom(name=atom[0], x=cartesian[0], y=cartesian[1], z=cartesian[2]))
def get_scf_params_and_run_info(self): """ self.run_info[] start_time: the task start time stop_time: the task stop time scf_energies: all the energies during the scf procedure #fermi_energy: fermi energy of the system (if output) """ self.run_info["scf_energies"] = [] for i in range(len(self.lines)): # if it is an empty line continue to next line if len(self.lines[i].split()) == 0: continue if self.lines[i].split()[0] == "Program" and self.lines[i].split()[1] == "PWSCF" and self.lines[i].split()[3] == "starts": self.run_info["start_time"] = self.lines[i].split("\n")[0] elif self.lines[i].split()[0] == "This" and self.lines[i].split()[1] == "run" and self.lines[i].split()[3] == "terminated": self.run_info["stop_time"] = self.lines[i].split("\n")[0] elif self.lines[i].split()[0] == "Parallel" and self.lines[i].split()[-1] == "processors": self.run_info["processors"] = int(self.lines[i].split()[-2]) elif self.lines[i].split()[0] == "MPI" and self.lines[i].split()[-1] == "nodes": self.run_info["nodes"] = int(self.lines[i].split()[-2]) elif self.lines[i].split()[0] == "bravais-lattice" and self.lines[i].split()[1] == "index": self.scf_params["alat_au"] = float(self.lines[i+1].split()[4]) self.scf_params["nat"] = int(self.lines[i+3].split()[4]) self.scf_params["nelectron"] = float(self.lines[i+5].split()[4]) self.scf_params["n_ks_state"] = int(self.lines[i+6].split("=")[1]) self.scf_params["ecutwfc"] = int(float(self.lines[i+7].split()[3])) self.scf_params["ecutrho"] = int(float(self.lines[i+8].split()[4])) self.scf_params["conv_thr"] = float(self.lines[i+9].split()[3]) self.scf_params["mixing_beta"] = float(self.lines[i+10].split()[3]) elif self.lines[i].split()[0] == "crystal" and self.lines[i].split()[1] == "axes:" and self.lines[i].split()[-1] =="alat)": self.scf_params["cell_a_alat"] = [] self.scf_params["cell_a_alat"].append([float(self.lines[i+1].split()[3]), float(self.lines[i+1].split()[4]), float(self.lines[i+1].split()[5])]) self.scf_params["cell_a_alat"].append([float(self.lines[i+2].split()[3]), float(self.lines[i+2].split()[4]), float(self.lines[i+2].split()[5])]) self.scf_params["cell_a_alat"].append([float(self.lines[i+3].split()[3]), float(self.lines[i+3].split()[4]), float(self.lines[i+3].split()[5])]) elif self.lines[i].split()[0] == "reciprocal" and self.lines[i].split()[1] == "axes:" and self.lines[i].split()[-1] == "pi/alat)": # actually '2 pi/alat' self.scf_params["cell_b_2pi_alat"] = [] self.scf_params["cell_b_2pi_alat"].append([float(self.lines[i+1].split()[3]), float(self.lines[i+1].split()[4]), float(self.lines[i+1].split()[5])]) self.scf_params["cell_b_2pi_alat"].append([float(self.lines[i+2].split()[3]), float(self.lines[i+2].split()[4]), float(self.lines[i+2].split()[5])]) self.scf_params["cell_b_2pi_alat"].append([float(self.lines[i+3].split()[3]), float(self.lines[i+3].split()[4]), float(self.lines[i+3].split()[5])]) elif self.lines[i].split()[0] == "site" and self.lines[i].split()[-1] == "units)" and self.lines[i].split()[-2] == "(alat": self.run_info["site_line_number"] = i elif self.lines[i].split()[0] == "number" and self.lines[i].split()[2] == 'k': if self.lines[i].split()[5] == "(tetrahedron": self.scf_params["degauss"] = "tetrahedron method: degauss not needed" else: self.scf_params["degauss"] = float(self.lines[i].split()[9]) self.run_info["number-of-k-points"] = int(self.lines[i].split()[4]) elif self.lines[i].split()[0] == "Estimated" and self.lines[i].split()[1] == "max": self.run_info["ram_per_process"] = self.lines[i].split()[7] + " " + self.lines[i].split()[8] self.run_info["total_ram"] = self.lines[i+2].split()[5] + " " + self.lines[i+2].split()[6] elif self.lines[i].split()[0] == "total" and self.lines[i].split()[1] == "energy": # the total energy of the last iteration is not print like the previous scf iteration # it begin with a ! total energy self.run_info["scf_energies"].append(float(self.lines[i].split()[3])) elif self.lines[i].split()[0] == "!" and self.lines[i].split()[5] == "Ry": self.run_info["scf_final_energy"] = float(self.lines[i].split()[4]) elif self.lines[i].split()[0] == "convergence" and self.lines[i].split()[3] == "achieved": self.run_info["scf_iterations"] = int(self.lines[i].split()[5]) elif self.lines[i].split()[0] == "the" and self.lines[i].split()[1] == "Fermi": self.run_info["fermi_energy"] = float(self.lines[i].split()[4]) elif self.lines[i].split()[0] == "Total" and self.lines[i].split()[1] == "force": self.run_info["total_force"] = float(self.lines[i].split()[3]) elif self.lines[i].split()[0] == "Computing" and self.lines[i].split()[-1] == "pressure": self.run_info["total_stress_ry_bohr_3"] = [] self.run_info["total_stress_kbar"] = [] self.run_info["pressure"] = float(self.lines[i+2].split()[-1]) self.run_info["total_stress_ry_bohr_3"].append([float(self.lines[i+3].split()[0]), float(self.lines[i+3].split()[1]), float(self.lines[i+3].split()[2])]) self.run_info["total_stress_ry_bohr_3"].append([float(self.lines[i+4].split()[0]), float(self.lines[i+4].split()[1]), float(self.lines[i+4].split()[2])]) self.run_info["total_stress_ry_bohr_3"].append([float(self.lines[i+5].split()[0]), float(self.lines[i+5].split()[1]), float(self.lines[i+5].split()[2])]) self.run_info["total_stress_kbar"].append([float(self.lines[i+3].split()[3]), float(self.lines[i+3].split()[4]), float(self.lines[i+3].split()[5])]) self.run_info["total_stress_kbar"].append([float(self.lines[i+4].split()[3]), float(self.lines[i+4].split()[4]), float(self.lines[i+4].split()[5])]) self.run_info["total_stress_kbar"].append([float(self.lines[i+5].split()[3]), float(self.lines[i+5].split()[4]), float(self.lines[i+5].split()[5])]) # note: at present len(self.run_info["scf_energies"]) = len(self.run_info["scf_iterations"]) - 1 # because the total energy of the last step is not printed in format like the previous scf step, # and it is printed as the '! total energy = ' where there is a "!" in the beginning # now we append the final scf step energy to self.run_info["scf_energies"] self.run_info["scf_energies"].append(self.run_info["scf_final_energy"]) # ---------------------------------------------------------------------- # get the xyz structure from information extracted above: self.xyz = base_xyz() self.xyz.natom = self.scf_params["nat"] begin = self.run_info["site_line_number"] + 1 # Warning: # there are numeric erros when obtaining atom coordinated from qe output # in unit of alat and multiplied by alat and bohr. namely the acquired # atomic coordinates have numeric errors compared to the input xyz # so be cautious when use it. bohr = 0.529177208 # 1 Bohr = 0.529177208 Angstrom for i in range(self.xyz.natom): self.xyz.atoms.append(Atom( self.lines[begin+i].split()[1], self.scf_params["alat_au"] * bohr * float(self.lines[begin+i].split()[6]), self.scf_params["alat_au"] * bohr * float(self.lines[begin+i].split()[7]), self.scf_params["alat_au"] * bohr * float(self.lines[begin+i].split()[8]))) self.xyz.cell = self.scf_params["cell_a_alat"] # now in unit of alat for i in range(3): for j in range(3): self.xyz.cell[i][j] = self.scf_params["cell_a_alat"][i][i] * self.scf_params["alat_au"] * bohr
def redefine_lattice(structure, a, b, c, precision=1.0e-8): """ :param a, b, c: new lattice vectors in terms of old. new_a = a[0] * old_a + a[1] * old_b + a[2] * old_c like a=[1, 0, 0], b=[0, 1, 0], c=[0, 0, 1] actually defines the same lattice as old. :param precision, a value that is less than 1 and infinitely close to 1 used to judge whether one atom is in another periodic of the redefined cell :return an object of crystal() Method: first make a large enough supercell, which guarantee that all the atoms in the new lattice are inside the supercell. then redfine the cell, and calc the fractional coord of all atoms with regarding the new cell finally remove those atoms who's fractional coord is not within range [0, 1), and we can convert fractional coords to cartesian. Note: relationship among convertion of coords. the most important point is that all coords actually have one common reference system, namely the General XYZ coordinate system. all the cell are defined with XYZ as reference, and the convmat build from the cell(with XYZ as reference) can be applied only to atoms also with XYZ as ference, finally we convert frac to cartesian using convmat also defined using cell with XYZ as reference, so we get the cartesian with general XYZ as reference. In the last all the coord of atoms and cell have the general XYZ system as reference. So it works! """ from pymatflow.structure.crystal import crystal from pymatflow.base.atom import Atom old_cell = copy.deepcopy(structure.cell) new_cell = copy.deepcopy(structure.cell) new_cell[0] = list(a[0] * np.array(old_cell[0]) + a[1] * np.array(old_cell[1]) + a[2] * np.array(old_cell[2])) new_cell[1] = list(b[0] * np.array(old_cell[0]) + b[1] * np.array(old_cell[1]) + b[2] * np.array(old_cell[2])) new_cell[2] = list(c[0] * np.array(old_cell[0]) + c[1] * np.array(old_cell[1]) + c[2] * np.array(old_cell[2])) # enlarge the system atoms_container = crystal() atoms_container.get_atoms( enlarge_atoms_new_cell(structure=structure, new_cell=new_cell)) print("atoms_container.size(): %d\n" % len(atoms_container.atoms)) # now calc the fractional coordinates of all atoms in atoms_container with new_cell as reference atoms_frac = [] latcell_new = np.array(new_cell) convmat_new = np.linalg.inv(latcell_new.T) for i in range(len(atoms_container.atoms)): atom = [] atom.append(atoms_container.atoms[i].name) atom = atom + list( convmat_new.dot( np.array([ atoms_container.atoms[i].x, atoms_container.atoms[i].y, atoms_container.atoms[i].z ]))) atoms_frac.append(atom) atoms_frac_within_new_cell = [] for atom in atoms_frac: if (0 <= atom[1] < (1 - precision)) and (0 <= atom[2] < (1 - precision)) and (0 <= atom[3] < (1 - precision)): atoms_frac_within_new_cell.append(atom) # now convert coord of atom in atoms_frac_within_new_cell to cartesian out = crystal() out.atoms = [] latcell_new = np.array(new_cell) convmat_frac_to_cartesian = latcell_new.T for atom in atoms_frac_within_new_cell: cartesian = list( convmat_frac_to_cartesian.dot(np.array([atom[1], atom[2], atom[3]]))) out.atoms.append( Atom(name=atom[0], x=cartesian[0], y=cartesian[1], z=cartesian[2])) # out.cell = new_cell return out
def get_opt_params_and_run_info(self): """ self.run_info[] start_time: the task start time stop_time: the task stop time #scf_energies: all the energies during the scf procedure #fermi_energy: fermi energy of the system (if output) """ #self.run_info["scf_energies"] = [] for i in range(len(self.lines)): # if it is an empty line continue to next line if len(self.lines[i].split()) == 0: continue if self.lines[i].split()[0] == "Program" and self.lines[i].split( )[1] == "PWSCF" and self.lines[i].split()[3] == "starts": self.run_info["start_time"] = self.lines[i].split("\n")[0] elif self.lines[i].split()[0] == "This" and self.lines[i].split( )[1] == "run" and self.lines[i].split()[3] == "terminated": self.run_info["stop_time"] = self.lines[i].split("\n")[0] elif self.lines[i].split( )[0] == "Parallel" and self.lines[i].split()[-1] == "processors": self.run_info["processors"] = int(self.lines[i].split()[-2]) elif self.lines[i].split()[0] == "MPI" and self.lines[i].split( )[-1] == "nodes": self.run_info["nodes"] = int(self.lines[i].split()[-2]) elif self.lines[i].split( )[0] == "bravais-lattice" and self.lines[i].split()[1] == "index": self.opt_params["alat_au"] = float(self.lines[i + 1].split()[4]) self.opt_params["nat"] = int(self.lines[i + 3].split()[4]) self.opt_params["nelectron"] = float(self.lines[i + 5].split()[4]) self.opt_params["n_ks_state"] = int( self.lines[i + 6].split("=")[1]) self.opt_params["ecutwfc"] = int( float(self.lines[i + 7].split()[3])) self.opt_params["ecutrho"] = int( float(self.lines[i + 8].split()[4])) self.opt_params["conv_thr"] = float(self.lines[i + 9].split()[3]) self.opt_params["mixing_beta"] = float( self.lines[i + 10].split()[3]) if "nstep" in self.opt_params and self.opt_params[ "nstep"] != None: pass else: self.opt_params["nstep"] = int(self.lines[i + 13].split()[2]) elif self.lines[i].split()[0] == "crystal" and self.lines[i].split( )[1] == "axes:" and self.lines[i].split()[-1] == "alat)": self.opt_params["cell_a_alat"] = [] self.opt_params["cell_a_alat"].append([ float(self.lines[i + 1].split()[3]), float(self.lines[i + 1].split()[4]), float(self.lines[i + 1].split()[5]) ]) self.opt_params["cell_a_alat"].append([ float(self.lines[i + 2].split()[3]), float(self.lines[i + 2].split()[4]), float(self.lines[i + 2].split()[5]) ]) self.opt_params["cell_a_alat"].append([ float(self.lines[i + 3].split()[3]), float(self.lines[i + 3].split()[4]), float(self.lines[i + 3].split()[5]) ]) elif self.lines[i].split()[0] == "reciprocal" and self.lines[ i].split()[1] == "axes:" and self.lines[i].split( )[-1] == "pi/alat)": # actually '2 pi/alat' self.opt_params["cell_b_2pi_alat"] = [] self.opt_params["cell_b_2pi_alat"].append([ float(self.lines[i + 1].split()[3]), float(self.lines[i + 1].split()[4]), float(self.lines[i + 1].split()[5]) ]) self.opt_params["cell_b_2pi_alat"].append([ float(self.lines[i + 2].split()[3]), float(self.lines[i + 2].split()[4]), float(self.lines[i + 2].split()[5]) ]) self.opt_params["cell_b_2pi_alat"].append([ float(self.lines[i + 3].split()[3]), float(self.lines[i + 3].split()[4]), float(self.lines[i + 3].split()[5]) ]) elif self.lines[i].split()[0] == "site" and self.lines[i].split( )[-1] == "units)" and self.lines[i].split()[-2] == "(alat": self.run_info["site_line_number"] = i elif self.lines[i].split()[0] == "number" and self.lines[i].split( )[2] == 'k': if self.lines[i].split()[5] == "(tetrahedron": self.opt_params[ "degauss"] = "tetrahedron method: degauss not needed" else: self.opt_params["degauss"] = float( self.lines[i].split()[9]) self.run_info["number-of-k-points"] = int( self.lines[i].split()[4]) elif self.lines[i].split( )[0] == "Estimated" and self.lines[i].split()[1] == "max": self.run_info["ram_per_process"] = self.lines[i].split( )[7] + " " + self.lines[i].split()[8] self.run_info["total_ram"] = self.lines[ i + 2].split()[5] + " " + self.lines[i + 2].split()[6] # ---------------------------------------------------------------------- # get the input xyz structure from information extracted above: self.xyz = base_xyz() self.xyz.natom = self.opt_params["nat"] begin = self.run_info["site_line_number"] + 1 # Warning: # there are numeric erros when obtaining atom coordinated from qe output # in unit of alat and multiplied by alat and bohr. namely the acquired # atomic coordinates have numeric errors compared to the input xyz # so be cautious when use it. bohr = 0.529177208 # 1 Bohr = 0.529177208 Angstrom for i in range(self.xyz.natom): self.xyz.atoms.append( Atom( self.lines[begin + i].split()[1], self.opt_params["alat_au"] * bohr * float(self.lines[begin + i].split()[6]), self.opt_params["alat_au"] * bohr * float(self.lines[begin + i].split()[7]), self.opt_params["alat_au"] * bohr * float(self.lines[begin + i].split()[8]))) self.xyz.cell = self.opt_params["cell_a_alat"] # now in unit of alat for i in range(3): for j in range(3): self.xyz.cell[i][j] = self.opt_params["cell_a_alat"][i][ j] * self.opt_params["alat_au"] * bohr # now self.xyz.cell are in unit of Angstrom # ---------------------------------------------------------------------- # get information output each ion step self._get_info_for_each_ions_step()
def get_trajectory(self): """ Note: initial input structure is not in self.trajectory, but is in self.xyz self.trajectory contains all other structures and the last of it is the optimized structure. """ if self.run_type == "relax": self.trajectory = [] for i in range(len(self.lines)): if len(self.lines[i].split()) > 0 and self.lines[i].split( )[0] == "ATOMIC_POSITIONS": if self.lines[i].split()[1] == "(angstrom)": atm = [] j = i + 1 while len(self.lines[j].split()) == 4 or len( self.lines[j].split()) == 7: atm.append( Atom(self.lines[j].split()[0], float(self.lines[j].split()[1]), float(self.lines[j].split()[2]), float(self.lines[j].split()[3]))) j = j + 1 self.trajectory.append(atm) elif self.lines[i].split()[1] == "(crystal)": # convert frac to cartesian again latcell = np.array(self.xyz.cell) convmat = latcell.T atm = [] j = i + 1 while len(self.lines[j].split()) == 4 or len( self.lines[j].split()) == 7: x, y, z = list( convmat.dot( np.array([ float(self.lines[j].split()[1]), float(self.lines[j].split()[2]), float(self.lines[j].split()[3]) ]))) atm.append(Atom(self.lines[j].split()[0], x, y, z)) j = j + 1 self.trajectory.append(atm) # elif self.run_type == "vc-relax": self.trajectory = [] for i in range(len(self.lines)): if len(self.lines[i].split()) > 0 and self.lines[i].split( )[0] == "ATOMIC_POSITIONS": if self.lines[i].split()[1] == "(angstrom)": atm = [] j = i + 1 while len(self.lines[j].split()) == 4 or len( self.lines[j].split()) == 7: atm.append( Atom(self.lines[j].split()[0], float(self.lines[j].split()[1]), float(self.lines[j].split()[2]), float(self.lines[j].split()[3]))) j = j + 1 self.trajectory.append(atm) elif self.lines[i].split()[1] == "(crystal)": cell = [] for k in range(3): cell.append([ float(self.lines[i - 4 + k].split()[0]), float(self.lines[i - 4 + k].split()[1]), float(self.lines[i - 4 + k].split()[2]) ]) # convert frac to cartesian again latcell = np.array(cell) convmat = latcell.T atm = [] j = i + 1 while len(self.lines[j].split()) == 4 or len( self.lines[j].split()) == 7: x, y, z = list( convmat.dot( np.array([ float(self.lines[j].split()[1]), float(self.lines[j].split()[2]), float(self.lines[j].split()[3]) ]))) atm.append(Atom(self.lines[j].split()[0], x, y, z)) j = j + 1 self.trajectory.append(atm) # if self.relaxed == True and self.run_type == "vc-relax": # get the line number of the 'Begin final coordinates' # and 'End final coordinates' begin_final_coord_line = 0 end_final_coord_line = 0 while self.lines[ begin_final_coord_line] != "Begin final coordinates\n": begin_final_coord_line += 1 while self.lines[end_final_coord_line] != "End final coordinates\n": end_final_coord_line += 1 # get the optimized cell self.final_cell = [] for i in range(begin_final_coord_line + 5, begin_final_coord_line + 8): vec = [] for j in range(3): vec.append(float(self.lines[i].split()[j])) self.final_cell.append(vec)
def _get_trajectory(self): """ 1 Bohr=0.5291772108 Angstroms xcart is in unit of bohr so we convert it to angstrom Note: """ bohr = 0.5291772108 self.info["trajectory"] = [] self.info["cells"] = [] self.info["acells"] = [] self.info["rprimds"] = [] for i in range(len(self.lines)): if len(self.lines[i].split()) == 0: continue # used to get the trajectory if self.lines[i].split()[0] == "Cartesian" and self.lines[i].split( )[1] == "coordinates" and self.lines[i].split()[2] == "(xcart)": atm = [] j = i + 1 while self.lines[j].split()[1] != "coordinates": atm.append( Atom("XXX", float(self.lines[j].split()[0]) * bohr, float(self.lines[j].split()[1]) * bohr, float(self.lines[j].split()[2]) * bohr)) j = j + 1 self.info["trajectory"].append(atm) # -------------------------------- # get the self.info["acells"] and self.info["rprimds"] # if not opt cell, the following two if code will never run to get the cell # so we can finally judget whether it is optcell by chekcing the length of self.info["rprimds"] and self.info["acells"] if self.lines[i].split()[0] == "Scale" and self.lines[i].split( )[4] == "(acell)": self.info["acells"].append([ float(self.lines[i + 1].split()[0]), float(self.lines[i + 1].split()[1]), float(self.lines[i + 1].split()[2]) ]) if self.lines[i].split()[0] == "Real" and self.lines[i].split( )[1] == "space" and self.lines[i].split()[4] == "(rprimd)": rprimd = [] for k in range(3): for j in range(3): rprimd.append(float(self.lines[i + 1 + k].split()[j])) self.info["rprimds"].append(rprimd) # end extract the cell # -------------------------------- if len(self.info["acells"]) == len(self.info["trajectory"]): # so cell is optimized. acell and rprimd is print every ion step # Note rprimd is not rprim!!! and rprimd is actually rprim already scaled by acell # so to build cell from rprimd, we only need to multiply by bohr, and don't need to scale by acell self.info["cells"] = copy.deepcopy(self.info["rprimds"]) for m in range(len(self.info["cells"])): for n in range(9): self.info["cells"][m][n] = self.info["cells"][m][n] * bohr # # now using self.atom_type from self.get_pseudo_info() for i in range(len(self.info["trajectory"])): for j in range(len(self.info["trajectory"][i])): name = self.atom_type[self.info["outvars"]["before"]["typat"] [j]] self.info["trajectory"][i][j].set_name(name)
def main(): parser = argparse.ArgumentParser() subparsers = parser.add_subparsers( dest="driver", title="subcommands", description="choose one and only one subcommand") # -------------------------------------------------------------------------- # supercell builder # -------------------------------------------------------------------------- subparser = subparsers.add_parser("supercell", help="using supercell subcommand") subparser.add_argument("-i", "--input", type=str, required=True, help="input structure file") subparser.add_argument("-o", "--output", type=str, required=True, help="output structure file") subparser.add_argument("-n", "--supern", nargs="+", type=int, help="bulid supern:[int, int, int] supercell") # -------------------------------------------------------------------------- # fix atoms # -------------------------------------------------------------------------- subparser = subparsers.add_parser("fix", help="using fix subcommand") subparser.add_argument("-i", "--input", type=str, required=True, help="input structure file") subparser.add_argument("-o", "--output", type=str, required=True, help="output structure file") subparser.add_argument( "--fix", help= "list of fixed atoms, index start from 1, have privilege over --around-z", nargs='+', type=int, default=None) subparser.add_argument( "--around-z", type=float, nargs=3, default=None, help= "select atoms around specified z in Angstrom with tolerance, like this --around-z 10 -0.5 0.5" ) subparser.add_argument( "--color", type=str, default="white", choices=["red", "green", "blue", "white"], help= "select color to color the fix atoms in xsd file, can be: red green blue and white" ) # -------------------------------------------------------------------------- # convert file type # -------------------------------------------------------------------------- subparser = subparsers.add_parser("convert", help="using convert subcommand") subparser.add_argument("-i", "--input", type=str, required=True, help="input structure file") subparser.add_argument("-o", "--output", type=str, required=True, help="output structure file") # -------------------------------------------------------------------------- # kpath # -------------------------------------------------------------------------- subparser = subparsers.add_parser("kpath", help="using kpath subcommand") subparser.add_argument("-i", "--input", type=str, required=True, help="input structure file") subparser.add_argument("--engine", type=str, default="seekpath", choices=["seekpath"], help="choose tool to generate kpath") subparser.add_argument("--kpath-file", type=str, default="kpath-from-seekpath.txt", help="the output kpoints file") # --------------------------------------------------------------------------------- # move atoms along one direction # --------------------------------------------------------------------------------- subparser = subparsers.add_parser("move", help="move atoms along one direction") subparser.add_argument("-i", "--input", type=str, required=True, help="input structure file") subparser.add_argument("-o", "--output", type=str, required=True, help="output structure file") subparser.add_argument("--atoms", type=int, nargs="+", help="atoms to move, index start from 1") subparser.add_argument( "--direction", type=float, nargs=3, help= "direction to move the atoms, in format of crystal orientation index") subparser.add_argument( "--disp", type=float, help="displacement along the moving direction, in unit of Anstrom") # --------------------------------------------------------------------------------- # remove atoms # --------------------------------------------------------------------------------- subparser = subparsers.add_parser("remove", help="remove specified atoms") subparser.add_argument("-i", "--input", type=str, required=True, help="input structure file") subparser.add_argument("-o", "--output", type=str, required=True, help="output structure file") subparser.add_argument("--atoms", type=int, nargs="+", help="atoms to remove, index start from 1") subparser.add_argument("--elements", type=str, nargs="+", help="elements to remove") # --------------------------------------------------------------------------------- # vacuum layer # --------------------------------------------------------------------------------- subparser = subparsers.add_parser("vacuum", help="add vacuum layer") subparser.add_argument("-i", "--input", type=str, required=True, help="input structure file") subparser.add_argument("-o", "--output", type=str, required=True, help="output structure file") subparser.add_argument( "--plane", type=int, default=1, help="on which plane to add vacuum layer. 1: ab, 2: ac, 3: bc") subparser.add_argument( "--thick", type=float, default=10, help="thickness of the vacuum layer, in unit of Angstrom, default is 10" ) # --------------------------------------------------------------------------------- # inverse atoms against geometric center # --------------------------------------------------------------------------------- subparser = subparsers.add_parser("inverse", help="inverse against geo center") subparser.add_argument("-i", "--input", type=str, required=True, help="input structure file") subparser.add_argument("-o", "--output", type=str, required=True, help="output structure file") subparser.add_argument("-c", "--center", type=str, default="cell", choices=["geo", "cell"], help="inversion center, can geo or cell") # --------------------------------------------------------------------------------- # redefine lattice # --------------------------------------------------------------------------------- subparser = subparsers.add_parser("redefine", help="redefine lattice") subparser.add_argument("-i", "--input", type=str, required=True, help="input structure file") subparser.add_argument("-o", "--output", type=str, required=True, help="output structure file") subparser.add_argument("-a", type=int, nargs=3, default=[1, 0, 0], help="a from old a b c") subparser.add_argument("-b", type=int, nargs=3, default=[0, 1, 0], help="b from old a b c") subparser.add_argument("-c", type=int, nargs=3, default=[0, 0, 1], help="c from old a b c") subparser.add_argument( "--precision", type=float, default=1.0e-8, help= "a value that is less than 1 and infinitely close to 1 used to judge whether one atom is in another periodic of the redefined cell" ) # --------------------------------------------------------------------------------- # cleave surface # --------------------------------------------------------------------------------- subparser = subparsers.add_parser("cleave", help="cleave surface") subparser.add_argument("-i", "--input", type=str, required=True, help="input structure file") subparser.add_argument("-o", "--output", type=str, required=True, help="output structure file") subparser.add_argument("--direction", type=int, nargs=3, default=[0, 0, 1], help="direction of the surface plane to cleave") subparser.add_argument( "--thick", type=float, help="thickness of the vacuum layer, in unit of Angstrom, default is 10" ) subparser.add_argument( "--precision", type=float, default=1.0e-8, help= "a value that is large than 0 and infinitely close to 0 used to judge whether one atom is in another periodic of the redefined cell used in cleave surface" ) # --------------------------------------------------------------------------------- # merge layers | ab plane # --------------------------------------------------------------------------------- subparser = subparsers.add_parser("merge", help="merge layers | ab plane") subparser.add_argument("-i", "--input", type=str, nargs=2, required=True, help="input structure files") subparser.add_argument("-o", "--output", type=str, required=True, help="output structure file") #subparser.add_argument("--direction", type=int, nargs=3, default=[0, 0, 1], # help="direction of the surface plane to cleave") subparser.add_argument( "--usecell", type=str, default="average", choices=["1", "2", "average"], help="use cell of structure 1 or 2 , otherwise average by default") subparser.add_argument( "--thick", type=float, help="thickness of the vacuum layer, in unit of Angstrom, default is 10" ) subparser.add_argument( "--distance", type=float, help="distance between the layer, in unit of Angstrom, default is 3.4") # --------------------------------------------------------------------------------- # nanotube builder # --------------------------------------------------------------------------------- subparser = subparsers.add_parser( "tube", help= "nanotube along b direction(a must be perpendicular to b and ab is the surface plane)" ) subparser.add_argument("-i", "--input", type=str, required=True, help="input structure files") subparser.add_argument("-o", "--output", type=str, required=True, help="output structure file") subparser.add_argument( "--plane", type=int, default=1, help="on which plane to add vacuum layer. 1: ab, 2: ac, 3: bc") subparser.add_argument( "--axis", type=str, default="b", choices=["a", "b", "c"], help="build nanotube along an axis parallel to axis specified") # ----------------------------------------------------------------------------------- # set frac within zero and one # ------------------------------------------------------------------------------------ subparser = subparsers.add_parser( "std", help="set fractional coordinates within zero and one") subparser.add_argument("-i", "--input", type=str, required=True, help="input structure file") subparser.add_argument("-o", "--output", type=str, required=True, help="output structure file") # ------------------------------------------------------------------------------------ # generate series of cell volume changed structures # ------------------------------------------------------------------------------------ subparser = subparsers.add_parser( "cv", help="generate series of cell volume changed structures") subparser.add_argument("-i", "--input", type=str, required=True, help="input structure file") subparser.add_argument("-d", "--directory", type=str, default="./", help="directory to put the generated structures") subparser.add_argument( "--range", type=float, nargs=3, default=[0.95, 1.05, 0.01], help="cell volume change ratio, default is [0.95, 1.05, 0.01]") # ========================================================== # transfer parameters from the arg subparser to static_run setting # ========================================================== args = parser.parse_args() # if no argument passed to matflow if len(sys.argv) == 1: # display help message when no args provided parser.print_help() sys.exit(1) if args.driver == "supercell": from pymatflow.base.xyz import base_xyz from pymatflow.structure.crystal import crystal a = read_structure(filepath=args.input) supercell = a.build_supercell(args.supern) new_structure = crystal() new_structure.get_cell_atoms(cell=supercell["cell"], atoms=supercell["atoms"]) write_structure(structure=new_structure, filepath=args.output) print("=========================================================\n") print(" structflow supercell builder\n") print("---------------------------------------------------------\n") print("you are trying to bulid supercell from %s\n" % args.input) print("the output structure file is -> %s\n" % args.output) elif args.driver == "fix": # can only write xyz and poscar file a = read_structure(filepath=args.input) if args.fix != None: fix = args.fix elif args.around_z != None: atoms_index_from_1 = [] for i in range(len(a.atoms)): if a.atoms[i].z > (args.around_z[0] + args.around_z[1]) and a.atoms[i].z < ( args.around_z[0] + args.around_z[2]): atoms_index_from_1.append(i + 1) fix = atoms_index_from_1 else: fix = [] if args.output.split(".")[-1] == "xyz": fix_str = "" for i in fix: fix_str += "%d " % i os.system("xyz-fix-atoms.py -i %s -o %s --fix %s" % (args.input, args.output, fix_str)) elif os.path.basename(args.output) == "POSCAR": from pymatflow.vasp.base.poscar import vasp_poscar for i in fix: a.atoms[i - 1].fix = [True, True, True] poscar = vasp_poscar() poscar.xyz.cell = a.cell poscar.xyz.atoms = a.atoms poscar.xyz.natom = len(poscar.xyz.atoms) poscar.xyz.set_species_number() # needed for poscar output poscar.selective_dynamics = True with open(args.output, 'w') as fout: poscar.to_poscar(fout=fout, coordtype="Direct") else: print( "===============================================================\n" ) print(" WARNING !!!\n") print( "---------------------------------------------------------------\n" ) print("structflow fix now only supports write of xyz and POSCAR\n") sys.exit(1) # output an xsd file with fixed atoms colored specifically so that user can check the atoms fixed from xml.etree.ElementTree import parse os.system("mkdir -p /tmp/structflow/fix") write_structure(a, filepath="/tmp/structflow/fix/tmp.xsd") # read xsd file xsd = parse("/tmp/structflow/fix/tmp.xsd") # ID of Atom3D in xsd file start from 4 imap = xsd.getroot().find("AtomisticTreeRoot").find( "SymmetrySystem").find("MappingSet").find("MappingFamily").find( "IdentityMapping") atoms = imap.findall("Atom3d") if args.color == "white": RGB = [255, 255, 255] elif args.color == "red": RGB = [255, 0, 0] elif args.color == "green": RGB = [0, 255, 0] elif args.color == "blue": RGB = [0, 0, 255] else: RGB = [255, 255, 255] # default for i in fix: atoms[i - 1].set("Color", "%f, %f, %f, %f" % (RGB[0], RGB[1], RGB[2], 1)) # write xsd file xsd.write(args.input + ".coloring.atoms.fixed.xsd") elif args.driver == "convert": # will convert file type according to the suffix of the specified input and output file a = read_structure(filepath=args.input) write_structure(structure=a, filepath=args.output) print("=========================================================\n") print(" structflow convert\n") print("---------------------------------------------------------\n") print("with the help from ase.io\n") elif args.driver == "kpath": if args.engine == "seekpath": os.system("kpath-xyz-seekpath.py -i %s -o %s" % (args.input, args.output)) else: pass elif args.driver == "move": from pymatflow.structure.tools import move_along # input structure a = read_structure(filepath=args.input) # move atoms print("=========================================================\n") print(" structflow\n") print("----------------------------------------------------------\n") print("you are trying to move atoms:\n") print(args.atoms) for i in args.atoms: print("%d -> %s\n" % (i, a.atoms[i - 1].name)) print("\n") print("along direction:\n") print(args.direction) print("\n") print("by length of -> %f, in unit of Angstrom\n" % args.disp) move_along(a, atoms_to_move=[i - 1 for i in args.atoms], direc=args.direction, disp=args.disp) # output structure write_structure(structure=a, filepath=args.output) elif args.driver == "remove": from pymatflow.structure.tools import remove_atoms a = read_structure(filepath=args.input) # remove atoms print( "=======================================================================\n" ) print(" structflow\n") print( "-----------------------------------------------------------------------\n" ) print( "you are trying to remove from %s the following list of atoms:\n" % args.input) print(args.atoms) if args.atoms != None: for i in args.atoms: print("%d -> %s\n" % (i, a.atoms[i - 1].name)) else: pass print("\n") print("also the following elements will be removed:\n") print(args.elements) print("the output structure file is -> %s\n" % args.output) if args.atoms != None: remove_atoms(a, atoms_to_remove=[i - 1 for i in args.atoms]) # we should first remove atoms specified by args.atoms # and remove atoms specified by args.elements # as remove atom will change the index of atom if args.elements != None: element_atoms_to_remove = [] for i in range(len(a.atoms)): if a.atoms[i].name in args.elements: element_atoms_to_remove.append(i) remove_atoms(a, atoms_to_remove=element_atoms_to_remove) # output structure write_structure(structure=a, filepath=args.output) elif args.driver == "vacuum": from pymatflow.structure.tools import vacuum_layer a = read_structure(filepath=args.input) # add vacuum layer print( "=======================================================================\n" ) print(" structflow\n") print( "-----------------------------------------------------------------------\n" ) if args.plane == 1: plane = "ab" elif args.plane == 2: plane = "ac" elif args.plane == 3: plane = "bc" print( "you are trying to add vacuum layer of %f Angstrom on %s plane\n" % (args.thick, plane)) print("from %s\n" % args.input) print("\n") print("the output structure file is -> %s\n" % args.output) vacuum_layer(a, plane=args.plane, thickness=args.thick if args.thick != None else 10.0) # output structure write_structure(structure=a, filepath=args.output) elif args.driver == "inverse": from pymatflow.structure.tools import inverse_geo_center from pymatflow.structure.tools import inverse_cell_center a = read_structure(filepath=args.input) print( "=======================================================================\n" ) print(" structflow\n") print( "-----------------------------------------------------------------------\n" ) if args.center == "geo": print( "you are trying to inverse the system against the geometric center\n" ) elif args.center == "cell": print( "you are trying to inverse the system against the cell center\n" ) print("from %s\n" % args.input) print("\n") print("the output structure file is -> %s\n" % args.output) if args.center == "geo": inverse_geo_center(a) elif args.center == "cell": inverse_cell_center(a) # output structure write_structure(structure=a, filepath=args.output) elif args.driver == "redefine": from pymatflow.structure.tools import redefine_lattice a = read_structure(filepath=args.input) print( "=======================================================================\n" ) print(" structflow\n") print( "-----------------------------------------------------------------------\n" ) print("you are trying to redefine the lattice\n") print("from %s\n" % args.input) print("\n") print("the output structure file is -> %s\n" % args.output) redefined = redefine_lattice(structure=a, a=args.a, b=args.b, c=args.c, precision=args.precision) # output structure write_structure(structure=redefined, filepath=args.output) elif args.driver == "cleave": from pymatflow.structure.tools import cleave_surface a = read_structure(filepath=args.input) print( "=======================================================================\n" ) print(" structflow\n") print( "-----------------------------------------------------------------------\n" ) print("you are trying to cleave the surface of (%d, %d, %d)\n" % (args.direction[0], args.direction[1], args.direction[2])) print("from %s\n" % args.input) print("\n") print("the output structure file is -> %s\n" % args.output) cleaved = cleave_surface( structure=a, direction=args.direction, thickness=args.thick if args.thick != None else 10.0, precision=args.precision) # output structure write_structure(structure=cleaved, filepath=args.output) elif args.driver == "merge": from pymatflow.structure.tools import merge_layers a_list = [] for i in range(2): a_list.append(read_structure(filepath=args.input[i])) print( "=======================================================================\n" ) print(" structflow\n") print( "-----------------------------------------------------------------------\n" ) print("you are trying to merge layers on ab plane\n") print("from %s\n" % (args.input[0])) print("and %s\n" % (args.input[1])) print("\n") print("the output structure file is -> %s\n" % args.output) if args.usecell == "1": usecell = 1 elif args.usecell == "2": usecell = 2 else: usecell = "average" merged = merge_layers( structure1=a_list[0], structure2=a_list[1], use_cell=usecell, distance=args.distance if args.distance != None else 3.4, thickness=args.thick if args.thick != None else 10.0) # output structure write_structure(structure=merged, filepath=args.output) elif args.driver == "tube": a = read_structure(filepath=args.input) if args.plane == 1: plane = "ab" elif args.plane == 2: plane = "ac" elif args.plane == 3: plane = "bc" print( "=======================================================================\n" ) print(" structflow\n") print( "-----------------------------------------------------------------------\n" ) print( "you are trying to build nanotube of %s plane along %s vector\n" % (plane, args.axis)) print("from %s\n" % (args.input)) print("the output structure file is -> %s\n" % args.output) tube = None if plane == "ab": from pymatflow.structure.tools import build_nanotube_ab if args.axis not in "ab": print( "building nanotube of ab plane along axis parallel to c is unphysical!!!\n" ) sys.exit() else: tube = build_nanotube_ab(structure=a, axis=args.axis) if plane == "ac": from pymatflow.structure.tools import build_nanotube_ac if args.axis not in "ac": print( "building nanotube of ac plane along axis parallel to b is unphysical!!!\n" ) sys.exit() else: tube = build_nanotube_ac(structure=a, axis=args.axis) if plane == "bc": from pymatflow.structure.tools import build_nanotube_bc if args.axis not in "bc": print( "building nanotube of bc plane along axis parallel to a is unphysical!!!\n" ) sys.exit() else: tube = build_nanotube_bc(structure=a, axis=args.axis) # output structure if tube != None: write_structure(structure=tube, filepath=args.output) elif args.driver == "std": from pymatflow.structure.tools import set_frac_within_zero_and_one a = read_structure(filepath=args.input) print( "=======================================================================\n" ) print(" structflow\n") print( "-----------------------------------------------------------------------\n" ) print("you are trying to set fractional coords within 0 and 1\n") print("from %s\n" % (args.input)) print("\n") print("the output structure file is -> %s\n" % args.output) normalized = set_frac_within_zero_and_one(structure=a) # output structure write_structure(structure=normalized, filepath=args.output) elif args.driver == "cv": from pymatflow.structure.crystal import crystal from pymatflow.base.atom import Atom a = read_structure(filepath=args.input) print( "=======================================================================\n" ) print(" structflow\n") print( "-----------------------------------------------------------------------\n" ) print( "you are trying to get a series of structure with different volume\n" ) print("from %s\n" % (args.input)) print("\n") print("the output dir for structure file is -> %s\n" % args.directory) # now calc the fractional coordinates atoms_frac = [] latcell = np.array(a.cell) convmat = np.linalg.inv(latcell.T) for i in range(len(a.atoms)): atom = [] atom.append(a.atoms[i].name) atom = atom + list( convmat.dot( np.array([a.atoms[i].x, a.atoms[i].y, a.atoms[i].z]))) atoms_frac.append(atom) # out = crystal() os.system("mkdir -p %s" % args.directory) for i, ratio_v in enumerate( np.arange(args.range[0], args.range[1], args.range[2])): ratio = np.power(ratio_v, 1 / 3) # now convert coord of atom in atoms_frac_within_new_cell to cartesian out.atoms = [] out.cell = (np.array(a.cell) * ratio).tolist() latcell = np.array(out.cell) convmat_frac_to_cartesian = latcell.T for atom in atoms_frac: cartesian = list( convmat_frac_to_cartesian.dot( np.array([atom[1], atom[2], atom[3]]))) out.atoms.append( Atom(name=atom[0], x=cartesian[0], y=cartesian[1], z=cartesian[2])) output_name = ".".join( os.path.basename(args.input).split(".")[:-1] + ["%d" % i, "cif"]) write_structure(out, filepath=os.path.join(args.directory, output_name)) # with open(os.path.join(args.directory, "log.txt"), 'w') as fout: fout.write("# index\tratio_v\tvolume(Angstrom^3)\n") for i, ratio_v in enumerate( np.arange(args.range[0], args.range[1], args.range[2])): ratio = np.power(ratio_v, 1 / 3) cell_now = (np.array(a.cell) * ratio).tolist() fout.write("%d\t%f\t%f\n" % (i, ratio_v, np.linalg.det(cell_now)))
def merge_layers(structure1, structure2, use_cell=None, distance=3.4, thickness=10): """ :param structure1: an instance of crystal() :param structure2: an instance of crystal() :param use_cell: use cell parameter of structure 1 or 2 or None(average) to set the new a b cell parameter attention: c vector is not handled this way :param distance: the distance between layers :param thickness: the vaccum layer thickness of the combined system :return an object of crystal() Note: only merge layers with ab plane as the surface plane """ from pymatflow.structure.crystal import crystal from pymatflow.base.atom import Atom structure1 = set_frac_within_zero_and_one(structure1) structure2 = set_frac_within_zero_and_one(structure2) old_cell_1 = copy.deepcopy(structure1.cell) old_cell_2 = copy.deepcopy(structure2.cell) # first transfer to fractional coordinate structure1.natom = len(structure1.atoms) frac_1 = structure1.get_fractional() structure2.natom = len(structure2.atoms) frac_2 = structure2.get_fractional() average_cell = [] for i in range(3): average_cell.append( list((np.array(old_cell_1[i]) + np.array(old_cell_2[i])) / 2)) out = crystal() if use_cell == 1: latcell_frac_to_cart_1 = old_cell_1 latcell_frac_to_cart_2 = old_cell_1[0:2] latcell_frac_to_cart_2.append(old_cell_2[2]) elif use_cell == 2: latcell_frac_to_cart_2 = old_cell_2 latcell_frac_to_cart_1 = old_cell_2[0:2] latcell_frac_to_cart_1.append(old_cell_1[2]) else: #average_ab = [] #for i in range(2): # vec = [] # for j in range(3): # vec.append((old_cell_1[i][j] + old_cell_2[i][j]) / 2 ) # average_ab.append(vec) latcell_frac_to_cart_1 = average_cell latcell_frac_to_cart_1[2] = old_cell_1[2] latcell_frac_to_cart_2 = average_cell latcell_frac_to_cart_2[2] = old_cell_2[2] # convert frac to cartesian again convmat_1 = np.array(latcell_frac_to_cart_1).T convmat_2 = np.array(latcell_frac_to_cart_2).T cart_1 = [] for atom in frac_1: cartesian = list(convmat_1.dot(np.array([atom[1], atom[2], atom[3]]))) cart_1.append([atom[0], cartesian[0], cartesian[1], cartesian[2]]) cart_2 = [] for atom in frac_2: cartesian = list(convmat_2.dot(np.array([atom[1], atom[2], atom[3]]))) cart_2.append([atom[0], cartesian[0], cartesian[1], cartesian[2]]) # make distance gap between cart_1 and cart_2 is the value of distance z_1 = [] for atom in cart_1: z_1.append(atom[3]) z_2 = [] for atom in cart_2: z_2.append(atom[3]) max_z_1 = max(z_1) min_z_2 = min(z_2) for i in range(len(cart_2)): cart_2[i][3] += distance - (min_z_2 - max_z_1) cart_all = cart_1 + cart_2 out.atoms = [] for atom in cart_all: out.atoms.append(Atom(name=atom[0], x=atom[1], y=atom[2], z=atom[3])) if use_cell == 1: out.cell = old_cell_1 factor = (np.linalg.norm(np.array(old_cell_1[2])) + np.linalg.norm( np.array(old_cell_2[2]))) / np.linalg.norm(np.array(old_cell_1[2])) out.cell[2] = list(np.array(old_cell_1[2]) * factor) elif use_cell == 2: out.cell = old_cell_2 factor = (np.linalg.norm(np.array(old_cell_1[2])) + np.linalg.norm( np.array(old_cell_2[2]))) / np.linalg.norm(np.array(old_cell_2[2])) out.cell[2] = list(np.array(old_cell_2[2]) * factor) else: out.cell = average_cell factor = (np.linalg.norm(np.array(old_cell_1[2])) + np.linalg.norm( np.array(old_cell_2[2]))) / np.linalg.norm(np.array(out.cell[2])) out.cell[2] = list(np.array(out.cell[2]) * factor) vacuum_layer(structure=out, plane=1, thickness=thickness) return out