class Xyz(File): """Class for xyz files.""" def __init__(self, absolute_path=None): """Initiates an Xyz object. If absolute_path is not None, reads the xyz file.""" super().__init__(absolute_path) self.tags.add("xyz") self.atoms = Atoms([]) if self.absolute_path is not None: self.read_xyz() def read_xyz(self): """ Reads info from xyz file, whose path was given as absolute_path when the object was instantiated. Raises ------ ValueError If the xyz file has extra lines between atoms. If a Lattice is found that has the wrong number of parameters. TypeError If non-numeric values are found for an atom's position. """ self._check_read() lines = iter(self.content) line = next(lines) n_atoms = 0 while True: try: n_atoms = int(line.split()[0]) except TypeError: line = next(lines) continue else: break lattice_indexes = self._find("Lattice") must_invent_cell = False if len(lattice_indexes) > 0: lattice_index = lattice_indexes[0] lattice = find_between(self.content[lattice_index], '"', '"') try: xx, xy, xz, yx, yy, yz, zx, zy, zz = tuple(lattice.split()) except ValueError: raise ValueError("Lattice in xyz file is bad") self.atoms.cell = [[xx, xy, xz], [yx, yy, yz], [zx, zy, zz]] else: # if no "Lattice" is found, atoms.cell is invented below lattice_index = 0 must_invent_cell = True for line in self.content[lattice_index + 1:]: if not line[0].isalpha(): continue try: s, x, y, z = tuple(line.split()) except ValueError: continue # raise ValueError("xyz file has a bad format, avoid extra lines") try: xyz = [float(x), float(y), float(z)] except TypeError: raise TypeError("bad positions in file: {} {} {}".format( x, y, z)) atom = Atom(atom_type=s, position=xyz) self.atoms.add_atom(atom) if len(self.atoms) != n_atoms: print("WARNING: {} atoms declared in file, {} atoms found in file". format(n_atoms, len(self.atoms))) if must_invent_cell: gap = 10 # angstroms min_x = min(atom.position[0] for atom in self.atoms) max_x = max(atom.position[0] for atom in self.atoms) min_y = min(atom.position[1] for atom in self.atoms) max_y = max(atom.position[1] for atom in self.atoms) min_z = min(atom.position[2] for atom in self.atoms) max_z = max(atom.position[2] for atom in self.atoms) xx = max_x - min_x + gap yy = max_y - min_y + gap zz = max_z - min_z + gap self.atoms.cell = [[xx, 0, 0], [0, yy, 0], [0, 0, zz]] def write_xyz(self, filename, real_types=False, with_classification=False): """ Writes xyz file with info present in the Xyz object. Parameters ---------- filename : str Path to output xyz file. real_types : bool, optional If real types should be used instead of types (e.g. C instead of C2). Standard is False. with_classification : bool, optional If atom classification is wanted as a comment after every line. Standard is False. """ with open(filename, "w") as F: F.write(str(len(self.atoms))) F.write('\n') if self.atoms.cell is not None: F.write('Lattice="') F.write(' '.join([str(p) for p in self.atoms.cell[0]]) + ' ') F.write(' '.join([str(p) for p in self.atoms.cell[1]]) + ' ') F.write(' '.join([str(p) for p in self.atoms.cell[2]]) + '"' + '\n') else: F.write('\n') if not with_classification: for atom in self.atoms: F.write(atom.__str__(real_type=real_types) + '\n') else: for atom in self.atoms: F.write( atom.__str__(real_type=real_types) + '\t# ' + atom.classification + '\n') def write_simple_cif(self, filename, struct_name, comment=None, centralize=True): """ Writes a simple cif file. Meant to be used for 1D structures. Parameters ---------- filename : str Path to output cif file. struct_name : str Name of the structure, to be included in the file info. comment : str, optional Comment to the structure, to be included in the file info. Standard comment is 'simple cif for 1D nanostructure'. centralize : bool, optional If the atomic positions should be centralized in the cell. Standard is True. Notes ----- This is a very simple cif writing for theoretical structures. It's not meant to be used for complete crystallography information. """ self._check_read() if not self.atoms.atoms: self.read_xyz() # arguments checking struct_name = struct_name.replace(" ", "") if comment is None: comment = "simple cif for 1D nanostructure" with open(filename, "w") as F: F.write("data_" + struct_name + "\n\n") F.write("_publ_section_comment" + "\n" + ";" + "\n" + comment + "\n" + ";" + "\n\n") cell_x = self.atoms.cell[0][0] cell_y = self.atoms.cell[1][1] cell_z = self.atoms.cell[2][2] F.write("_cell_length_a " + str(round(cell_x, 4)) + "(0)" + "\n") F.write("_cell_length_b " + str(round(cell_y, 4)) + "(0)" + "\n") F.write("_cell_length_c " + str(round(cell_z, 4)) + "(0)" + "\n") F.write("_cell_angle_alpha 90.0000(0)" + "\n") F.write("_cell_angle_beta 90.0000(0)" + "\n") F.write("_cell_angle_gamma 90.0000(0)" + "\n\n") F.write("_symmetry_space_group_name_H-M 'P 1'" + "\n") F.write("_symmetry_Int_Tables_number 1" + "\n") F.write("_symmetry_cell_setting triclinic" + "\n\n") F.write("loop_" + "\n") F.write("_atom_site_label" + "\n") F.write("_atom_site_type_symbol" + "\n") F.write("_atom_site_occupancy" + "\n") F.write("_atom_site_fract_x" + "\n") F.write("_atom_site_fract_y" + "\n") F.write("_atom_site_fract_z" + "\n") if centralize: self.atoms.translate_to_cell_center() counter = dict() for atom in self.atoms: atom_type = str(atom.type) if atom_type in counter.keys(): counter[atom_type] += 1 else: counter[atom_type] = 1 F.write(atom_type + str(counter[atom_type]) + " " + atom.type.real + " 1.0000 " + str(round(atom.position[0] / cell_x, 4)) + " " + str(round(atom.position[1] / cell_y, 4)) + " " + str(round(atom.position[2] / cell_z, 4)) + "\n")
class Cfg(File): """Class for LAMMPS cfg files.""" # usual line: "mass type x y z *etc" where len(x,y,z,*etc) == entry_count def __init__(self, absolute_path=None, ignore_types=None): # ignore_types is for internal use super().__init__(absolute_path=absolute_path) self.number_of_particles = None self.has_velocity = True self.atoms = Atoms([]) self.entry_count = None self.auxiliary = [] if ignore_types is None: self.ignore_types = [] else: self.ignore_types = ignore_types if self.absolute_path is not None: self.read() def read(self): index_start = self._find("Number of particles")[0] index_auxiliary = None index_atoms = None self.number_of_particles = int(self.content[index_start].split()[-1]) h0_11, h0_12, h0_13, h0_21, h0_22, h0_23, h0_31, h0_32, h0_33 = 0, 0, 0, 0, 0, 0, 0, 0, 0 if len(self._find(".NO_VELOCITY.")) == 1: self.has_velocity = False if len(self._find("A = 1 Angstrom")) == 0: print( "WARNING: distance units in cfg file may not be in Angstroms!") for (index, line) in enumerate(self.content[index_start:], index_start): if line.startswith("H0"): if "H0(1,1)" in line: h0_11 = float(line.split()[-2]) elif "H0(1,2)" in line: h0_12 = float(line.split()[-2]) elif "H0(1,3)" in line: h0_13 = float(line.split()[-2]) elif "H0(2,1)" in line: h0_21 = float(line.split()[-2]) elif "H0(2,2)" in line: h0_22 = float(line.split()[-2]) elif "H0(2,3)" in line: h0_23 = float(line.split()[-2]) elif "H0(3,1)" in line: h0_31 = float(line.split()[-2]) elif "H0(3,2)" in line: h0_32 = float(line.split()[-2]) elif "H0(3,3)" in line: h0_33 = float(line.split()[-2]) elif "entry_count" in line: self.entry_count = int(line.split()[-1]) n_standard_args = 6 if self.has_velocity else 3 for _ in range(self.entry_count - n_standard_args): self.auxiliary.append(None) index_auxiliary = index + 1 break else: continue self.atoms.cell = [[h0_11, h0_12, h0_13], [h0_21, h0_22, h0_23], [h0_31, h0_32, h0_33]] for (index, line) in enumerate(self.content[index_auxiliary:], index_auxiliary): if line.startswith("auxiliary"): i = int(find_between(line, "[", "]")) _, __, auxiliary = tuple(line.split()) self.auxiliary[i] = auxiliary else: index_atoms = index break for i in range(index_atoms, len(self.content), 3): typ = clear_end(self.content[i + 1], [" ", "\n", "\t"]) if typ in self.ignore_types: continue mass = float(self.content[i + 0]) etc = dict() if self.has_velocity: raise TypeError("cfg with velocities! not implemented yet!") else: x, y, z, *args = tuple(self.content[i + 2].split()) atom = Atom(atom_type=typ, position=[float(x), float(y), float(z)]) atom.position = np.matmul(self.atoms.cell, atom.position) atom.type.mass = mass for (index, arg) in enumerate(args): etc[self.auxiliary[index]] = arg # string atom.etc = etc self.atoms.add_atom(atom) if self.number_of_particles != len( self.atoms) and not self.ignore_types: print("WARNING: file says {} atoms, but only {} were found".format( self.number_of_particles, len(self.atoms))) def write_xyz(self, path, real_types=False): xyz = Xyz() xyz.atoms = self.atoms xyz.write_xyz(path, real_types=real_types)