def check_configs(structure_lmpdat, gas_lmpdat, verbose): atoms = Atoms.from_lammps_data(structure_lmpdat, use_comment_for_type_labels=True) gas_atoms = Atoms.from_lammps_data(gas_lmpdat, use_comment_for_type_labels=True) # check for charges num_zero_charges = np.count_nonzero(atoms.charges == 0.0) if num_zero_charges > 0: print("WARNING: %d/%d atoms in structure have 0 charges" % (num_zero_charges, len(atoms.charges))) num_zero_charges = np.count_nonzero(gas_atoms.charges == 0.0) if num_zero_charges > 0: print("WARNING: %d/%d atoms in gas have 0 charges" % (num_zero_charges, len(gas_atoms.charges))) # check for overlapped atom positions atoms.extend(gas_atoms) s_ss = distance.cdist(atoms.positions, atoms.positions, "sqeuclidean") min_dist_sq = s_ss[np.triu_indices_from(s_ss, k=1)].min() min_dist_indices = list(zip(*np.where(s_ss == min_dist_sq))) min_dist = min_dist_sq ** 0.5 if verbose: print("min distance between atoms: %6.4f" % min_dist) if verbose: zero_indices = [(i1,i2) for i1, i2 in np.argwhere(s_ss < 0.1) if i1 < i2] for i1, i2 in zero_indices: print("WARNING: some atoms look like they are overlapped: %d %d (%s-%s) dist=%6.4f" % (i1+1, i2+1, atoms.elements[i1], atoms.elements[i2], s_ss[i1,i2] ** 0.5))
def checkdumpfile(lmpdatpath, dumppath, warns=True, verbose=False): atoms = Atoms.from_lammps_data(open(lmpdatpath, "r"), use_comment_for_type_labels=True) dumpatoms = ase.io.read(dumppath, format="lammps-dump-text") assert len(dumpatoms.positions) == len(atoms.positions) atoms.positions = dumpatoms.positions # check for overlapped atom positions s_ss = distance.cdist(atoms.positions, atoms.positions, "sqeuclidean") min_dist_sq = s_ss[np.triu_indices_from(s_ss, k=1)].min() min_dist_indices = list(zip(*np.where(s_ss == min_dist_sq))) min_dist = min_dist_sq**0.5 if verbose: print("min distance between atoms: %6.4f" % min_dist) if verbose: zero_indices = [(i1, i2) for i1, i2 in np.argwhere(s_ss < 0.1) if i1 < i2] for i1, i2 in zero_indices: print( "WARNING: some atoms look like they are overlapped: %d %d (%s-%s) dist=%6.4f" % (i1 + 1, i2 + 1, atoms.elements[i1], atoms.elements[i2], s_ss[i1, i2]**0.5)) # check for bond distances dumpatoms.set_pbc(True) bonddists = np.array( [dumpatoms.get_distance(b1, b2, mic=True) for b1, b2 in atoms.bonds]) bonds_half_uc = (bonddists > dumpatoms.cell.lengths().min() / 2).any() if verbose: if bonds_half_uc: print( "WARNING: some bonds appear to have lengths > 1/2 the smallest UC dimension." ) print(bonddists) else: print( "INFO: all bonds have lengths < 1/2 the smallest UC dimension." ) print("maximum bond length is %.2f Å" % max(bonddists)) print("min bond length is %.2f Å" % min(bonddists)) else: if len(min_dist_indices) > 0: min_dist_indices_s = "(%4d %4d %2s - %-2s)" % ( min_dist_indices[0][0] + 1, min_dist_indices[0][1] + 1, atoms.elements[min_dist_indices[0][0]], atoms.elements[min_dist_indices[0][1]]) else: min_dist_indices_s = "(---- ----)" print("min_dist: %.2f %s; bonds: %.2f-%.2f" % (min_dist, min_dist_indices_s, min(bonddists), max(bonddists)), end='') if warns: if bonds_half_uc: print("; WARN: bonds > 1/2 UC", end='') if min_dist < 0.92 and min_dist < min(bonddists) - 1e-2: print("; WARN: atoms closer than shortest bond", end='')
def updatelmpdatpositions(lmpdatpath, dumppath, outputfile): atoms = Atoms.from_lammps_data(open(lmpdatpath, "r"), use_comment_for_type_labels=True) # update positions in original atoms file with new positions dumpatoms = ase.io.read(dumppath, format="lammps-dump-text") assert len(dumpatoms.positions) == len(atoms.positions) atoms.positions = dumpatoms.positions atoms.to_lammps_data(outputfile)
def packmol_gaslmpdat(structure_lmpdat, structure_xyz, gas_lmpdat, gas_xyz, num_molecules=1): """ packs gas into structure using packmol and converts the result into a LAMMPS lmpdat file. The resulting lmpdat file has only the gas molecules in it, according to the geometry and coeffs of the gas_lmpdat file. Note that it is redundant to have both an XYZ file input and LAMMPS file input but we have it this way because this method is used in large-scale screenings and both these files are generated at the beginning of the run. Args: structure_lmpdat: lmpdat file of structure; this is used to get the unit cell for packmol. structure_xyz: Path to xyz file of structure to pack the gas into. gas_lmpdat: lmpdat file with geometry and coeffs for gas we are packing. gas_xyz: xyz file of gas corresponding to the the gas_lmpdat. Atoms must be in the same order! num_molecules (int): number of gas molecules to pack into structure. """ gas_name = Path(gas_lmpdat).stem structure_name = Path(structure_xyz).stem satoms = Atoms.from_lammps_data(structure_lmpdat, use_comment_for_type_labels=True) output_gas_xyz = "%s_%s_packed.xyz" % (structure_name, gas_name) packmol_input = packmol_config(structure_xyz, gas_xyz, output_gas_xyz, num_molecules=num_molecules, boundary_tolerance=1.5, a2a_tolerance=1.5, supercell=[*np.diag(satoms.cell), 90, 90, 90]) with open("packmol.input", 'w') as f: f.write(packmol_input) subprocess.run("/Users/pboone/workspace/_prereqs/packmol/packmol < packmol.input", shell=True, check=True) gas_data = np.array(extract_gas_atoms_from_packmol_xyz(output_gas_xyz)) # update the dummy positions in the template gas lmpdat file with real positions from packmol # and save in the current directory. Note that this should NOT overwrite the template, since you # should be in a different directory at this point. atoms = Atoms.from_lammps_data(open(gas_lmpdat,'r'), use_comment_for_type_labels=True) atoms.positions = np.array(gas_data[:, 1:], dtype=float) atoms.atom_types = np.tile(atoms.atom_types, num_molecules) atoms.charges = np.tile(atoms.charges, num_molecules) atoms.atom_groups = np.repeat(np.arange(num_molecules), len(gas_data) / num_molecules) atoms.cell = satoms.cell atoms.to_lammps_data(open("%s.lmpdat" % gas_name, 'w')) Path("tmp").mkdir(exist_ok=True) Path("packmol.input").rename("tmp/packmol.input") Path(output_gas_xyz).rename(Path("tmp/") / output_gas_xyz)
def lmpdat2cif(lmpdatpath, dumppath=None, outpath=None, framework_element=None): atoms = Atoms.from_lammps_data(open(lmpdatpath, "r"), use_comment_for_type_labels=True) if dumppath is not None: # update positions in original atoms file with new positions dumpatoms = ase.io.read(dumppath, format="lammps-dump-text") assert len(dumpatoms.positions) == len(atoms.positions) atoms.positions = dumpatoms.positions aseatoms = atoms.to_ase() if framework_element is not None: aseatoms.symbols[atoms.atom_groups == 0] = framework_element aseatoms.set_pbc(True) if outpath is None: aseatoms.write('-', format="cif") else: aseatoms.write(outpath)
def functionalize_structure_with_linkers(structure_path, linker_path, fnlinkers, output_dir=Path()): linker = Atoms.from_cml(Path(linker_path)) structure = Atoms.from_cif(structure_path) output_dir = Path(output_dir) assign_pair_params_to_structure(structure) for fnlinker_path in fnlinkers: print("reading %s" % fnlinker_path) fnlinker = Atoms.from_lammps_data(open(fnlinker_path, "r"), use_comment_for_type_labels=True) try: new_structure = replace_pattern_in_structure( structure, linker, fnlinker) with open( output_dir.joinpath(Path(fnlinker_path).stem + ".lmpdat"), "w") as fd: new_structure.to_lammps_data(fd) except Exception as e: print("ERROR! ", e.args)