def run(self, set_atoms=False): # !TODO: split this function """Method which explicitly runs LAMMPS.""" pbc = self.atoms.get_pbc() if all(pbc): cell = self.atoms.get_cell() elif not any(pbc): # large enough cell for non-periodic calculation - # LAMMPS shrink-wraps automatically via input command # "periodic s s s" # below cell = 2 * np.max(np.abs(self.atoms.get_positions())) * np.eye(3) else: warnings.warn( "semi-periodic ASE cell detected - translation " + "to proper LAMMPS input cell might fail" ) cell = self.atoms.get_cell() self.prism = Prism(cell) self.set_missing_parameters() self.calls += 1 # change into subdirectory for LAMMPS calculations cwd = os.getcwd() os.chdir(self.parameters.tmp_dir) # setup file names for LAMMPS calculation label = "{0}{1:>06}".format(self.label, self.calls) lammps_in = uns_mktemp( prefix="in_" + label, dir=self.parameters.tmp_dir ) lammps_log = uns_mktemp( prefix="log_" + label, dir=self.parameters.tmp_dir ) lammps_trj_fd = NamedTemporaryFile( prefix="trj_" + label, suffix=(".bin" if self.parameters.binary_dump else ""), dir=self.parameters.tmp_dir, delete=(not self.parameters.keep_tmp_files), ) lammps_trj = lammps_trj_fd.name if self.parameters.no_data_file: lammps_data = None else: lammps_data_fd = NamedTemporaryFile( prefix="data_" + label, dir=self.parameters.tmp_dir, delete=(not self.parameters.keep_tmp_files), mode='w', encoding='ascii' ) write_lammps_data( lammps_data_fd, self.atoms, specorder=self.parameters.specorder, force_skew=self.parameters.always_triclinic, velocities=self.parameters.write_velocities, prismobj=self.prism, units=self.parameters.units, atom_style=self.parameters.atom_style ) lammps_data = lammps_data_fd.name lammps_data_fd.flush() # see to it that LAMMPS is started if not self._lmp_alive(): command = self.get_lammps_command() # Attempt to (re)start lammps self._lmp_handle = Popen( shlex.split(command, posix=(os.name == "posix")), stdin=PIPE, stdout=PIPE, ) lmp_handle = self._lmp_handle # Create thread reading lammps stdout (for reference, if requested, # also create lammps_log, although it is never used) if self.parameters.keep_tmp_files: lammps_log_fd = open(lammps_log, "wb") fd = SpecialTee(lmp_handle.stdout, lammps_log_fd) else: fd = lmp_handle.stdout thr_read_log = Thread(target=self.read_lammps_log, args=(fd,)) thr_read_log.start() # write LAMMPS input (for reference, also create the file lammps_in, # although it is never used) if self.parameters.keep_tmp_files: lammps_in_fd = open(lammps_in, "wb") fd = SpecialTee(lmp_handle.stdin, lammps_in_fd) else: fd = lmp_handle.stdin write_lammps_in( lammps_in=fd, parameters=self.parameters, atoms=self.atoms, prismobj=self.prism, lammps_trj=lammps_trj, lammps_data=lammps_data, ) if self.parameters.keep_tmp_files: lammps_in_fd.close() # Wait for log output to be read (i.e., for LAMMPS to finish) # and close the log file if there is one thr_read_log.join() if self.parameters.keep_tmp_files: lammps_log_fd.close() if not self.parameters.keep_alive: self._lmp_end() exitcode = lmp_handle.poll() if exitcode and exitcode != 0: cwd = os.getcwd() raise RuntimeError( "LAMMPS exited in {} with exit code: {}." "".format(cwd, exitcode) ) # A few sanity checks if len(self.thermo_content) == 0: raise RuntimeError("Failed to retrieve any thermo_style-output") if int(self.thermo_content[-1]["atoms"]) != len(self.atoms): # This obviously shouldn't happen, but if prism.fold_...() fails, # it could raise RuntimeError("Atoms have gone missing") trj_atoms = read_lammps_dump( infileobj=lammps_trj, order=False, index=-1, prismobj=self.prism, specorder=self.parameters.specorder, ) if set_atoms: self.atoms = trj_atoms.copy() self.forces = trj_atoms.get_forces() # !TODO: trj_atoms is only the last snapshot of the system; Is it # desirable to save also the inbetween steps? if self.parameters.trajectory_out is not None: # !TODO: is it advisable to create here temporary atoms-objects self.trajectory_out.write(trj_atoms) tc = self.thermo_content[-1] self.results["energy"] = convert( tc["pe"], "energy", self.parameters["units"], "ASE" ) self.results["free_energy"] = self.results["energy"] self.results["forces"] = self.forces.copy() stress = np.array( [-tc[i] for i in ("pxx", "pyy", "pzz", "pyz", "pxz", "pxy")] ) # We need to apply the Lammps rotation stuff to the stress: xx, yy, zz, yz, xz, xy = stress stress_tensor = np.array([[xx, xy, xz], [xy, yy, yz], [xz, yz, zz]]) R = self.prism.rot_mat stress_atoms = np.dot(R, stress_tensor) stress_atoms = np.dot(stress_atoms, R.T) stress_atoms = stress_atoms[[0, 1, 2, 1, 0, 0], [0, 1, 2, 2, 2, 1]] stress = stress_atoms self.results["stress"] = convert( stress, "pressure", self.parameters["units"], "ASE" ) lammps_trj_fd.close() if not self.parameters.no_data_file: lammps_data_fd.close() os.chdir(cwd)
def writeLammpsData( atoms, data='data', specorder=None, masses={ 'Al': 26.9820, 'C': 12.0000, 'H': 1.0080, 'O': 15.9990, 'N': 14.0000, 'F': 18.9980 }, force_skew=False, velocities=False, units="real", atom_style='charge'): """Write atomic structure data to a LAMMPS data_ file.""" f = open(data, "w", encoding="ascii") if isinstance(atoms, list): if len(atoms) > 1: raise ValueError( "Can only write one configuration to a lammps data file!") atoms = atoms[0] f.write("{0} (written by ASE) \n\n".format(f.name)) symbols = atoms.get_chemical_symbols() n_atoms = len(symbols) f.write("{0} \t atoms \n".format(n_atoms)) if specorder is None: # This way it is assured that LAMMPS atom types are always # assigned predictably according to the alphabetic order species = sorted(set(symbols)) else: # To index elements in the LAMMPS data file # (indices must correspond to order in the potential file) species = specorder n_atom_types = len(species) f.write("{0} atom types\n".format(n_atom_types)) p = Prism(atoms.get_cell()) xhi, yhi, zhi, xy, xz, yz = convert(p.get_lammps_prism(), "distance", "ASE", units) f.write("0.0 {0:23.17g} xlo xhi\n".format(xhi)) f.write("0.0 {0:23.17g} ylo yhi\n".format(yhi)) f.write("0.0 {0:23.17g} zlo zhi\n".format(zhi)) if force_skew or p.is_skewed(): f.write("{0:23.17g} {1:23.17g} {2:23.17g} xy xz yz\n".format( xy, xz, yz)) f.write("\n\n") f.write("Masses \n\n") for i, sp in enumerate(species): f.write("%d %6.4f\n" % (i + 1, masses[sp])) f.write("\n\n") f.write("Atoms \n\n") pos = p.vector_to_lammps(atoms.get_positions(), wrap=True) if atom_style == 'atomic': for i, r in enumerate(pos): # Convert position from ASE units to LAMMPS units r = convert(r, "distance", "ASE", units) s = species.index(symbols[i]) + 1 f.write("{0:>6} {1:>3} {2:23.17g} {3:23.17g} {4:23.17g}\n".format( *(i + 1, s) + tuple(r))) elif atom_style == 'charge': charges = atoms.get_initial_charges() for i, (q, r) in enumerate(zip(charges, pos)): # Convert position and charge from ASE units to LAMMPS units r = convert(r, "distance", "ASE", units) q = convert(q, "charge", "ASE", units) s = species.index(symbols[i]) + 1 f.write("{0:>6} {1:>3} {2:>5} {3:23.17g} {4:23.17g} {5:23.17g}\n". format(*(i + 1, s, q) + tuple(r))) elif atom_style == 'full': charges = atoms.get_initial_charges() molecule = 1 # Assign all atoms to a single molecule for i, (q, r) in enumerate(zip(charges, pos)): # Convert position and charge from ASE units to LAMMPS units r = convert(r, "distance", "ASE", units) q = convert(q, "charge", "ASE", units) s = species.index(symbols[i]) + 1 f.write( "{0:>6} {1:>3} {2:>3} {3:>5} {4:23.17g} {5:23.17g} {6:23.17g}\n" .format(*(i + 1, molecule, s, q) + tuple(r))) else: raise NotImplementedError if velocities and atoms.get_velocities() is not None: f.write("\n\nVelocities \n\n") vel = p.vector_to_lammps(atoms.get_velocities()) for i, v in enumerate(vel): # Convert velocity from ASE units to LAMMPS units v = convert(v, "velocity", "ASE", units) f.write("{0:>6} {1:23.17g} {2:23.17g} {3:23.17g}\n".format( *(i + 1, ) + tuple(v))) f.flush() f.close()
def write_lammps_data(fileobj, atoms, specorder=None, force_skew=False, prismobj=None, velocities=False, units="metal", atom_style='atomic'): """Write atomic structure data to a LAMMPS data file.""" if isinstance(fileobj, basestring): f = paropen(fileobj, "w", encoding="ascii") close_file = True else: # Presume fileobj acts like a fileobj f = fileobj close_file = False # FIXME: We should add a check here that the encoding of the file object # is actually ascii once the 'encoding' attribute of IOFormat objects # starts functioning in implementation (currently it doesn't do # anything). if isinstance(atoms, list): if len(atoms) > 1: raise ValueError( "Can only write one configuration to a lammps data file!" ) atoms = atoms[0] f.write("{0} (written by ASE) \n\n".format(f.name)) symbols = atoms.get_chemical_symbols() n_atoms = len(symbols) f.write("{0} \t atoms \n".format(n_atoms)) if specorder is None: # This way it is assured that LAMMPS atom types are always # assigned predictably according to the alphabetic order species = sorted(set(symbols)) else: # To index elements in the LAMMPS data file # (indices must correspond to order in the potential file) species = specorder n_atom_types = len(species) f.write("{0} atom types\n".format(n_atom_types)) if prismobj is None: p = Prism(atoms.get_cell()) else: p = prismobj # Get cell parameters and convert from ASE units to LAMMPS units xhi, yhi, zhi, xy, xz, yz = convert(p.get_lammps_prism(), "distance", "ASE", units) f.write("0.0 {0:23.17g} xlo xhi\n".format(xhi)) f.write("0.0 {0:23.17g} ylo yhi\n".format(yhi)) f.write("0.0 {0:23.17g} zlo zhi\n".format(zhi)) if force_skew or p.is_skewed(): f.write( "{0:23.17g} {1:23.17g} {2:23.17g} xy xz yz\n".format( xy, xz, yz ) ) f.write("\n\n") f.write("Atoms \n\n") pos = p.vector_to_lammps(atoms.get_positions(), wrap=True) if atom_style == 'atomic': for i, r in enumerate(pos): # Convert position from ASE units to LAMMPS units r = convert(r, "distance", "ASE", units) s = species.index(symbols[i]) + 1 f.write( "{0:>6} {1:>3} {2:23.17g} {3:23.17g} {4:23.17g}\n".format( *(i + 1, s) + tuple(r) ) ) elif atom_style == 'charge': charges = atoms.get_initial_charges() for i, (q, r) in enumerate(zip(charges, pos)): # Convert position and charge from ASE units to LAMMPS units r = convert(r, "distance", "ASE", units) q = convert(q, "charge", "ASE", units) s = species.index(symbols[i]) + 1 f.write( "{0:>6} {1:>3} {2:>5} {3:23.17g} {4:23.17g} {5:23.17g}\n".format( *(i + 1, s, q) + tuple(r) ) ) elif atom_style == 'full': charges = atoms.get_initial_charges() molecule = 1 # Assign all atoms to a single molecule for i, (q, r) in enumerate(zip(charges, pos)): # Convert position and charge from ASE units to LAMMPS units r = convert(r, "distance", "ASE", units) q = convert(q, "charge", "ASE", units) s = species.index(symbols[i]) + 1 f.write( "{0:>6} {1:>3} {2:>3} {3:>5} {4:23.17g} {5:23.17g} {6:23.17g}\n".format( *(i + 1, molecule, s, q) + tuple(r) ) ) else: raise NotImplementedError if velocities and atoms.get_velocities() is not None: f.write("\n\nVelocities \n\n") vel = p.vector_to_lammps(atoms.get_velocities()) for i, v in enumerate(vel): # Convert velocity from ASE units to LAMMPS units v = convert(v, "velocity", "ASE", units) f.write( "{0:>6} {1:23.17g} {2:23.17g} {3:23.17g}\n".format( *(i + 1,) + tuple(v) ) ) f.flush() if close_file: f.close()
def write_lammps_data(fileobj, atoms, specorder=None, force_skew=False, prismobj=None, velocities=False, units="metal", atom_style='atomic'): """Write atomic structure data to a LAMMPS data file.""" if isinstance(fileobj, str): fd = paropen(fileobj, "w", encoding="ascii") close_file = True else: # Presume fileobj acts like a fileobj fd = fileobj close_file = False # FIXME: We should add a check here that the encoding of the file object # is actually ascii once the 'encoding' attribute of IOFormat objects # starts functioning in implementation (currently it doesn't do # anything). if isinstance(atoms, list): if len(atoms) > 1: raise ValueError( "Can only write one configuration to a lammps data file!") atoms = atoms[0] if hasattr(fd, "name"): fd.write("{0} (written by ASE) \n\n".format(fd.name)) else: fd.write("(written by ASE) \n\n") symbols = atoms.get_chemical_symbols() n_atoms = len(symbols) fd.write("{0} \t atoms \n".format(n_atoms)) if specorder is None: # This way it is assured that LAMMPS atom types are always # assigned predictably according to the alphabetic order species = sorted(set(symbols)) else: # To index elements in the LAMMPS data file # (indices must correspond to order in the potential file) species = specorder n_atom_types = len(species) fd.write("{0} atom types\n".format(n_atom_types)) if prismobj is None: p = Prism(atoms.get_cell()) else: p = prismobj # Get cell parameters and convert from ASE units to LAMMPS units xhi, yhi, zhi, xy, xz, yz = convert(p.get_lammps_prism(), "distance", "ASE", units) fd.write("0.0 {0:23.17g} xlo xhi\n".format(xhi)) fd.write("0.0 {0:23.17g} ylo yhi\n".format(yhi)) fd.write("0.0 {0:23.17g} zlo zhi\n".format(zhi)) if force_skew or p.is_skewed(): fd.write("{0:23.17g} {1:23.17g} {2:23.17g} xy xz yz\n".format( xy, xz, yz)) fd.write("\n\n") fd.write("Atoms \n\n") pos = p.vector_to_lammps(atoms.get_positions(), wrap=True) if atom_style == 'atomic': for i, r in enumerate(pos): # Convert position from ASE units to LAMMPS units r = convert(r, "distance", "ASE", units) s = species.index(symbols[i]) + 1 fd.write("{0:>6} {1:>3} {2:23.17g} {3:23.17g} {4:23.17g}\n".format( *(i + 1, s) + tuple(r))) elif atom_style == 'charge': charges = atoms.get_initial_charges() for i, (q, r) in enumerate(zip(charges, pos)): # Convert position and charge from ASE units to LAMMPS units r = convert(r, "distance", "ASE", units) q = convert(q, "charge", "ASE", units) s = species.index(symbols[i]) + 1 fd.write("{0:>6} {1:>3} {2:>5} {3:23.17g} {4:23.17g} {5:23.17g}\n". format(*(i + 1, s, q) + tuple(r))) elif atom_style == 'full': charges = atoms.get_initial_charges() # The label 'mol-id' has apparenlty been introduced in read earlier, # but so far not implemented here. Wouldn't a 'underscored' label # be better, i.e. 'mol_id' or 'molecule_id'? if atoms.has('mol-id'): molecules = atoms.get_array('mol-id') if not np.issubdtype(molecules.dtype, np.integer): raise TypeError( ("If 'atoms' object has 'mol-id' array, then" " mol-id dtype must be subtype of np.integer, and" " not {:s}.").format(str(molecules.dtype))) if (len(molecules) != len(atoms)) or (molecules.ndim != 1): raise TypeError(("If 'atoms' object has 'mol-id' array, then" " each atom must have exactly one mol-id.")) else: # Assigning each atom to a distinct molecule id would seem # preferableabove assigning all atoms to a single molecule id per # default, as done within ase <= v 3.19.1. I.e., # molecules = np.arange(start=1, stop=len(atoms)+1, step=1, dtype=int) # However, according to LAMMPS default behavior, molecules = np.zeros(len(atoms)) # which is what happens if one creates new atoms within LAMMPS # without explicitly taking care of the molecule id. # Quote from docs at https://lammps.sandia.gov/doc/read_data.html: # The molecule ID is a 2nd identifier attached to an atom. # Normally, it is a number from 1 to N, identifying which # molecule the atom belongs to. It can be 0 if it is a # non-bonded atom or if you don't care to keep track of molecule # assignments. for i, (m, q, r) in enumerate(zip(molecules, charges, pos)): # Convert position and charge from ASE units to LAMMPS units r = convert(r, "distance", "ASE", units) q = convert(q, "charge", "ASE", units) s = species.index(symbols[i]) + 1 fd.write("{0:>6} {1:>3} {2:>3} {3:>5} {4:23.17g} {5:23.17g} " "{6:23.17g}\n".format(*(i + 1, m, s, q) + tuple(r))) else: raise NotImplementedError if velocities and atoms.get_velocities() is not None: fd.write("\n\nVelocities \n\n") vel = p.vector_to_lammps(atoms.get_velocities()) for i, v in enumerate(vel): # Convert velocity from ASE units to LAMMPS units v = convert(v, "velocity", "ASE", units) fd.write("{0:>6} {1:23.17g} {2:23.17g} {3:23.17g}\n".format( *(i + 1, ) + tuple(v))) fd.flush() if close_file: fd.close()
def create_bulk_crystal(name, size, round="up"): """Create a bulk crystal from a spacegroup description. :param name: name of the crystal. A list can be found by @TODO :type name: str :param size: size of the bulk crystal. In the case of a triclinic cell, the dimensions are the ones along the diagonal of the cell matrix, and the crystal tilt decides the rest. :type size: array_like with 3 elements :return: ase.Atoms object containing the crystal :rtype: ase.Atoms """ crystal = crystals[name] a, b, c, alpha, beta, gamma = [ crystal[i] for i in ["a", "b", "c", "alpha", "beta", "gamma"] ] lx, ly, lz = size[0], size[1], size[2] # cellpar = [a, b, c, alpha, beta, gamma] repeats = [ lx / a, ly / b / np.sin(np.radians(gamma)), lz / c / np.sin(np.radians(alpha)) / np.sin(np.radians(beta)) ] if round == "up": repeats = [int(np.ceil(i)) for i in repeats] elif round == "down": repeats = [int(np.floor(i)) for i in repeats] elif round == "round": repeats = [int(round(i)) for i in repeats] else: raise ValueError myCrystal = ase.spacegroup.crystal( crystal["elements"], crystal["positions"], spacegroup=crystal["spacegroup"], cellpar=[ crystal[i] for i in ["a", "b", "c", "alpha", "beta", "gamma"] ], size=repeats) ############################################################################### # Creating a Lammps prism and then recreating the ase cell is necessary # to avoid flipping of the simulation cell when outputing the lammps data file # By making the transformation here, what we see in the lammps output is the same as # the system we are actually carving into p = Prism(myCrystal.cell) xhi, yhi, zhi, xy, xz, yz = p.get_lammps_prism() xlo, ylo, zlo = 0, 0, 0 cell = np.zeros((3, 3)) cell[0, 0] = xhi - xlo cell[1, 1] = yhi - ylo cell[2, 2] = zhi - zlo if xy is not None: cell[1, 0] = xy if xz is not None: cell[2, 0] = xz if yz is not None: cell[2, 1] = yz myCrystal.set_cell(cell) myCrystal.wrap() ################################################################################## return myCrystal