def write_lammps_data(filename, atoms, atom_types, comment=None, cutoff=None, molecule_ids=None, charges=None, units='metal'): if isinstance(filename, str): fh = open(filename, 'w') else: fh = filename if comment is None: comment = 'lammpslib autogenerated data file' fh.write(comment.strip() + '\n\n') fh.write('{0} atoms\n'.format(len(atoms))) fh.write('{0} atom types\n'.format(len(atom_types))) fh.write('\n') cell, coord_transform = convert_cell(atoms.get_cell()) fh.write('{0:16.8e} {1:16.8e} xlo xhi\n'.format(0.0, cell[0, 0])) fh.write('{0:16.8e} {1:16.8e} ylo yhi\n'.format(0.0, cell[1, 1])) fh.write('{0:16.8e} {1:16.8e} zlo zhi\n'.format(0.0, cell[2, 2])) fh.write('{0:16.8e} {1:16.8e} {2:16.8e} xy xz yz\n' ''.format(cell[0, 1], cell[0, 2], cell[1, 2])) fh.write('\nMasses\n\n') sym_mass = {} masses = atoms.get_masses() symbols = atoms.get_chemical_symbols() for sym in atom_types: for i in range(len(atoms)): # TODO: Make this more efficient if symbols[i] == sym: sym_mass[sym] = convert(masses[i], "mass", "ASE", units) break else: sym_mass[sym] = convert( ase_atomic_masses[ase_chemical_symbols.index(sym)], "mass", "ASE", units) for (sym, typ) in sorted(atom_types.items(), key=operator.itemgetter(1)): fh.write('{0} {1}\n'.format(typ, sym_mass[sym])) fh.write('\nAtoms # full\n\n') if molecule_ids is None: molecule_ids = np.zeros(len(atoms), dtype=int) if charges is None: charges = atoms.get_initial_charges() for i, (sym, mol, q, pos) in enumerate( zip(symbols, molecule_ids, charges, atoms.get_positions())): typ = atom_types[sym] fh.write( '{0} {1} {2} {3:16.8e} {4:16.8e} {5:16.8e} {6:16.8e}\n'.format( i + 1, mol, typ, q, pos[0], pos[1], pos[2])) if isinstance(filename, str): fh.close()
def get_quantity(labels, quantity=None): try: cols = [colnames.index(label) for label in labels] if quantity: return convert(data[:, cols], quantity, units, "ASE") return data[:, cols] except ValueError: return None
def read_lammps_log(lammpsLog, units): """"Read lammps log file, for now we collect only TotEng""" from ase.calculators.lammps import convert with open(lammpsLog) as f: for line in f: if 'TotEng' in line: idx = line.split().index('TotEng') break for line in f: if not line.startswith('Loop'): yield convert(float(line.split()[idx]), 'energy', units, 'ASE') else: break
def set_lammps_pos(self, atoms): # Create local copy of positions that are wrapped along any periodic # directions cell = convert(atoms.cell, "distance", "ASE", self.units) pos = convert(atoms.positions, "distance", "ASE", self.units) # If necessary, transform the positions to new coordinate system if self.coord_transform is not None: pos = np.dot(pos, self.coord_transform.T) cell = np.dot(cell, self.coord_transform.T) # wrap only after scaling and rotating to reduce chances of # lammps neighbor list bugs. pos = wrap_positions(pos, cell, atoms.get_pbc()) # Convert ase position matrix to lammps-style position array # contiguous in memory lmp_positions = list(pos.ravel()) # Convert that lammps-style array into a C object c_double_array = (ctypes.c_double * len(lmp_positions)) lmp_c_positions = c_double_array(*lmp_positions) # self.lmp.put_coosrds(lmp_c_positions) self.lmp.scatter_atoms('x', 1, 3, lmp_c_positions)
def set_cell(self, atoms, change=False): lammps_cell, self.coord_transform = convert_cell(atoms.get_cell()) xhi, xy, xz, _, yhi, yz, _, _, zhi = convert( lammps_cell.flatten(order='C'), "distance", "ASE", self.units) box_hi = [xhi, yhi, zhi] if change: cell_cmd = ('change_box all ' 'x final 0 {} y final 0 {} z final 0 {} ' 'xy final {} xz final {} yz final {} units box' ''.format(xhi, yhi, zhi, xy, xz, yz)) if self.parameters.post_changebox_cmds is not None: for cmd in self.parameters.post_changebox_cmds: self.lmp.command(cmd) else: # just in case we'll want to run with a funny shape box, # and here command will only happen once, and before # any calculation if self.parameters.create_box: self.lmp.command('box tilt large') # Check if there are any indefinite boundaries. If so, # shrink-wrapping will end up being used, but we want to # define the LAMMPS region and box fairly tight around the # atoms to avoid losing any lammps_boundary_conditions = self.lammpsbc(atoms).split() if 's' in lammps_boundary_conditions: pos = atoms.get_positions() if self.coord_transform is not None: pos = np.dot(self.coord_transform, pos.transpose()) pos = pos.transpose() posmin = np.amin(pos, axis=0) posmax = np.amax(pos, axis=0) for i in range(0, 3): if lammps_boundary_conditions[i] == 's': box_hi[i] = 1.05 * abs(posmax[i] - posmin[i]) cell_cmd = ('region cell prism ' '0 {} 0 {} 0 {} ' '{} {} {} units box' ''.format(*box_hi, xy, xz, yz)) self.lmp.command(cell_cmd)
def extract_velocities(raw_datafile_contents): """ NOTE: Assumes metal units are used in data file """ velocities_block = extract_section(raw_datafile_contents, "Velocities") RE_VELOCITY = re.compile(r"\s*[0-9]+\s+(\S+)\s+(\S+)\s+(\S+)") # Now parse each individual line for velocity velocities = [] for velocities_line in velocities_block.splitlines(): v = RE_VELOCITY.match(velocities_line).groups() velocities.append(list(map(float, v))) # Convert to ASE's velocity units (uses unusual unit of time) velocities = convert(np.asarray(velocities), "velocity", "metal", "ASE") return velocities
def get_params(model_name, supported_units, supported_species, atom_style): """ Extract parameters for LAMMPS calculator from model definition lines. Returns a dictionary with entries for "pair_style" and "pair_coeff". Expects there to be only one "pair_style" line. There can be multiple "pair_coeff" lines (result is returned as a list). """ parameters = {} # In case the SM supplied its own atom_style in its model-init -- only needed # because lammpsrun writes data files and needs to know the proper format if atom_style: parameters["atom_style"] = atom_style # Set units to prevent them from defaulting to metal parameters["units"] = supported_units parameters["model_init"] = [ "kim_init {} {}{}".format(model_name, supported_units, os.linesep) ] parameters["kim_interactions"] = "kim_interactions {}{}".format( (" ").join(supported_species), os.linesep) # For every species in "supported_species", add an entry to the # "masses" key in dictionary "parameters". parameters["masses"] = [] for i, species in enumerate(supported_species): if species not in atomic_numbers: raise KIMCalculatorError( "Could not determine mass of unknown species " "{} listed as supported by model".format(species)) massstr = str( convert( atomic_masses[atomic_numbers[species]], "mass", "ASE", supported_units, )) parameters["masses"].append(str(i + 1) + " " + massstr) return parameters
def set_lammps_pos(self, atoms, wrap=True): if wrap: atoms.wrap() pos = convert(atoms.get_positions(), "distance", "ASE", self.units) # If necessary, transform the positions to new coordinate system if self.coord_transform is not None: pos = np.dot(self.coord_transform, pos.transpose()) pos = pos.transpose() # Convert ase position matrix to lammps-style position array # contiguous in memory lmp_positions = list(pos.ravel()) # Convert that lammps-style array into a C object c_double_array = (ctypes.c_double * len(lmp_positions)) lmp_c_positions = c_double_array(*lmp_positions) # self.lmp.put_coosrds(lmp_c_positions) self.lmp.scatter_atoms('x', 1, 3, lmp_c_positions)
def set_lammps_pos(self, atoms): # Create local copy of positions that are wrapped along any periodic # directions pos = wrap_positions(atoms.get_positions(), atoms.get_cell(), atoms.get_pbc()) pos = convert(pos, "distance", "ASE", self.units) # If necessary, transform the positions to new coordinate system if self.coord_transform is not None: pos = np.dot(self.coord_transform, pos.transpose()) pos = pos.transpose() # Convert ase position matrix to lammps-style position array # contiguous in memory lmp_positions = list(pos.ravel()) # Convert that lammps-style array into a C object c_double_array = (ctypes.c_double * len(lmp_positions)) lmp_c_positions = c_double_array(*lmp_positions) # self.lmp.put_coosrds(lmp_c_positions) self.lmp.scatter_atoms('x', 1, 3, lmp_c_positions)
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 lammps_data_to_ase_atoms( data, colnames, cell, celldisp, pbc=False, atomsobj=Atoms, order=True, specorder=None, prismobj=None, units="metal", ): """Extract positions and other per-atom parameters and create Atoms :param data: per atom data :param colnames: index for data :param cell: cell dimensions :param celldisp: origin shift :param pbc: periodic boundaries :param atomsobj: function to create ase-Atoms object :param order: sort atoms by id. Might be faster to turn off :param specorder: list of species to map lammps types to ase-species (usually .dump files to not contain type to species mapping) :param prismobj: Coordinate transformation between lammps and ase :type prismobj: Prism :param units: lammps units for unit transformation between lammps and ase :returns: Atoms object :rtype: Atoms """ # data array of doubles ids = data[:, colnames.index("id")].astype(int) types = data[:, colnames.index("type")].astype(int) if order: sort_order = np.argsort(ids) ids = ids[sort_order] data = data[sort_order, :] types = types[sort_order] # reconstruct types from given specorder if specorder: types = [specorder[t - 1] for t in types] def get_quantity(labels, quantity=None): try: cols = [colnames.index(label) for label in labels] if quantity: return convert(data[:, cols], quantity, units, "ASE") return data[:, cols] except ValueError: return None # slice data block into columns # + perform necessary conversions to ASE units positions = get_quantity(["x", "y", "z"], "distance") scaled_positions = get_quantity(["xs", "ys", "zs"]) velocities = get_quantity(["vx", "vy", "vz"], "velocity") charges = get_quantity(["q"], "charge") forces = get_quantity(["fx", "fy", "fz"], "force") # !TODO: how need quaternions be converted? quaternions = get_quantity(["c_q[1]", "c_q[2]", "c_q[3]", "c_q[4]"]) # convert cell cell = convert(cell, "distance", units, "ASE") celldisp = convert(celldisp, "distance", units, "ASE") if prismobj: celldisp = prismobj.vector_to_ase(celldisp) cell = prismobj.update_cell(cell) if quaternions: out_atoms = Quaternions( symbols=types, positions=positions, cell=cell, celldisp=celldisp, pbc=pbc, quaternions=quaternions, ) elif positions is not None: # reverse coordinations transform to lammps system # (for all vectors = pos, vel, force) if prismobj: positions = prismobj.vector_to_ase(positions, wrap=True) out_atoms = atomsobj( symbols=types, positions=positions, pbc=pbc, celldisp=celldisp, cell=cell ) elif scaled_positions is not None: out_atoms = atomsobj( symbols=types, scaled_positions=scaled_positions, pbc=pbc, celldisp=celldisp, cell=cell, ) if velocities is not None: if prismobj: velocities = prismobj.vector_to_ase(velocities) out_atoms.set_velocities(velocities) if charges is not None: out_atoms.set_initial_charges(charges) if forces is not None: if prismobj: forces = prismobj.vector_to_ase(forces) # !TODO: use another calculator if available (or move forces # to atoms.property) (other problem: synchronizing # parallel runs) calculator = SinglePointCalculator(out_atoms, energy=0.0, forces=forces) out_atoms.calc = calculator # process the extra columns of fixes, variables and computes # that can be dumped, add as additional arrays to atoms object for colname in colnames: # determine if it is a compute or fix (but not the quaternian) if (colname.startswith('f_') or colname.startswith('v_') or (colname.startswith('c_') and not colname.startswith('c_q['))): out_atoms.new_array(colname, get_quantity([colname]), dtype='float') return out_atoms
def initialise_lammps(self, atoms): # Initialising commands if self.parameters.boundary: # if the boundary command is in the supplied commands use that # otherwise use atoms pbc for cmd in self.parameters.lmpcmds: if 'boundary' in cmd: break else: self.lmp.command('boundary ' + self.lammpsbc(atoms)) # Initialize cell self.set_cell(atoms, change=not self.parameters.create_box) if self.parameters.atom_types is None: # if None is given, create from atoms object in order of appearance s = atoms.get_chemical_symbols() _, idx = np.unique(s, return_index=True) s_red = np.array(s)[np.sort(idx)].tolist() self.parameters.atom_types = { j: i + 1 for i, j in enumerate(s_red) } # Initialize box if self.parameters.create_box: # count number of known types n_types = len(self.parameters.atom_types) create_box_command = 'create_box {} cell'.format(n_types) self.lmp.command(create_box_command) # Initialize the atoms with their types # positions do not matter here if self.parameters.create_atoms: self.lmp.command('echo none') # don't echo the atom positions self.rebuild(atoms) self.lmp.command('echo log') # turn back on else: self.previous_atoms_numbers = atoms.numbers.copy() # execute the user commands for cmd in self.parameters.lmpcmds: self.lmp.command(cmd) # Set masses after user commands, e.g. to override # EAM-provided masses for sym in self.parameters.atom_types: if self.parameters.atom_type_masses is None: mass = ase_atomic_masses[ase_atomic_numbers[sym]] else: mass = self.parameters.atom_type_masses[sym] self.lmp.command('mass %d %.30f' % (self.parameters.atom_types[sym], convert(mass, "mass", "ASE", self.units))) # Define force & energy variables for extraction self.lmp.command('variable pxx equal pxx') self.lmp.command('variable pyy equal pyy') self.lmp.command('variable pzz equal pzz') self.lmp.command('variable pxy equal pxy') self.lmp.command('variable pxz equal pxz') self.lmp.command('variable pyz equal pyz') # I am not sure why we need this next line but LAMMPS will # raise an error if it is not there. Perhaps it is needed to # ensure the cell stresses are calculated self.lmp.command('thermo_style custom pe pxx emol ecoul') self.lmp.command('variable fx atom fx') self.lmp.command('variable fy atom fy') self.lmp.command('variable fz atom fz') # do we need this if we extract from a global ? self.lmp.command('variable pe equal pe') self.lmp.command("neigh_modify delay 0 every 1 check yes") self.initialized = True
def propagate(self, atoms, properties, system_changes, n_steps, dt=None, dt_not_real_time=False, velocity_field=None): """"atoms: Atoms object Contains positions, unit-cell, ... properties: list of str List of what needs to be calculated. Can be any combination of 'energy', 'forces', 'stress', 'dipole', 'charges', 'magmom' and 'magmoms'. system_changes: list of str List of what has changed since last calculation. Can be any combination of these five: 'positions', 'numbers', 'cell', 'pbc', 'charges' and 'magmoms'. """ if len(system_changes) == 0: return self.coord_transform = None if not self.started: self.start_lammps() if not self.initialized: self.initialise_lammps(atoms) else: # still need to reset cell # Apply only requested boundary condition changes. # Note this needs to happen # before the call to set_cell since 'change_box' will apply any # shrink-wrapping *after* it's updated the cell dimensions if 'pbc' in system_changes: change_box_str = 'change_box all boundary {}' change_box_cmd = change_box_str.format(self.lammpsbc(atoms)) self.lmp.command(change_box_cmd) # Reset positions so that if they are crazy from last # propagation, change_box (in set_cell()) won't hang. # Could do this only after testing for crazy positions? # Could also use scatter_atoms() to set values (requires # MPI comm), or extra_atoms() to get pointers to local # data structures to zero, but then we would have to be # careful with parallelism. self.lmp.command("set atom * x 0.0 y 0.0 z 0.0") self.set_cell(atoms, change=True) if self.parameters.atom_types is None: raise NameError("atom_types are mandatory.") do_rebuild = ( not np.array_equal(atoms.numbers, self.previous_atoms_numbers) or ("numbers" in system_changes)) if not do_rebuild: do_redo_atom_types = not np.array_equal( atoms.numbers, self.previous_atoms_numbers) else: do_redo_atom_types = False self.lmp.command('echo none') # don't echo the atom positions if do_rebuild: self.rebuild(atoms) elif do_redo_atom_types: self.redo_atom_types(atoms) self.lmp.command('echo log') # switch back log self.set_lammps_pos(atoms) if self.parameters.amendments is not None: for cmd in self.parameters.amendments: self.lmp.command(cmd) if n_steps > 0: if velocity_field is None: vel = convert(atoms.get_velocities(), "velocity", "ASE", self.units) else: # FIXME: Do we need to worry about converting to lammps units # here? vel = atoms.arrays[velocity_field] # If necessary, transform the velocities to new coordinate system if self.coord_transform is not None: vel = np.dot(self.coord_transform, vel.T).T # Convert ase velocities matrix to lammps-style velocities array lmp_velocities = list(vel.ravel()) # Convert that lammps-style array into a C object c_double_array = (ctypes.c_double * len(lmp_velocities)) lmp_c_velocities = c_double_array(*lmp_velocities) self.lmp.scatter_atoms('v', 1, 3, lmp_c_velocities) # Run for 0 time to calculate if dt is not None: if dt_not_real_time: self.lmp.command('timestep %.30f' % dt) else: self.lmp.command('timestep %.30f' % convert(dt, "time", "ASE", self.units)) self.lmp.command('run %d' % n_steps) if n_steps > 0: # TODO this must be slower than native copy, but why is it broken? pos = np.array([x for x in self.lmp.gather_atoms("x", 1, 3) ]).reshape(-1, 3) if self.coord_transform is not None: pos = np.dot(pos, self.coord_transform) # Convert from LAMMPS units to ASE units pos = convert(pos, "distance", self.units, "ASE") atoms.set_positions(pos) vel = np.array([v for v in self.lmp.gather_atoms("v", 1, 3) ]).reshape(-1, 3) if self.coord_transform is not None: vel = np.dot(vel, self.coord_transform) if velocity_field is None: atoms.set_velocities( convert(vel, 'velocity', self.units, 'ASE')) # Extract the forces and energy self.results['energy'] = convert( self.lmp.extract_variable('pe', None, 0), "energy", self.units, "ASE") self.results['free_energy'] = self.results['energy'] stress = np.empty(6) stress_vars = ['pxx', 'pyy', 'pzz', 'pyz', 'pxz', 'pxy'] for i, var in enumerate(stress_vars): stress[i] = self.lmp.extract_variable(var, None, 0) stress_mat = np.zeros((3, 3)) stress_mat[0, 0] = stress[0] stress_mat[1, 1] = stress[1] stress_mat[2, 2] = stress[2] stress_mat[1, 2] = stress[3] stress_mat[2, 1] = stress[3] stress_mat[0, 2] = stress[4] stress_mat[2, 0] = stress[4] stress_mat[0, 1] = stress[5] stress_mat[1, 0] = stress[5] if self.coord_transform is not None: stress_mat = np.dot(self.coord_transform.T, np.dot(stress_mat, self.coord_transform)) stress[0] = stress_mat[0, 0] stress[1] = stress_mat[1, 1] stress[2] = stress_mat[2, 2] stress[3] = stress_mat[1, 2] stress[4] = stress_mat[0, 2] stress[5] = stress_mat[0, 1] self.results['stress'] = convert(-stress, "pressure", self.units, "ASE") # definitely yields atom-id ordered force array f = convert( np.array(self.lmp.gather_atoms("f", 1, 3)).reshape(-1, 3), "force", self.units, "ASE") if self.coord_transform is not None: self.results['forces'] = np.dot(f, self.coord_transform) else: self.results['forces'] = f.copy() # otherwise check_state will always trigger a new calculation self.atoms = atoms.copy() if not self.parameters.keep_alive: self.lmp.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 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 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 read_lammps_data(fileobj, Z_of_type=None, style="full", sort_by_id=False, units="metal"): """Method which reads a LAMMPS data file. sort_by_id: Order the particles according to their id. Might be faster to switch it off. Units are set by default to the style=metal setting in LAMMPS. """ if isinstance(fileobj, str): fd = paropen(fileobj) else: fd = fileobj # load everything into memory lines = fd.readlines() # begin read_lammps_data comment = None N = None # N_types = None xlo = None xhi = None ylo = None yhi = None zlo = None zhi = None xy = None xz = None yz = None pos_in = {} travel_in = {} mol_id_in = {} charge_in = {} mass_in = {} vel_in = {} bonds_in = [] angles_in = [] dihedrals_in = [] sections = [ "Atoms", "Velocities", "Masses", "Charges", "Ellipsoids", "Lines", "Triangles", "Bodies", "Bonds", "Angles", "Dihedrals", "Impropers", "Impropers Pair Coeffs", "PairIJ Coeffs", "Pair Coeffs", "Bond Coeffs", "Angle Coeffs", "Dihedral Coeffs", "Improper Coeffs", "BondBond Coeffs", "BondAngle Coeffs", "MiddleBondTorsion Coeffs", "EndBondTorsion Coeffs", "AngleTorsion Coeffs", "AngleAngleTorsion Coeffs", "BondBond13 Coeffs", "AngleAngle Coeffs", ] header_fields = [ "atoms", "bonds", "angles", "dihedrals", "impropers", "atom types", "bond types", "angle types", "dihedral types", "improper types", "extra bond per atom", "extra angle per atom", "extra dihedral per atom", "extra improper per atom", "extra special per atom", "ellipsoids", "lines", "triangles", "bodies", "xlo xhi", "ylo yhi", "zlo zhi", "xy xz yz", ] sections_re = "(" + "|".join(sections).replace(" ", "\\s+") + ")" header_fields_re = "(" + "|".join(header_fields).replace(" ", "\\s+") + ")" section = None header = True for line in lines: if comment is None: comment = line.rstrip() else: line = re.sub("#.*", "", line).rstrip().lstrip() if re.match("^\\s*$", line): # skip blank lines continue # check for known section names m = re.match(sections_re, line) if m is not None: section = m.group(0).rstrip().lstrip() header = False continue if header: field = None val = None # m = re.match(header_fields_re+"\s+=\s*(.*)", line) # if m is not None: # got a header line # field=m.group(1).lstrip().rstrip() # val=m.group(2).lstrip().rstrip() # else: # try other format # m = re.match("(.*)\s+"+header_fields_re, line) # if m is not None: # field = m.group(2).lstrip().rstrip() # val = m.group(1).lstrip().rstrip() m = re.match("(.*)\\s+" + header_fields_re, line) if m is not None: field = m.group(2).lstrip().rstrip() val = m.group(1).lstrip().rstrip() if field is not None and val is not None: if field == "atoms": N = int(val) # elif field == "atom types": # N_types = int(val) elif field == "xlo xhi": (xlo, xhi) = [float(x) for x in val.split()] elif field == "ylo yhi": (ylo, yhi) = [float(x) for x in val.split()] elif field == "zlo zhi": (zlo, zhi) = [float(x) for x in val.split()] elif field == "xy xz yz": (xy, xz, yz) = [float(x) for x in val.split()] if section is not None: fields = line.split() if section == "Atoms": # id * id = int(fields[0]) if style == "full" and (len(fields) == 7 or len(fields) == 10): # id mol-id type q x y z [tx ty tz] pos_in[id] = ( int(fields[2]), float(fields[4]), float(fields[5]), float(fields[6]), ) mol_id_in[id] = int(fields[1]) charge_in[id] = float(fields[3]) if len(fields) == 10: travel_in[id] = ( int(fields[7]), int(fields[8]), int(fields[9]), ) elif style == "atomic" and (len(fields) == 5 or len(fields) == 8): # id type x y z [tx ty tz] pos_in[id] = ( int(fields[1]), float(fields[2]), float(fields[3]), float(fields[4]), ) if len(fields) == 8: travel_in[id] = ( int(fields[5]), int(fields[6]), int(fields[7]), ) elif (style in ("angle", "bond", "molecular")) and (len(fields) == 6 or len(fields) == 9): # id mol-id type x y z [tx ty tz] pos_in[id] = ( int(fields[2]), float(fields[3]), float(fields[4]), float(fields[5]), ) mol_id_in[id] = int(fields[1]) if len(fields) == 9: travel_in[id] = ( int(fields[6]), int(fields[7]), int(fields[8]), ) elif (style == "charge" and (len(fields) == 6 or len(fields) == 9)): # id type q x y z [tx ty tz] pos_in[id] = ( int(fields[1]), float(fields[3]), float(fields[4]), float(fields[5]), ) charge_in[id] = float(fields[2]) if len(fields) == 9: travel_in[id] = ( int(fields[6]), int(fields[7]), int(fields[8]), ) else: raise RuntimeError("Style '{}' not supported or invalid " "number of fields {}" "".format(style, len(fields))) elif section == "Velocities": # id vx vy vz vel_in[int(fields[0])] = ( float(fields[1]), float(fields[2]), float(fields[3]), ) elif section == "Masses": mass_in[int(fields[0])] = float(fields[1]) elif section == "Bonds": # id type atom1 atom2 bonds_in.append( (int(fields[1]), int(fields[2]), int(fields[3]))) elif section == "Angles": # id type atom1 atom2 atom3 angles_in.append(( int(fields[1]), int(fields[2]), int(fields[3]), int(fields[4]), )) elif section == "Dihedrals": # id type atom1 atom2 atom3 atom4 dihedrals_in.append(( int(fields[1]), int(fields[2]), int(fields[3]), int(fields[4]), int(fields[5]), )) # set cell 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 # initialize arrays for per-atom quantities positions = np.zeros((N, 3)) numbers = np.zeros((N), int) ids = np.zeros((N), int) types = np.zeros((N), int) if len(vel_in) > 0: velocities = np.zeros((N, 3)) else: velocities = None if len(mass_in) > 0: masses = np.zeros((N)) else: masses = None if len(mol_id_in) > 0: mol_id = np.zeros((N), int) else: mol_id = None if len(charge_in) > 0: charge = np.zeros((N), float) else: charge = None if len(travel_in) > 0: travel = np.zeros((N, 3), int) else: travel = None if len(bonds_in) > 0: bonds = [""] * N else: bonds = None if len(angles_in) > 0: angles = [""] * N else: angles = None if len(dihedrals_in) > 0: dihedrals = [""] * N else: dihedrals = None ind_of_id = {} # copy per-atom quantities from read-in values for (i, id) in enumerate(pos_in.keys()): # by id ind_of_id[id] = i if sort_by_id: ind = id - 1 else: ind = i type = pos_in[id][0] positions[ind, :] = [pos_in[id][1], pos_in[id][2], pos_in[id][3]] if velocities is not None: velocities[ind, :] = [vel_in[id][0], vel_in[id][1], vel_in[id][2]] if travel is not None: travel[ind] = travel_in[id] if mol_id is not None: mol_id[ind] = mol_id_in[id] if charge is not None: charge[ind] = charge_in[id] ids[ind] = id # by type types[ind] = type if Z_of_type is None: numbers[ind] = type else: numbers[ind] = Z_of_type[type] if masses is not None: masses[ind] = mass_in[type] # convert units positions = convert(positions, "distance", units, "ASE") cell = convert(cell, "distance", units, "ASE") if masses is not None: masses = convert(masses, "mass", units, "ASE") if velocities is not None: velocities = convert(velocities, "velocity", units, "ASE") # create ase.Atoms at = Atoms( positions=positions, numbers=numbers, masses=masses, cell=cell, pbc=[True, True, True], ) # set velocities (can't do it via constructor) if velocities is not None: at.set_velocities(velocities) at.arrays["id"] = ids at.arrays["type"] = types if travel is not None: at.arrays["travel"] = travel if mol_id is not None: at.arrays["mol-id"] = mol_id if charge is not None: at.arrays["initial_charges"] = charge at.arrays["mmcharges"] = charge.copy() if bonds is not None: for (type, a1, a2) in bonds_in: i_a1 = ind_of_id[a1] i_a2 = ind_of_id[a2] if len(bonds[i_a1]) > 0: bonds[i_a1] += "," bonds[i_a1] += "%d(%d)" % (i_a2, type) for i in range(len(bonds)): if len(bonds[i]) == 0: bonds[i] = "_" at.arrays["bonds"] = np.array(bonds) if angles is not None: for (type, a1, a2, a3) in angles_in: i_a1 = ind_of_id[a1] i_a2 = ind_of_id[a2] i_a3 = ind_of_id[a3] if len(angles[i_a2]) > 0: angles[i_a2] += "," angles[i_a2] += "%d-%d(%d)" % (i_a1, i_a3, type) for i in range(len(angles)): if len(angles[i]) == 0: angles[i] = "_" at.arrays["angles"] = np.array(angles) if dihedrals is not None: for (type, a1, a2, a3, a4) in dihedrals_in: i_a1 = ind_of_id[a1] i_a2 = ind_of_id[a2] i_a3 = ind_of_id[a3] i_a4 = ind_of_id[a4] if len(dihedrals[i_a1]) > 0: dihedrals[i_a1] += "," dihedrals[i_a1] += "%d-%d-%d(%d)" % (i_a2, i_a3, i_a4, type) for i in range(len(dihedrals)): if len(dihedrals[i]) == 0: dihedrals[i] = "_" at.arrays["dihedrals"] = np.array(dihedrals) at.info["comment"] = comment return at
def lammps_data_to_ase_atoms( data, colnames, cell, celldisp, pbc=False, atomsobj=Atoms, order=True, specorder=None, prismobj=None, units="metal", ): """Extract positions and other per-atom parameters and create Atoms :param data: per atom data :param colnames: index for data :param cell: cell dimensions :param celldisp: origin shift :param pbc: periodic boundaries :param atomsobj: function to create ase-Atoms object :param order: sort atoms by id. Might be faster to turn off. Disregarded in case `id` column is not given in file. :param specorder: list of species to map lammps types to ase-species (usually .dump files to not contain type to species mapping) :param prismobj: Coordinate transformation between lammps and ase :type prismobj: Prism :param units: lammps units for unit transformation between lammps and ase :returns: Atoms object :rtype: Atoms """ # read IDs if given and order if needed if "id" in colnames: ids = data[:, colnames.index("id")].astype(int) if order: sort_order = np.argsort(ids) data = data[sort_order, :] # determine the elements if "element" in colnames: # priority to elements written in file elements = data[:, colnames.index("element")] elif "type" in colnames: # fall back to `types` otherwise elements = data[:, colnames.index("type")].astype(int) # reconstruct types from given specorder if specorder: elements = [specorder[t - 1] for t in elements] else: # todo: what if specorder give but no types? # in principle the masses could work for atoms, but that needs # lots of cases and new code I guess raise ValueError("Cannot determine atom types form LAMMPS dump file") def get_quantity(labels, quantity=None): try: cols = [colnames.index(label) for label in labels] if quantity: return convert(data[:, cols].astype(float), quantity, units, "ASE") return data[:, cols].astype(float) except ValueError: return None # Positions positions = None scaled_positions = None if "x" in colnames: # doc: x, y, z = unscaled atom coordinates positions = get_quantity(["x", "y", "z"], "distance") elif "xs" in colnames: # doc: xs,ys,zs = scaled atom coordinates scaled_positions = get_quantity(["xs", "ys", "zs"]) elif "xu" in colnames: # doc: xu,yu,zu = unwrapped atom coordinates positions = get_quantity(["xu", "yu", "zu"], "distance") elif "xsu" in colnames: # xsu,ysu,zsu = scaled unwrapped atom coordinates scaled_positions = get_quantity(["xsu", "ysu", "zsu"]) else: raise ValueError("No atomic positions found in LAMMPS output") velocities = get_quantity(["vx", "vy", "vz"], "velocity") charges = get_quantity(["q"], "charge") forces = get_quantity(["fx", "fy", "fz"], "force") # !TODO: how need quaternions be converted? quaternions = get_quantity(["c_q[1]", "c_q[2]", "c_q[3]", "c_q[4]"]) # convert cell cell = convert(cell, "distance", units, "ASE") celldisp = convert(celldisp, "distance", units, "ASE") if prismobj: celldisp = prismobj.vector_to_ase(celldisp) cell = prismobj.update_cell(cell) if quaternions: out_atoms = Quaternions( symbols=elements, positions=positions, cell=cell, celldisp=celldisp, pbc=pbc, quaternions=quaternions, ) elif positions is not None: # reverse coordinations transform to lammps system # (for all vectors = pos, vel, force) if prismobj: positions = prismobj.vector_to_ase(positions, wrap=True) out_atoms = atomsobj(symbols=elements, positions=positions, pbc=pbc, celldisp=celldisp, cell=cell) elif scaled_positions is not None: out_atoms = atomsobj( symbols=elements, scaled_positions=scaled_positions, pbc=pbc, celldisp=celldisp, cell=cell, ) if velocities is not None: if prismobj: velocities = prismobj.vector_to_ase(velocities) out_atoms.set_velocities(velocities) if charges is not None: out_atoms.set_initial_charges(charges) if forces is not None: if prismobj: forces = prismobj.vector_to_ase(forces) # !TODO: use another calculator if available (or move forces # to atoms.property) (other problem: synchronizing # parallel runs) calculator = SinglePointCalculator(out_atoms, energy=0.0, forces=forces) out_atoms.calc = calculator # process the extra columns of fixes, variables and computes # that can be dumped, add as additional arrays to atoms object for colname in colnames: # determine if it is a compute or fix (but not the quaternian) if (colname.startswith('f_') or colname.startswith('v_') or (colname.startswith('c_') and not colname.startswith('c_q['))): out_atoms.new_array(colname, get_quantity([colname]), dtype='float') return out_atoms