def test_electrostatics(self): """Tests that the results are consistent with the electrostatic interpretation. Each matrix [i, j] element should correspond to the Coulomb energy of a system consisting of the pair of atoms i, j. """ system = H2O n_atoms = len(system) a = 0.5 desc = EwaldSumMatrix(n_atoms_max=3, permutation="none", flatten=False) # The Ewald matrix contains the electrostatic interaction between atoms # i and j. Here we construct the total electrostatic energy for a # system consisting of atoms i and j. matrix = desc.create(system, a=a, rcut=rcut, gcut=gcut) energy_matrix = np.zeros(matrix.shape) for i in range(n_atoms): for j in range(n_atoms): if i == j: energy_matrix[i, j] = matrix[i, j] else: energy_matrix[i, j] = matrix[i, j] + matrix[i, i] + matrix[j, j] # Converts unit of q*q/r into eV conversion = 1e10 * scipy.constants.e / (4 * math.pi * scipy.constants.epsilon_0) energy_matrix *= conversion # The value in each matrix element should correspond to the Coulomb # energy of a system with with only those atoms. Here the energies from # the Ewald matrix are compared against the Ewald energy calculated # with pymatgen. positions = system.get_positions() atomic_num = system.get_atomic_numbers() for i in range(n_atoms): for j in range(n_atoms): if i == j: pos = [positions[i]] sym = [atomic_num[i]] else: pos = [positions[i], positions[j]] sym = [atomic_num[i], atomic_num[j]] i_sys = Atoms( cell=system.get_cell(), positions=pos, symbols=sym, pbc=True, ) structure = Structure( lattice=i_sys.get_cell(), species=i_sys.get_atomic_numbers(), coords=i_sys.get_scaled_positions(), ) structure.add_oxidation_state_by_site(i_sys.get_atomic_numbers()) ewald = EwaldSummation(structure, eta=a, real_space_cut=rcut, recip_space_cut=gcut) energy = ewald.total_energy # Check that the energy given by the pymatgen implementation is # the same as given by the descriptor self.assertTrue(np.allclose(energy_matrix[i, j], energy, atol=0.00001, rtol=0))
def __init__(self, blocks, blmin, volume, cellbounds=None, cell=None, splits={(1, ): 1}): self.volume = volume self.cellbounds = cellbounds self.cell = cell # normalize splitting probabilities: tot = sum([v for v in splits.values()]) self.splits = {k: v * 1. / tot for k, v in splits.items()} # pre-process blocks and blmin: self.blocks = [] numbers = [] for block, count in blocks: if isinstance(block, Atoms): pass elif block in atomic_numbers: block = Atoms(block) else: block = molecule(block) self.blocks.append((block, count)) numbers.extend(block.get_atomic_numbers()) numbers = np.unique(numbers) if type(blmin) == dict: self.blmin = blmin else: self.blmin = closest_distances_generator(numbers, blmin)
def getSpaceGroup(elements, coords, la, lb, lc): print(elements) #coords = getFracCoords() crystal = Atoms([elements[0]] * 20, coords, pbc=[1, 1, 1]) lattice = [[la, 0, 0], [0, lb, 0], [0, 0, lc]] cell = (lattice, coords, crystal.get_atomic_numbers()) spacegroup = spglib.get_spacegroup(cell, symprec=1e-3) print(spacegroup, crystal.get_atomic_numbers()) return (spacegroup)
def ase_atoms_to_spglib_cell( structure: Atoms) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """ Returns a tuple of three components: cell metric, atomic positions, and atomic species of the input ASE Atoms object. """ return (structure.cell, structure.get_scaled_positions(), structure.get_atomic_numbers())
def draw_CPKsurf(interfc, ascale=1.0, outName=None, zLim=None, title=''): plt.rcParams["font.family"] = "Dejavu Serif" print(' |- Drawing CPK pic of \t%s to\t%s'%\ (interfc.tags, outName+'-cpk.png')) atoms = interfc.get_allAtoms() if zLim is None: zLim = [(interfc.get_subPos()[:, 2].min() + interfc.get_subPos()[:, 2].max()) / 2, interfc.get_pos()[:, 2].max()] atoms = atoms[[a.index for a in atoms \ if zLim[0] < interfc.get_pos()[a.index][2] <= zLim[1]]] # atoms = atoms[[a.index for a in atoms \ # if zLim[0] < allpos[a.index][2] <= zLim[1]]] ori_cell = convert_cell(atoms.get_cell()) atoms_old = atoms.copy() atoms = Atoms(sorted(atoms, key=lambda atm: atm.position[2])) allnum = atoms.get_atomic_numbers() allpos = atoms.get_positions() adsList = interfc.get_adsList() bufList = interfc.get_bufList() adsList = [i for i in range(len(atoms)) \ if atoms.get_positions()[i] in interfc.get_pos()[adsList]] bufList = [i for i in range(len(atoms)) \ if atoms.get_positions()[i] in interfc.get_pos()[bufList]] allc = [ 0 if i in adsList # Full color else 0.5 if i in bufList # Shallow color else 1.0 for i in range(len(atoms)) ] plotCell(ori_cell[0] * 0 + ori_cell[1] * 0, ori_cell[0] * 1 + ori_cell[1] * 0, ori_cell[0] * 1 + ori_cell[1] * 1, ori_cell[0] * 0 + ori_cell[1] * 1, linwt=5) for i in range(len(atoms)): anum = allnum[i] apos = allpos[i] plotAtom(anum, apos, scale=ascale,\ colorparam=allc[i], linwt=1.5-allc[i]/2) plotElliShine(anum, apos, atomscale=ascale, shift=0.45, scale=0.667) plt.axis('scaled') plt.xlim(min(ori_cell[0][0], ori_cell[1][0]), max(ori_cell[0][0], ori_cell[1][0])) plt.ylim(min(ori_cell[0][1], ori_cell[1][1]), max(ori_cell[0][1], ori_cell[1][1])) plt.tight_layout() plt.axis('off') plt.gca().xaxis.set_major_locator(plt.NullLocator()) plt.gca().yaxis.set_major_locator(plt.NullLocator()) plt.margins(0, 0) plt.title(title, fontsize='xx-large', fontweight='bold') if outName is not None: plt.savefig(outName+'-cpk.png', dpi=100, \ bbox_inches = "tight", transparent=True, pad_inches = 0) else: plt.show() plt.close()
def get_density(self, atom_indicees=None, gridrefinement=2): """Get sum of atomic densities from the given atom list. All atoms are taken if the list is not given.""" all_atoms = self.calculator.get_atoms() if atom_indicees is None: atom_indicees = range(len(all_atoms)) density = self.calculator.density spos_ac = all_atoms.get_scaled_positions() rank_a = self.finegd.get_ranks_from_positions(spos_ac) density.set_positions(all_atoms.get_scaled_positions(), rank_a ) # select atoms atoms = [] D_asp = {} rank_a = [] all_D_asp = self.calculator.density.D_asp all_rank_a = self.calculator.density.rank_a for a in atom_indicees: if a in all_D_asp: D_asp[len(atoms)] = all_D_asp.get(a) atoms.append(all_atoms[a]) rank_a.append(all_rank_a[a]) atoms = Atoms(atoms, cell=all_atoms.get_cell(), pbc=all_atoms.get_pbc()) spos_ac = atoms.get_scaled_positions() Z_a = atoms.get_atomic_numbers() par = self.calculator.input_parameters setups = Setups(Z_a, par.setups, par.basis, par.lmax, XC(par.xc), self.calculator.wfs.world) self.D_asp = D_asp # initialize self.initialize(setups, self.calculator.timer, np.zeros((len(atoms), 3)), False) self.set_mixer(None) # FIXME nparray causes partitionong.py test to fail self.set_positions(spos_ac, np.array(rank_a)) basis_functions = BasisFunctions(self.gd, [setup.phit_j for setup in self.setups], cut=True) basis_functions.set_positions(spos_ac) self.initialize_from_atomic_densities(basis_functions) aed_sg, gd = self.get_all_electron_density(atoms, gridrefinement) return aed_sg[0], gd
def get_density(self, atom_indices=None, gridrefinement=2): """Get sum of atomic densities from the given atom list. All atoms are taken if the list is not given.""" all_atoms = self.calculator.get_atoms() if atom_indices is None: atom_indices = range(len(all_atoms)) density = self.calculator.density spos_ac = all_atoms.get_scaled_positions() rank_a = self.finegd.get_ranks_from_positions(spos_ac) density.set_positions(all_atoms.get_scaled_positions(), AtomPartition(self.finegd.comm, rank_a)) # select atoms atoms = [] D_asp = {} rank_a = [] all_D_asp = self.calculator.density.D_asp all_rank_a = self.calculator.density.atom_partition.rank_a for a in atom_indices: if a in all_D_asp: D_asp[len(atoms)] = all_D_asp.get(a) atoms.append(all_atoms[a]) rank_a.append(all_rank_a[a]) atoms = Atoms(atoms, cell=all_atoms.get_cell(), pbc=all_atoms.get_pbc()) spos_ac = atoms.get_scaled_positions() Z_a = atoms.get_atomic_numbers() par = self.calculator.parameters setups = Setups(Z_a, par.setups, par.basis, XC(par.xc), self.calculator.wfs.world) # initialize self.initialize(setups, self.calculator.timer, np.zeros(len(atoms)), False) self.set_mixer(None) # FIXME nparray causes partitionong.py test to fail self.set_positions(spos_ac, AtomPartition(self.gd.comm, rank_a)) self.D_asp = D_asp basis_functions = BasisFunctions( self.gd, [setup.phit_j for setup in self.setups], cut=True) basis_functions.set_positions(spos_ac) self.initialize_from_atomic_densities(basis_functions) aed_sg, gd = self.get_all_electron_density(atoms, gridrefinement) return aed_sg.sum(axis=0), gd
def make_anti_bcc_struc(alat): """ Creates the crystal structure using ASE. :param alat: Lattice parameter in angstrom :return: structure object converted from ase """ fecell = Atoms('Fe2', [(0, 0, 0), (alat/2, alat/2, alat/2)], cell=[alat, alat, alat, 90, 90, 90], pbc=True) # check how your cell looks like #write('s.cif', gecell) print(fecell, fecell.get_atomic_numbers()) #fecell.set_atomic_numbers([26, 27]) structure = Struc(ase2struc(fecell)) print(structure.species) return structure
def _preprocess_atoms(self, atoms: Atoms) -> Dict[str, Optional[Tensor]]: pos = torch.tensor( atoms.get_positions(), device=self.device, dtype=self.dtype, requires_grad=True ) Z = torch.tensor(atoms.get_atomic_numbers(), device=self.device) if any(atoms.pbc): cell = torch.tensor( atoms.get_cell(), device=self.device, dtype=self.dtype, requires_grad=True ) else: cell = None pbc = torch.tensor(atoms.pbc, device=self.device) edge_index, S = self._calc_edge_index(pos, cell, pbc) input_dicts = dict(pos=pos, Z=Z, cell=cell, pbc=pbc, edge_index=edge_index, shift=S) return input_dicts
def get_density(self, atom_indicees=None): """Get sum of atomic densities from the given atom list. All atoms are taken if the list is not given.""" all_atoms = self.calculator.get_atoms() if atom_indicees is None: atom_indicees = range(len(all_atoms)) density = self.calculator.density density.set_positions(all_atoms.get_scaled_positions() % 1.0, self.calculator.wfs.rank_a) # select atoms atoms = [] D_asp = {} rank_a = [] all_D_asp = self.calculator.density.D_asp all_rank_a = self.calculator.density.rank_a for a in atom_indicees: if a in all_D_asp: D_asp[len(atoms)] = all_D_asp.get(a) atoms.append(all_atoms[a]) rank_a.append(all_rank_a[a]) atoms = Atoms(atoms, cell=all_atoms.get_cell()) spos_ac = atoms.get_scaled_positions() % 1.0 Z_a = atoms.get_atomic_numbers() par = self.calculator.input_parameters setups = Setups(Z_a, par.setups, par.basis, par.lmax, XC(par.xc), self.calculator.wfs.world) self.D_asp = D_asp # initialize self.initialize(setups, par.stencils[1], self.calculator.timer, [0] * len(atoms), False) self.set_mixer(None) self.set_positions(spos_ac, rank_a) basis_functions = BasisFunctions( self.gd, [setup.phit_j for setup in self.setups], cut=True) basis_functions.set_positions(spos_ac) self.initialize_from_atomic_densities(basis_functions) aed_sg, gd = Density.get_all_electron_density(self, atoms, gridrefinement=2) return aed_sg[0], gd
def read_ref_coords(system_name, filename="coords"): skip = True list_atoms = [] with open(filename) as fp: for line in fp: if skip: skip = False continue temp = line.strip().split() list_atoms.append( Atom(temp[0], (float(temp[1]), float(temp[2]), float(temp[3])))) system = Atoms(list_atoms) return system.get_atomic_numbers(), system.get_positions()
def _crd2frag(symbols, crds, pbc=False, cell=None, return_bonds=False): atomnumber = len(symbols) if pbc: all_atoms = Atoms(symbols=symbols, positions=crds, pbc=True, cell=cell) repeated_atoms = all_atoms.repeat(2)[atomnumber:] tree = cKDTree(crds) d = tree.query(repeated_atoms.get_positions(), k=1)[0] nearest = d < 5 ghost_atoms = repeated_atoms[nearest] realnumber = np.where(nearest)[0] % atomnumber all_atoms += ghost_atoms # Use openbabel to connect atoms mol = openbabel.OBMol() mol.BeginModify() for idx, (num, position) in enumerate( zip(all_atoms.get_atomic_numbers(), all_atoms.positions)): atom = mol.NewAtom(idx) atom.SetAtomicNum(int(num)) atom.SetVector(*position) mol.ConnectTheDots() mol.PerceiveBondOrders() mol.EndModify() bonds = [] for ii in range(mol.NumBonds()): bond = mol.GetBond(ii) a = bond.GetBeginAtom().GetId() b = bond.GetEndAtom().GetId() bo = bond.GetBO() if a >= atomnumber and b >= atomnumber: # duplicated continue elif a >= atomnumber: a = realnumber[a - atomnumber] elif b >= atomnumber: b = realnumber[b - atomnumber] bonds.extend([[a, b, bo], [b, a, bo]]) bonds = np.array(bonds, ndmin=2).reshape((-1, 3)) graph = csr_matrix((bonds[:, 2], (bonds[:, 0], bonds[:, 1])), shape=(atomnumber, atomnumber)) frag_numb, frag_index = connected_components(graph, 0) if return_bonds: return frag_numb, frag_index, graph return frag_numb, frag_index
def test(): """Simple test function to test atoms2json and json2atoms function""" from ase import Atoms teststructure = Atoms('N2', [(0, 0, 0), (0, 0, 1.1)]) teststring = atoms2json(teststructure, additional_information={"abc": "def" }) #convert to the string newstructure = json2atoms(teststring) #and convert back assert (newstructure.get_atomic_numbers() == teststructure.get_atomic_numbers()).all() assert ( newstructure.get_positions() == teststructure.get_positions()).all() assert (newstructure.get_cell() == teststructure.get_cell()).all() assert (newstructure.get_pbc() == teststructure.get_pbc()).all() assert newstructure.info["key_value_pairs"]["abc"] == "def" print("JSON Conversion in both directions: apparently no issue")
def make_struc(alat, atom, clat): """ Creates the crystal structure using ASE. :param alat: Lattice parameter in angstrom :return: structure object converted from ase """ if atom == 'Cu' or atom == 'Au': fcccell = bulk(atom, 'fcc', a=alat) write('fcc.cif', fcccell) print(fcccell, fcccell.get_atomic_numbers()) structure = Struc(ase2struc(fcccell)) elif atom == 'CuAu': lattice = alat * numpy.identity(3) lattice[2][2] = clat symbols = ['Cu', 'Au'] sc_pos = [[0, 0, 0], [0.5, 0.5, 0.5]] bctcell = Atoms(symbols=symbols, scaled_positions=sc_pos, cell=lattice) write('bct.cif', bctcell) print(bctcell, bctcell.get_atomic_numbers()) structure = Struc(ase2struc(bctcell)) # check how your cell looks like print(structure.species) return structure
def test_features(self): """Tests that the correct features are present in the desciptor. """ desc = SineMatrix(n_atoms_max=2, permutation="none", flatten=False) # Test that without cell the matrix cannot be calculated system = Atoms( positions=[[0, 0, 0], [1.0, 1.0, 1.0]], symbols=["H", "H"], ) with self.assertRaises(ValueError): desc.create(system) # Test that periodic boundaries are considered by seeing that an atom # in the origin is replicated to the corners system = Atoms( cell=[ [10, 10, 0], [0, 10, 0], [0, 0, 10], ], scaled_positions=[[0, 0, 0], [1.0, 1.0, 1.0]], symbols=["H", "H"], pbc=True, ) # from ase.visualize import view # view(system) matrix = desc.create(system) # The interaction between atoms 1 and 2 should be infinite due to # periodic boundaries. self.assertEqual(matrix[0, 1], float("Inf")) # The interaction of an atom with itself is always 0.5*Z**2.4 atomic_numbers = system.get_atomic_numbers() for i, i_diag in enumerate(np.diag(matrix)): self.assertEqual(i_diag, 0.5 * atomic_numbers[i]**2.4)
def __init__(self, atoms: Atoms, num_configs, sigmas: Mapping[Union[str, int], float], seed=None): """ Generates atomic configurations for thermal diffuse scattering. Randomly displaces the atomic positions of an ASE Atoms object to emulate thermal vibrations. Parameters ---------- atoms : ase.Atoms ASE atoms object with the average atomic configuration. sigmas : Mapping[Union[str, int], float] Mapping from atomic species to the variance of the displacements of that atomic species. The atomic species can be specified as atomic number or symbol. """ new_sigmas = {} for key, sigma in sigmas.items(): try: new_sigmas[atomic_numbers[key]] = sigma except KeyError: pass unique_atomic_numbers = np.unique(atoms.get_atomic_numbers()) if len( set(unique_atomic_numbers).intersection(set( new_sigmas.keys()))) != len(unique_atomic_numbers): raise RuntimeError( 'Mapping sigma not provided for all atomic species') self._sigmas = new_sigmas self._atoms = atoms self._num_configs = num_configs self._seed = seed
def WriteSlabFilm(output, atoms, precision=0.06, smear=0.6, slab_identifers=(78, 79), excluded_from_density=(8, 1), formula_prefix=("Pt", "Au"), formula_suffix=("O", "H")): ''' Write the slab and film layer/type information to the output file, returns an array of slab layer tags. After the script runs, the atoms will be tagged to appropriate layers. ''' #First generate an array of the density of atoms on each step of the Z axis. #This step is influenced by precision and smear. density = [] for step in DRange(0, atoms.get_cell()[2][2], precision): #Keep track of how many atoms are in this density step dcount = 0 for atom in atoms: #Exclude some atoms from the density count. Hydrogen and oxygen are ignored #by default as they typically arn't represented in layers very well. if not (atom.number in excluded_from_density): if ((atom.position[2] - smear) < step) and ( (atom.position[2] + smear) > step): dcount += 1 density.append(dcount) #At this point density is populated with the density of atoms on the Z axis #Next is identifying the layer ranges. startx = -1 endx = -1 density_max = -1 #Ranges are defined layer_ranges = [] #Iterate over all the density values for d_index in xrange(0, len(density), 1): #Move the value to a local variable value = density[d_index] #if there is no know endx if endx == -1: #check if the value is higher than 1/6th the last known highest density for this section. if value > (density_max / 6): #set all the relevant values startx = d_index endx = d_index density_max = value #if you have found a starting index and the value has increased, then restart the range elif value > density_max: startx = d_index endx = d_index density_max = value #if the value is the same, extend the range elif value == density_max: endx = d_index #if the value ever drops under 1/6th of the highest known density for this section, then the #layer can be defined with known values and the startx/endx can be reset. Don't reset the density #max as there is no good reason to. All layers should have densities that are at least that similar #or the definition of a layer is hard to define. Also might cause errors as moving down away from #the density peak. elif value <= density_max / 6: layer_ranges.append((startx - (smear / precision), endx - startx, endx + (smear / precision))) startx = -1 endx = -1 #Find which atoms belong in which layers and tag them. for index, layer_range in enumerate(layer_ranges): atoms_in_layer = [ atom for atom in atoms if (atom.position[2] / precision > layer_range[0]) and ( atom.position[2] / precision < layer_range[2]) ] for atom in atoms_in_layer: atom.tag = index + 1 #Define a dictionary of atom tag keys and if they are part of the slab slab_layers = {} #Try to indentify which layers belong in the slab for atom in atoms: if atom.tag == 0: continue if atom.number in slab_identifers: slab_layers[atom.tag] = True else: #This part is designed to not overwrite a value if it already exists. You never want #to overwrite a True value. try: slab_layers[atom.tag] = (slab_layers[atom.tag]) except: slab_layers[atom.tag] = False for atom in atoms: #if atom is not assigned if atom.tag == 0: new_layer = 0 distance = 1000 #loop over all layer ranges for index, layer_range in enumerate(layer_ranges): #the atoms excluded from the density are not part of the slab if (atom.number in excluded_from_density) and (slab_layers[index + 1]): continue start = abs((atom.position[2] / smear * precision) - layer_range[0]) end = abs((atom.position[2] / smear * precision) - layer_range[2]) if (distance >= start): new_layer = index distance = start if (distance > end): new_layer = index distance = start atom.tag = new_layer + 1 slab_layer_count = 0 film_layer_count = 0 for slab_layer in slab_layers: if slab_layers[slab_layer]: slab_layer_count += 1 else: film_layer_count += 1 output['SlabLayers'] = slab_layer_count output['FilmLayers'] = film_layer_count if film_layer_count == 0: return Reports.NoFilmLayers, None if slab_layer_count == 0: return Reports.NoSlabLayers, None slab = Atoms([atom for atom in atoms if (slab_layers[atom.tag])]) slab.set_cell(atoms.get_cell()) film = Atoms([atom for atom in atoms if not (slab_layers[atom.tag])]) film.set_cell(atoms.get_cell()) slabform = slab.get_chemical_formula() filmform = film.get_chemical_formula() #Obtain the layers of the slab as individual layers for additional slab analysis layers = [] known_layers = set() for atom in slab: known_layers.add(atom.tag) for layer in known_layers: this_layer = [] for atom in atoms: if atom.tag == layer: this_layer.append(atom) layers.append(Atoms(this_layer)) layers.sort(key=lambda layer: layer[0].position[2]) layer_formulas = [] for layer in layers: layer_formulas.append(layer.get_chemical_formula()) slab_numbers = slab.get_atomic_numbers() slabtype = "" #H**o, Hetero, Skin skintype = "" #Only defined if skinned | Plate, Anneal skincomp = "" #Only defined if skinned annealcomp = "" #Reduced composition of annealed layer slabcomp = layer_formulas[0] if (CountList(slab_numbers, slab_numbers[0]) == len(slab_numbers)): slabtype = "H**o" elif CountList(layer_formulas, layer_formulas[0]) == len(layer_formulas): slabtype = "Hetero" elif ((CountList(layer_formulas, layer_formulas[0]) == len(layer_formulas) - 1) or (CountList(layer_formulas, layer_formulas[1]) == len(layer_formulas) - 1)): #Plated Skin slabtype = "Skin" skintype = "Plate" skincomp = layer_formulas[len(layer_formulas) - 1] elif ((CountList(layer_formulas, layer_formulas[0]) == len(layer_formulas) - 2) or (CountList( layer_formulas, layer_formulas[1]) == len(layer_formulas) - 2) or (CountList(layer_formulas, layer_formulas[2]) == len(layer_formulas) - 2)): #Annealed Skin slabtype = "Skin" skintype = "Anneal" skincomp = layer_formulas[len(layer_formulas) - 1] annealcomp = layer_formulas[len(layer_formulas) - 2] else: #Failed return Reports.SlabTypeIDFail, None output['SystemFormula'] = FormatFormula(atoms.get_chemical_formula(), formula_prefix, formula_suffix, red=False) output['SlabFormula'] = FormatFormula(slabform, formula_prefix, formula_suffix, red=False) output['SlabType'] = slabtype output['SkinType'] = skintype output['SkinComp'] = FormatFormula(skincomp, formula_prefix, formula_suffix) output['AnnealComp'] = FormatFormula(annealcomp, formula_prefix, formula_suffix) output['SlabComp'] = FormatFormula(slabcomp, formula_prefix, formula_suffix) output['FilmFormula'] = FormatFormula(filmform, formula_prefix, formula_suffix, red=False) output['FilmComp'] = FormatFormula(filmform, formula_prefix, formula_suffix) return False, slab_layers
def WriteSlabFilm(output, atoms, precision = 0.06, smear = 0.6, slab_identifers = (78, 79), excluded_from_density = (8, 1), formula_prefix = ("Pt","Au"), formula_suffix = ("O", "H")): #First generate an array of the density of atoms on each step of the Z axis. #This step is influenced by precision and smear. density = [] for step in DRange(0,atoms.get_cell()[2][2],precision): #Keep track of how many atoms are in this density step dcount=0 for atom in atoms: #Exclude some atoms from the density count. Hydrogen and oxygen are ignored #by default as they typically arn't represented in layers very well. if not (atom.number in excluded_from_density): if ((atom.position[2]-smear) < step) and ((atom.position[2]+smear) > step): dcount+=1 density.append(dcount) #At this point density is populated with the density of atoms on the Z axis #Next is identifying the layer ranges. startx = -1 endx = -1 density_max = -1 #Ranges are defined layer_ranges = [] #Iterate over all the density values for d_index in xrange(0,len(density),1): #Move the value to a local variable value = density[d_index] #if there is no know endx if endx == -1: #check if the value is higher than 1/6th the last known highest density for this section. if value > (density_max/6): #set all the relevant values startx = d_index endx = d_index density_max = value #if you have found a starting index and the value has increased, then restart the range elif value > density_max: startx = d_index endx = d_index density_max = value #if the value is the same, extend the range elif value == density_max: endx = d_index #if the value ever drops under 1/6th of the highest known density for this section, then the #layer can be defined with known values and the startx/endx can be reset. Don't reset the density #max as there is no good reason to. All layers should have densities that are at least that similar #or the definition of a layer is hard to define. Also might cause errors as moving down away from #the density peak. elif value <= density_max/6: layer_ranges.append((startx-(smear/precision),endx-startx,endx+(smear/precision))) startx = -1 endx = -1 #Find which atoms belong in which layers and tag them. for index, layer_range in enumerate(layer_ranges): atoms_in_layer = [atom for atom in atoms if (atom.position[2]/precision > layer_range[0]) and (atom.position[2]/precision < layer_range[2])] for atom in atoms_in_layer: atom.tag=index+1 #Define a dictionary of atom tag keys and if they are part of the slab slab_layers = {} #Try to indentify which layers belong in the slab for atom in atoms: if atom.tag == 0: continue if atom.number in slab_identifers: slab_layers[atom.tag] = True else: #This part is designed to not overwrite a value if it already exists. You never want #to overwrite a True value. try: slab_layers[atom.tag]=(slab_layers[atom.tag]) except: slab_layers[atom.tag]=False for atom in atoms: #if atom is not assigned if atom.tag == 0: new_layer = 0 distance = 1000 #loop over all layer ranges for index, layer_range in enumerate(layer_ranges): #the atoms excluded from the density are not part of the slab if (atom.number in excluded_from_density) and (slab_layers[index+1]): continue start = abs((atom.position[2]/smear*precision)-layer_range[0]) end = abs((atom.position[2]/smear*precision)-layer_range[2]) if (distance>=start): new_layer = index distance = start if (distance>end): new_layer = index distance = start atom.tag=new_layer+1 slab_layer_count=0 film_layer_count=0 for slab_layer in slab_layers: if slab_layers[slab_layer]: slab_layer_count+=1 else: film_layer_count+=1 output[K._VSF_SlabLayers_]=slab_layer_count output[K._VSF_FilmLayers_]=film_layer_count if film_layer_count == 0: return E._NoFilmLayers_, None if slab_layer_count == 0: return E._NoSlabLayers_, None slab = Atoms([atom for atom in atoms if (slab_layers[atom.tag])]) slab.set_cell(atoms.get_cell()) film = Atoms([atom for atom in atoms if not (slab_layers[atom.tag])]) film.set_cell(atoms.get_cell()) slabform = slab.get_chemical_formula() filmform = film.get_chemical_formula() #Obtain the layers of the slab as individual layers for additional slab analysis layers=[] known_layers=set() for atom in slab: known_layers.add(atom.tag) for layer in known_layers: this_layer=[] for atom in atoms: if atom.tag == layer: this_layer.append(atom) layers.append(Atoms(this_layer)) layers.sort(key=lambda layer: layer[0].position[2]) layer_formulas = [] for layer in layers: layer_formulas.append(layer.get_chemical_formula()) slab_numbers = slab.get_atomic_numbers() slabtype = "" #H**o, Hetero, Skin skintype = "" #Only defined if skinned | Plate, Anneal skincomp = "" #Only defined if skinned annealcomp = "" #Reduced composition of annealed layer slabcomp = layer_formulas[0] if (Count(slab_numbers, slab_numbers[0])==len(slab_numbers)): slabtype = "H**o" elif Count(layer_formulas, layer_formulas[0])==len(layer_formulas): slabtype = "Hetero" elif ((Count(layer_formulas, layer_formulas[0])==len(layer_formulas)-1) or (Count(layer_formulas, layer_formulas[1])==len(layer_formulas)-1)): #Plated Skin slabtype = "Skin" skintype = "Plate" skincomp = layer_formulas[len(layer_formulas)-1] elif ((Count(layer_formulas, layer_formulas[0])==len(layer_formulas)-2) or (Count(layer_formulas, layer_formulas[1])==len(layer_formulas)-2) or (Count(layer_formulas, layer_formulas[2])==len(layer_formulas)-2)): #Annealed Skin slabtype = "Skin" skintype = "Anneal" skincomp = layer_formulas[len(layer_formulas)-1] annealcomp = layer_formulas[len(layer_formulas)-2] else: #Failed return E._SlabTypeIDFail_, None output[K._SystemFormula_] = FormatForm(atoms.get_chemical_formula(), formula_prefix, formula_suffix, red = False) output[K._VSF_SlabFormula_] = FormatForm(slabform, formula_prefix, formula_suffix, red = False) output[K._VSF_SlabType_] = slabtype output[K._VSF_SkinType_] = skintype output[K._VSF_SkinComp_] = FormatForm(skincomp, formula_prefix, formula_suffix) output[K._VSF_AnnealComp_] = FormatForm(annealcomp, formula_prefix, formula_suffix) output[K._VSF_SlabComp_] = FormatForm(slabcomp, formula_prefix, formula_suffix) output[K._VSF_FilmFormula_] = FormatForm(filmform, formula_prefix, formula_suffix, red = False) output[K._VSF_FilmComp_] = FormatForm(filmform, formula_prefix, formula_suffix) return False, slab_layers
def atoms_to_graph_const_cutoff( atoms: ase.Atoms, cutoff, atom_to_node_fn, self_interaction=False, cutoff_covalent=False, ): atoms.wrap() atom_numbers = atoms.get_atomic_numbers() if cutoff_covalent: radii = ase.data.covalent_radii[atom_numbers] * cutoff else: radii = [cutoff] * len(atoms) neighborhood = NeighborList(radii, skin=0.0, self_interaction=self_interaction, bothways=True) neighborhood.update(atoms) nodes = [] connections = [] connections_offset = [] edges = [] if np.any(atoms.get_pbc()): atom_positions = atoms.get_positions(wrap=True) else: atom_positions = atoms.get_positions(wrap=False) unitcell = atoms.get_cell() for ii in range(len(atoms)): nodes.append(atom_to_node_fn(atom_numbers[ii])) for ii in range(len(atoms)): neighbor_indices, offset = neighborhood.get_neighbors(ii) for jj, offs in zip(neighbor_indices, offset): ii_pos = atom_positions[ii] jj_pos = atom_positions[jj] + np.dot(offs, unitcell) dist_vec = ii_pos - jj_pos dist = np.sqrt(np.dot(dist_vec, dist_vec)) connections.append([jj, ii]) connections_offset.append(np.vstack((offs, np.zeros(3, float)))) edges.append([dist]) if len(edges) == 0: warnings.warn("Generated graph has zero edges") edges = np.zeros((0, 1)) connections = np.zeros((0, 2)) connections_offset = np.zeros((0, 2, 3)) else: connections_offset = np.stack(connections_offset, axis=0) return ( np.array(nodes), atom_positions, np.array(edges), np.array(connections), connections_offset, unitcell, )
def atoms_to_graph_knearest(atoms: ase.Atoms, num_neighbors, atom_to_node_fn, initial_radius=3.0): atoms.wrap() atom_numbers = atoms.get_atomic_numbers() unitcell = atoms.get_cell() for multiplier in range(1, 11): if multiplier == 10: raise RuntimeError("Reached maximum radius") radii = [initial_radius * multiplier] * len(atoms) neighborhood = NeighborList(radii, skin=0.0, self_interaction=False, bothways=True) neighborhood.update(atoms) nodes = [] connections = [] connections_offset = [] edges = [] if np.any(atoms.get_pbc()): atom_positions = atoms.get_positions(wrap=True) else: atom_positions = atoms.get_positions(wrap=False) keep_connections = [] keep_connections_offset = [] keep_edges = [] for ii in range(len(atoms)): nodes.append(atom_to_node_fn(atom_numbers[ii])) early_exit = False for ii in range(len(atoms)): this_edges = [] this_connections = [] this_connections_offset = [] neighbor_indices, offset = neighborhood.get_neighbors(ii) if len(neighbor_indices) < num_neighbors: # Not enough neigbors, so exit and increase radius early_exit = True break for jj, offs in zip(neighbor_indices, offset): ii_pos = atom_positions[ii] jj_pos = atom_positions[jj] + np.dot(offs, unitcell) dist_vec = ii_pos - jj_pos dist = np.sqrt(np.dot(dist_vec, dist_vec)) this_connections.append([jj, ii]) # from, to this_connections_offset.append( np.vstack((offs, np.zeros(3, float)))) this_edges.append([dist]) edges.append(np.array(this_edges)) connections.append(np.array(this_connections)) connections_offset.append( np.stack(this_connections_offset, axis=0)) if early_exit: continue else: for e, c, o in zip(edges, connections, connections_offset): # Keep only num_neighbors closest indices keep_ind = np.argsort(e[:, 0])[0:num_neighbors] keep_edges.append(e[keep_ind]) keep_connections.append(c[keep_ind]) keep_connections_offset.append(o[keep_ind]) break return ( np.array(nodes), atom_positions, np.concatenate(keep_edges), np.concatenate(keep_connections), np.concatenate(keep_connections_offset), unitcell, )
class Conformer(): """ A class for generating and editing 3D conformers of molecules """ def __init__(self, smiles=None, rmg_molecule=None, index=0): self.energy = None self.index = index if (smiles or rmg_molecule): if smiles and rmg_molecule: assert rmg_molecule.isIsomorphic(RMGMolecule( SMILES=smiles)), "SMILES string did not match RMG Molecule object" self.smiles = smiles self.rmg_molecule = rmg_molecule elif rmg_molecule: self.rmg_molecule = rmg_molecule self.smiles = rmg_molecule.toSMILES() else: self.smiles = smiles self.rmg_molecule = RMGMolecule(SMILES=smiles) self.rmg_molecule.updateMultiplicity() self.get_molecules() self.get_geometries() self._symmetry_number = None else: self.smiles = None self.rmg_molecule = None self.rdkit_molecule = None self.ase_molecule = None self.bonds = [] self.angles = [] self.torsions = [] self.cistrans = [] self.chiral_centers = [] self._symmetry_number = None def __repr__(self): return '<Conformer "{}">'.format(self.smiles) def copy(self): copy_conf = Conformer() copy_conf.smiles = self.smiles copy_conf.rmg_molecule = self.rmg_molecule.copy() copy_conf.rdkit_molecule = self.rdkit_molecule.__copy__() copy_conf.ase_molecule = self.ase_molecule.copy() copy_conf.get_geometries() copy_conf.energy = self.energy return copy_conf @property def symmetry_number(self): if not self._symmetry_number: self._symmetry_number = self.calculate_symmetry_number() return self._symmetry_number def get_rdkit_mol(self): """ A method for creating an rdkit geometry from an rmg mol """ assert self.rmg_molecule, "Cannot create an RDKit geometry without an RMG molecule object" RDMol = self.rmg_molecule.toRDKitMol(removeHs=False) rdkit.Chem.AllChem.EmbedMolecule(RDMol) self.rdkit_molecule = RDMol mol_list = AllChem.MolToMolBlock(self.rdkit_molecule).split('\n') for i, atom in enumerate(self.rmg_molecule.atoms): j = i + 4 coords = mol_list[j].split()[:3] for k, coord in enumerate(coords): coords[k] = float(coord) atom.coords = np.array(coords) return self.rdkit_molecule def get_ase_mol(self): """ A method for creating an ase atoms object from an rdkit mol """ if not self.rdkit_molecule: self.get_rdkit_mol() mol_list = AllChem.MolToMolBlock(self.rdkit_molecule).split('\n') ase_atoms = [] for i, line in enumerate(mol_list): if i > 3: try: atom0, atom1, bond, rest = line atom0 = int(atom0) atom0 = int(atom1) bond = float(bond) except ValueError: try: x, y, z, symbol = line.split()[0:4] x = float(x) y = float(y) z = float(z) ase_atoms.append( Atom(symbol=symbol, position=(x, y, z))) except BaseException: continue self.ase_molecule = Atoms(ase_atoms) return self.ase_molecule def get_molecules(self): if not self.rmg_molecule: self.rmg_molecule = RMGMolecule(SMILES=self.smiles) self.rdkit_molecule = self.get_rdkit_mol() self.ase_molecule = self.get_ase_mol() self.get_geometries() return self.rdkit_molecule, self.ase_molecule def view(self): """ A method designed to create a 3D figure of the AutoTST_Molecule with py3Dmol from the rdkit_molecule """ mb = Chem.MolToMolBlock(self.rdkit_molecule) p = py3Dmol.view(width=600, height=600) p.addModel(mb, "sdf") p.setStyle({'stick': {}}) p.setBackgroundColor('0xeeeeee') p.zoomTo() return p.show() def get_bonds(self): """ A method for identifying all of the bonds in a conformer """ bond_list = [] for bond in self.rdkit_molecule.GetBonds(): bond_list.append((bond.GetBeginAtomIdx(), bond.GetEndAtomIdx())) bonds = [] for index, indices in enumerate(bond_list): i, j = indices length = self.ase_molecule.get_distance(i, j) center = False if ((self.rmg_molecule.atoms[i].label) and ( self.rmg_molecule.atoms[j].label)): center = True bond = Bond(index=index, atom_indices=indices, length=length, reaction_center=center) mask = self.get_mask(bond) bond.mask = mask bonds.append(bond) self.bonds = bonds return self.bonds def get_angles(self): """ A method for identifying all of the angles in a conformer """ angle_list = [] for atom1 in self.rdkit_molecule.GetAtoms(): for atom2 in atom1.GetNeighbors(): for atom3 in atom2.GetNeighbors(): if atom1.GetIdx() == atom3.GetIdx(): continue to_add = (atom1.GetIdx(), atom2.GetIdx(), atom3.GetIdx()) if (to_add in angle_list) or ( tuple(reversed(to_add)) in angle_list): continue angle_list.append(to_add) angles = [] for index, indices in enumerate(angle_list): i, j, k = indices degree = self.ase_molecule.get_angle(i, j, k) ang = Angle(index=index, atom_indices=indices, degree=degree, mask=[]) mask = self.get_mask(ang) reaction_center = False angles.append(Angle(index=index, atom_indices=indices, degree=degree, mask=mask, reaction_center=reaction_center)) self.angles = angles return self.angles def get_torsions(self): """ A method for identifying all of the torsions in a conformer """ torsion_list = [] for bond1 in self.rdkit_molecule.GetBonds(): atom1 = bond1.GetBeginAtom() atom2 = bond1.GetEndAtom() if atom1.IsInRing() or atom2.IsInRing(): # Making sure that bond1 we're looking at are not in a ring continue bond_list1 = list(atom1.GetBonds()) bond_list2 = list(atom2.GetBonds()) if not len(bond_list1) > 1 and not len(bond_list2) > 1: # Making sure that there are more than one bond attached to # the atoms we're looking at continue # Getting the 0th and 3rd atom and insuring that atoms # attached to the 1st and 2nd atom are not terminal hydrogens # We also make sure that all of the atoms are properly bound # together # If the above are satisfied, we append a tuple of the torsion our # torsion_list got_atom0 = False got_atom3 = False for bond0 in bond_list1: atomX = bond0.GetOtherAtom(atom1) # if atomX.GetAtomicNum() == 1 and len(atomX.GetBonds()) == 1: # This means that we have a terminal hydrogen, skip this # NOTE: for H_abstraction TSs, a non teminal H should exist # continue if atomX.GetIdx() != atom2.GetIdx(): got_atom0 = True atom0 = atomX for bond2 in bond_list2: atomY = bond2.GetOtherAtom(atom2) # if atomY.GetAtomicNum() == 1 and len(atomY.GetBonds()) == 1: # This means that we have a terminal hydrogen, skip this # continue if atomY.GetIdx() != atom1.GetIdx(): got_atom3 = True atom3 = atomY if not (got_atom0 and got_atom3): # Making sure atom0 and atom3 were not found continue # Looking to make sure that all of the atoms are properly bonded to # eached if ( "SINGLE" in str( self.rdkit_molecule.GetBondBetweenAtoms( atom1.GetIdx(), atom2.GetIdx()).GetBondType()) and self.rdkit_molecule.GetBondBetweenAtoms( atom0.GetIdx(), atom1.GetIdx()) and self.rdkit_molecule.GetBondBetweenAtoms( atom1.GetIdx(), atom2.GetIdx()) and self.rdkit_molecule.GetBondBetweenAtoms( atom2.GetIdx(), atom3.GetIdx())): torsion_tup = (atom0.GetIdx(), atom1.GetIdx(), atom2.GetIdx(), atom3.GetIdx()) already_in_list = False for torsion_entry in torsion_list: a, b, c, d = torsion_entry e, f, g, h = torsion_tup if (b, c) == (f, g) or (b, c) == (g, f): already_in_list = True if not already_in_list: torsion_list.append(torsion_tup) torsions = [] for index, indices in enumerate(torsion_list): i, j, k, l = indices dihedral = self.ase_molecule.get_dihedral(i, j, k, l) tor = Torsion(index=index, atom_indices=indices, dihedral=dihedral, mask=[]) mask = self.get_mask(tor) reaction_center = False torsions.append(Torsion(index=index, atom_indices=indices, dihedral=dihedral, mask=mask, reaction_center=reaction_center)) self.torsions = torsions return self.torsions def get_cistrans(self): """ A method for identifying all possible cistrans bonds in a molecule """ torsion_list = [] cistrans_list = [] for bond1 in self.rdkit_molecule.GetBonds(): atom1 = bond1.GetBeginAtom() atom2 = bond1.GetEndAtom() if atom1.IsInRing() or atom2.IsInRing(): # Making sure that bond1 we're looking at are not in a ring continue bond_list1 = list(atom1.GetBonds()) bond_list2 = list(atom2.GetBonds()) if not len(bond_list1) > 1 and not len(bond_list2) > 1: # Making sure that there are more than one bond attached to # the atoms we're looking at continue # Getting the 0th and 3rd atom and insuring that atoms # attached to the 1st and 2nd atom are not terminal hydrogens # We also make sure that all of the atoms are properly bound # together # If the above are satisfied, we append a tuple of the torsion our # torsion_list got_atom0 = False got_atom3 = False for bond0 in bond_list1: atomX = bond0.GetOtherAtom(atom1) # if atomX.GetAtomicNum() == 1 and len(atomX.GetBonds()) == 1: # This means that we have a terminal hydrogen, skip this # NOTE: for H_abstraction TSs, a non teminal H should exist # continue if atomX.GetIdx() != atom2.GetIdx(): got_atom0 = True atom0 = atomX for bond2 in bond_list2: atomY = bond2.GetOtherAtom(atom2) # if atomY.GetAtomicNum() == 1 and len(atomY.GetBonds()) == 1: # This means that we have a terminal hydrogen, skip this # continue if atomY.GetIdx() != atom1.GetIdx(): got_atom3 = True atom3 = atomY if not (got_atom0 and got_atom3): # Making sure atom0 and atom3 were not found continue # Looking to make sure that all of the atoms are properly bonded to # eached if ( "DOUBLE" in str( self.rdkit_molecule.GetBondBetweenAtoms( atom1.GetIdx(), atom2.GetIdx()).GetBondType()) and self.rdkit_molecule.GetBondBetweenAtoms( atom0.GetIdx(), atom1.GetIdx()) and self.rdkit_molecule.GetBondBetweenAtoms( atom1.GetIdx(), atom2.GetIdx()) and self.rdkit_molecule.GetBondBetweenAtoms( atom2.GetIdx(), atom3.GetIdx())): torsion_tup = (atom0.GetIdx(), atom1.GetIdx(), atom2.GetIdx(), atom3.GetIdx()) already_in_list = False for torsion_entry in torsion_list: a, b, c, d = torsion_entry e, f, g, h = torsion_tup if (b, c) == (f, g) or (b, c) == (g, f): already_in_list = True if not already_in_list: cistrans_list.append(torsion_tup) cistrans = [] for ct_index, indices in enumerate(cistrans_list): i, j, k, l = indices b0 = self.rdkit_molecule.GetBondBetweenAtoms(i, j) b1 = self.rdkit_molecule.GetBondBetweenAtoms(j, k) b2 = self.rdkit_molecule.GetBondBetweenAtoms(k, l) b0.SetBondDir(Chem.BondDir.ENDUPRIGHT) b2.SetBondDir(Chem.BondDir.ENDDOWNRIGHT) Chem.AssignStereochemistry(self.rdkit_molecule, force=True) if "STEREOZ" in str(b1.GetStereo()): if round(self.ase_molecule.get_dihedral(i, j, k, l), -1) == 0: atom = self.rdkit_molecule.GetAtomWithIdx(k) bonds = atom.GetBonds() for bond in bonds: indexes = [ bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()] if not ((sorted([j, k]) == sorted(indexes)) or ( sorted([k, l]) == sorted(indexes))): break for index in indexes: if not (index in indices): l = index break indices = [i, j, k, l] stero = "Z" else: if round( self.ase_molecule.get_dihedral( i, j, k, l), -1) == 180: atom = self.rdkit_molecule.GetAtomWithIdx(k) bonds = atom.GetBonds() for bond in bonds: indexes = [ bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()] if not ((sorted([j, k]) == sorted(indexes)) or ( sorted([k, l]) == sorted(indexes))): break for index in indexes: if not (index in indices): l = index break indices = [i, j, k, l] stero = "E" dihedral = self.ase_molecule.get_dihedral(i, j, k, l) tor = CisTrans(index=ct_index, atom_indices=indices, dihedral=dihedral, mask=[], stero=stero) mask = self.get_mask(tor) reaction_center = False cistrans.append(CisTrans(index=ct_index, atom_indices=indices, dihedral=dihedral, mask=mask, stero=stero ) ) self.cistrans = cistrans return self.cistrans def get_mask(self, geometry): """ Getting the right hand mask for a geometry object: - self: an AutoTST Conformer object - geometry: a Bond, Angle, Dihedral, or Torsion object """ rdkit_atoms = self.rdkit_molecule.GetAtoms() if (isinstance(geometry, autotst.geometry.Torsion) or isinstance(geometry, autotst.geometry.CisTrans)): L1, L0, R0, R1 = geometry.atom_indices # trying to get the left hand side of this torsion LHS_atoms_index = [L0, L1] RHS_atoms_index = [R0, R1] elif isinstance(geometry, autotst.geometry.Angle): a1, a2, a3 = geometry.atom_indices LHS_atoms_index = [a2, a1] RHS_atoms_index = [a2, a3] elif isinstance(geometry, autotst.geometry.Bond): a1, a2 = geometry.atom_indices LHS_atoms_index = [a1] RHS_atoms_index = [a2] complete_RHS = False i = 0 atom_index = RHS_atoms_index[0] while complete_RHS is False: try: RHS_atom = rdkit_atoms[atom_index] for neighbor in RHS_atom.GetNeighbors(): if (neighbor.GetIdx() in RHS_atoms_index) or ( neighbor.GetIdx() in LHS_atoms_index): continue else: RHS_atoms_index.append(neighbor.GetIdx()) i += 1 atom_index = RHS_atoms_index[i] except IndexError: complete_RHS = True mask = [index in RHS_atoms_index for index in range( len(self.ase_molecule))] return mask def get_chiral_centers(self): """ A method to identify """ centers = rdkit.Chem.FindMolChiralCenters( self.rdkit_molecule, includeUnassigned=True) chiral_centers = [] for index, center in enumerate(centers): atom_index, chirality = center chiral_centers.append( ChiralCenter( index=index, atom_index=atom_index, chirality=chirality)) self.chiral_centers = chiral_centers return self.chiral_centers def get_geometries(self): """ A helper method to obtain all geometry things """ self.bonds = self.get_bonds() self.angles = self.get_angles() self.torsions = self.get_torsions() self.cistrans = self.get_cistrans() self.chiral_centers = self.get_chiral_centers() return ( self.bonds, self.angles, self.torsions, self.cistrans, self.chiral_centers) def update_coords(self): """ A function that creates distance matricies for the RMG, ASE, and RDKit molecules and finds which (if any) are different. If one is different, this will update the coordinates of the other two with the different one. If all three are different, nothing will happen. If all are the same, nothing will happen. """ rdkit_dm = rdkit.Chem.rdmolops.Get3DDistanceMatrix(self.rdkit_molecule) ase_dm = self.ase_molecule.get_all_distances() l = len(self.rmg_molecule.atoms) rmg_dm = np.zeros((l, l)) for i, atom_i in enumerate(self.rmg_molecule.atoms): for j, atom_j in enumerate(self.rmg_molecule.atoms): rmg_dm[i][j] = np.linalg.norm(atom_i.coords - atom_j.coords) d1 = round(abs(rdkit_dm - ase_dm).max(), 3) d2 = round(abs(rdkit_dm - rmg_dm).max(), 3) d3 = round(abs(ase_dm - rmg_dm).max(), 3) if np.all(np.array([d1, d2, d3]) > 0): return False, None if np.any(np.array([d1, d2, d3]) > 0): if d1 == 0: diff = "rmg" self.update_coords_from("rmg") elif d2 == 0: diff = "ase" self.update_coords_from("ase") else: diff = "rdkit" self.update_coords_from("rdkit") return True, diff else: return True, None def update_coords_from(self, mol_type="ase"): """ A method to update the coordinates of the RMG, RDKit, and ASE objects with a chosen object. """ possible_mol_types = ["ase", "rmg", "rdkit"] assert (mol_type.lower() in possible_mol_types), "Please specifiy a valid mol type. Valid types are {}".format( possible_mol_types) if mol_type.lower() == "rmg": conf = self.rdkit_molecule.GetConformers()[0] ase_atoms = [] for i, atom in enumerate(self.rmg_molecule.atoms): x, y, z = atom.coords symbol = atom.symbol conf.SetAtomPosition(i, [x, y, z]) ase_atoms.append(Atom(symbol=symbol, position=(x, y, z))) self.ase_molecule = Atoms(ase_atoms) # self.calculate_symmetry_number() elif mol_type.lower() == "ase": conf = self.rdkit_molecule.GetConformers()[0] for i, position in enumerate(self.ase_molecule.get_positions()): self.rmg_molecule.atoms[i].coords = position conf.SetAtomPosition(i, position) # self.calculate_symmetry_number() elif mol_type.lower() == "rdkit": mol_list = AllChem.MolToMolBlock(self.rdkit_molecule).split('\n') for i, atom in enumerate(self.rmg_molecule.atoms): j = i + 4 coords = mol_list[j].split()[:3] for k, coord in enumerate(coords): coords[k] = float(coord) atom.coords = np.array(coords) self.get_ase_mol() # self.calculate_symmetry_number() def set_bond_length(self, bond_index, length): """ This is a method to set bond lengths Variabels: - bond_index (int): the index of the bond you want to edit - length (float, int): the distance you want to set the bond (in angstroms) """ assert isinstance(length, (float, int)) matched = False for bond in self.bonds: if bond.index == bond_index: matched = True break if not matched: logging.info("Angle index provided is out of range. Nothing was changed.") return self i, j = bond.atom_indices self.ase_molecule.set_distance( a0=i, a1=j, distance=length, mask=bond.mask, fix=0 ) bond.length = length self.update_coords_from(mol_type="ase") return self def set_angle(self, angle_index, angle): """ A method that will set the angle of an Angle object accordingly """ assert isinstance( angle, (int, float)), "Plese provide a float or an int for the angle" matched = False for a in self.angles: if a.index == angle_index: matched = True break if not matched: logging.info("Angle index provided is out of range. Nothing was changed.") return self i, j, k = a.atom_indices self.ase_molecule.set_angle( a1=i, a2=j, a3=k, angle=angle, mask=a.mask ) a.degree = angle self.update_coords_from(mol_type="ase") return self def set_torsion(self, torsion_index, dihedral): """ A method that will set the diehdral angle of a Torsion object accordingly. """ assert isinstance( dihedral, (int, float)), "Plese provide a float or an int for the diehdral angle" matched = False for torsion in self.torsions: if torsion.index == torsion_index: matched = True break if not matched: logging.info("Torsion index provided is out of range. Nothing was changed.") return self i, j, k, l = torsion.atom_indices self.ase_molecule.set_dihedral( a1=i, a2=j, a3=k, a4=l, angle=dihedral, mask=torsion.mask ) torsion.dihedral = dihedral self.update_coords_from(mol_type="ase") return self def set_cistrans(self, cistrans_index, stero="E"): """ A module that will set a corresponding cistrans bond to the proper E/Z config """ assert stero.upper() in [ "E", "Z"], "Please specify a valid stero direction." matched = False for cistrans in self.cistrans: if cistrans.index == cistrans_index: matched = True break if not matched: logging.info("CisTrans index provided is out of range. Nothing was changed.") return self if cistrans.stero == stero.upper(): self.update_coords_from("ase") return self else: cistrans.stero = stero.upper() i, j, k, l = cistrans.atom_indices self.ase_molecule.rotate_dihedral( a1=i, a2=j, a3=k, a4=l, angle=float(180), mask=cistrans.mask ) cistrans.stero = stero.upper() self.update_coords_from(mol_type="ase") return self def set_chirality(self, chiral_center_index, stero="R"): """ A module that can set the orientation of a chiral center. """ assert stero.upper() in ["R", "S"], "Specify a valid stero orientation" centers_dict = { 'R': Chem.rdchem.ChiralType.CHI_TETRAHEDRAL_CW, 'S': Chem.rdchem.ChiralType.CHI_TETRAHEDRAL_CCW } assert isinstance(chiral_center_index, int), "Please provide an integer for the index" rdmol = self.rdkit_molecule.__copy__() match = False for chiral_center in self.chiral_centers: if chiral_center.index == chiral_center_index: match = True break if not match: logging.info("ChiralCenter index provided is out of range. Nothing was changed") return self rdmol.GetAtomWithIdx(chiral_center.atom_index).SetChiralTag( centers_dict[stero.upper()]) rdkit.Chem.rdDistGeom.EmbedMolecule(rdmol) old_torsions = self.torsions[:] + self.cistrans[:] self.rdkit_molecule = rdmol self.update_coords_from(mol_type="rdkit") # Now resetting dihedral angles in case if they changed. for torsion in old_torsions: i, j, k, l = torsion.atom_indices self.ase_molecule.set_dihedral( a1=i, a2=j, a3=k, a4=l, mask=torsion.mask, angle=torsion.dihedral, ) self.update_coords_from(mol_type="ase") return self def calculate_symmetry_number(self): from rmgpy.qm.symmetry import PointGroupCalculator from rmgpy.qm.qmdata import QMData atom_numbers = self.ase_molecule.get_atomic_numbers() coordinates = self.ase_molecule.get_positions() qmdata = QMData( groundStateDegeneracy=1, # Only needed to check if valid QMData numberOfAtoms=len(atom_numbers), atomicNumbers=atom_numbers, atomCoords=(coordinates, str('angstrom')), energy=(0.0, str('kcal/mol')) # Only needed to avoid error ) settings = type(str(''), (), dict(symmetryPath=str( 'symmetry'), scratchDirectory="."))() # Creates anonymous class pgc = PointGroupCalculator(settings, self.smiles, qmdata) pg = pgc.calculate() #os.remove("{}.symm".format(self.smiles)) if pg is not None: symmetry_number = pg.symmetryNumber else: symmetry_number = 1 return symmetry_number
def write_pw_in(d,a,p): if 'iofile' in p.keys() : # write special varians of the pw input fh=open(os.path.join(d,p['iofile']+'.in'),'w') else : # write standard pw input fh=open(os.path.join(d,'pw.in'),'w') # ---------------------------------------------------------- # CONTROL section # ---------------------------------------------------------- fh.write(' &CONTROL\n') fh.write(" calculation = '%s',\n" % p['calc']) pwin_k=['tstress', 'tprnfor','nstep','pseudo_dir','outdir', 'wfcdir', 'prefix','forc_conv_thr', 'etot_conv_thr'] write_section_params(fh, pwin_k, p) fh.write(' /\n') # ---------------------------------------------------------- # SYSTEM section # ---------------------------------------------------------- fh.write(' &SYSTEM\n') if p['use_symmetry'] : # Need to use symmetry properly # create a dummy atoms object for primitive cell primcell=write_cell_params(fh,a,p) if primcell : # primitive cell has been found - let us use it cr=Atoms(cell=primcell[0],scaled_positions=primcell[1],numbers=primcell[2],pbc=True) else : # no primitive cell found - drop the symmetry cr=a else : cr=a p['ibrav']=0 fh.write(" nat = %d,\n" % (cr.get_number_of_atoms())) fh.write(" ntyp = %d,\n" % (len(set(cr.get_atomic_numbers())))) pwin_k=['ecutwfc','ibrav','nbnd','occupations'] write_section_params(fh, pwin_k, p) fh.write(' /\n') # ---------------------------------------------------------- # ELECTRONS section # ---------------------------------------------------------- fh.write(' &ELECTRONS\n') fh.write(' /\n') # ---------------------------------------------------------- # | IONS section # ---------------------------------------------------------- if p['calc'] in ['vc-relax', 'vc-md', 'md', 'relax']: fh.write(' &IONS\n') pwin_k=['ion_dynamics','ion_positions', 'phase_space', 'pot_extrapolation'] write_section_params(fh, pwin_k, p) fh.write(' /\n') # ---------------------------------------------------------- # | CELL section # ---------------------------------------------------------- if p['calc'] in ['vc-relax', 'vc-md']: fh.write('&CELL\n') pwin_k=['cell_dynamics','press', 'cell_dofree'] write_section_params(fh, pwin_k, p) fh.write('/\n') # ---------------------------------------------------------- # Card: ATOMIC_SPECIES # ---------------------------------------------------------- fh.write('ATOMIC_SPECIES\n') xc=p['xc'] pp_type=p['pp_type'] pp_format=p['pp_format'] for nm, mass in set(zip(cr.get_chemical_symbols(),cr.get_masses())): fh.write(" %s %g %s_%s_%s.%s \n" % (nm, mass, nm, xc, pp_type, pp_format)) # ---------------------------------------------------------- # Card: ATOMIC_POSITIONS # ---------------------------------------------------------- fh.write('ATOMIC_POSITIONS crystal\n') # Now we can write it out for n,v in zip(cr.get_chemical_symbols(),cr.get_scaled_positions()): fh.write(" %s %g %g %g\n" % (n, v[0], v[1], v[2])) # ---------------------------------------------------------- # Card: CELL_PARAMETERS # ---------------------------------------------------------- # Write out only if ibrav==0 - no symmetry used if not p['use_symmetry'] or p['ibrav']==0: fh.write('CELL_PARAMETERS angstrom\n') for v in cr.get_cell(): fh.write(' %f %f %f\n' % tuple(v)) # ---------------------------------------------------------- # Card: K_POINTS # ---------------------------------------------------------- if p['kpt_type'] in ['automatic','edos'] : fh.write('K_POINTS %s\n' % 'automatic') else : fh.write('K_POINTS %s\n' % p['kpt_type']) if p['kpt_type'] is 'automatic': fh.write(' %d %d %d %d %d %d\n' % (tuple(p['kpts'])+tuple(p['kpt_shift']))) elif p['kpt_type'] is 'edos': fh.write(' %d %d %d %d %d %d\n' % (tuple(p['nkdos'])+tuple([0,0,0]))) elif not p['kpt_type'] is 'gamma': write_q_path(fh,p['qpath'],p['points']) fh.close()
elif cell_type == 'ortho' and border_type == 'zigzag': up_sat.translate([0,-one_to_zero[1],0]) up_sat.translate([bond_CC * 0.25,0,0]) elif cell_type == 'ortho' and border_type == 'armchair': up_sat.translate(-one_to_zero) right_sat.translate([bond_CC * (3 ** .5) * 0.5 ,0,0]) up_sat.translate(up_shift) up_sat.translate([0,bond_CH,0]) gr = gr + down_sat + up_sat + left_sat + right_sat gr.center(axis=2) natoms = len(gr) ncarbons = len((gr.get_atomic_numbers() == 6).nonzero()[0]) if show_atoms: view(gr) if verbose: print(f'Generated graphene sheet with {natoms} atoms') print(f'Cell dimensions are {np.linalg.norm(gr.cell[0]):.2f} x {np.linalg.norm(gr.cell[1]):.2f} x {np.linalg.norm(gr.cell[2]):.2f} ang') # ### 2. Build first neighbours list for each atom if verbose: print('Building first neighbours list') NNlist_i,NNlist_j = neighbor_list('ij', a=gr, cutoff={('C','C'): bond_CC+.2}, self_interaction=False) carbon_atoms = np.unique(NNlist_i)
def write_cell_params(fh, a, p): ''' Write the specification of the cell using a traditional A,B,C, alpha, beta, gamma scheme. The symmetry and parameters are determined from the atoms (a) object. The atoms object is translated into a primitive unit cell but *not* converted. This is just an internal procedure. If you wish to work with primitive unit cells in ASE, you need to make a conversion yourself. The cell params are in angstrom. Input ----- fh file handle of the opened pw.in file a atoms object p parameters dictionary Output ------ Primitive cell tuple of arrays: (lattice, atoms, atomic numbers) ''' assert(p['use_symmetry']) # Get a spacegroup name and number for the a sg,sgn=spglib.get_spacegroup(a).split() # Extract the number sgn=int(sgn[1:-1]) # Extract the lattice type ltyp=sg[0] # Find a primitive unit cell for the system # puc is a tuple of (lattice, atoms, atomic numbers) puc=spglib.find_primitive(a) cell=puc[0] apos=puc[1] anum=puc[2] icell=a.get_cell() A=norm(icell[0]) B=norm(icell[1]) C=norm(icell[2]) # Select appropriate ibrav if sgn >= 195 : # Cubic lattice if ltyp=='P': p['ibrav']=1 # Primitive qepc=array([[1,0,0],[0,1,0],[0,0,1]]) elif ltyp=='F': p['ibrav']=2 # FCC qepc=array([[-1,0,1],[0,1,1],[-1,1,0]])/2.0 elif ltyp=='I': p['ibrav']=3 # BCC qepc=array([[1,1,1],[-1,1,1],[-1,-1,1]])/2.0 else : print 'Impossible lattice symmetry! Contact the author!' raise NotImplementedError #a=sqrt(2)*norm(cell[0]) qepc=A*qepc fh.write(' A = %f,\n' % (A,)) elif sgn >= 143 : # Hexagonal and trigonal if ltyp=='P' : p['ibrav']=4 # Primitive qepc=array([[1,0,0],[-1/2,sqrt(3)/2,0],[0,0,C/A]]) elif ltyp=='R' : p['ibrav']=5 # Trigonal rhombohedral raise NotImplementedError else : print 'Impossible lattice symmetry! Contact the author!' raise NotImplementedError qepc=A*qepc fh.write(' A = %f,\n' % (A,)) fh.write(' C = %f,\n' % (C,)) elif sgn >= 75 : raise NotImplementedError elif sgn ==1 : # P1 symmetry - no special primitive cell signal to the caller p['ibrav']=0 return None else : raise NotImplementedError cp=Atoms(cell=puc[0], scaled_positions=puc[1], numbers=puc[2], pbc=True) qepc=Atoms(cell=qepc, positions=cp.get_positions(), numbers=cp.get_atomic_numbers(), pbc=True) return qepc.get_cell(), qepc.get_scaled_positions(), qepc.get_atomic_numbers()
def write_pw_in(d,a,p): if 'iofile' in p.keys() : # write special varians of the pw input fh=open(os.path.join(d,p['iofile']+'.in'),'w') else : # write standard pw input fh=open(os.path.join(d,'pw.in'),'w') # ---------------------------------------------------------- # CONTROL section # ---------------------------------------------------------- fh.write(' &CONTROL\n') fh.write(" calculation = '%s',\n" % p['calc']) pwin_k=['tstress', 'tprnfor','nstep','pseudo_dir','outdir', 'wfcdir', 'prefix','forc_conv_thr', 'etot_conv_thr'] write_section_params(fh, pwin_k, p) fh.write(' /\n') # ---------------------------------------------------------- # SYSTEM section # ---------------------------------------------------------- fh.write(' &SYSTEM\n') if p['use_symmetry'] : # Need to use symmetry properly # create a dummy atoms object for primitive cell primcell=write_cell_params(fh,a,p) if primcell : # primitive cell has been found - let us use it cr=Atoms(cell=primcell[0],scaled_positions=primcell[1],numbers=primcell[2],pbc=True) else : # no primitive cell found - drop the symmetry cr=a else : cr=a p['ibrav']=0 fh.write(" nat = %d,\n" % (cr.get_number_of_atoms())) fh.write(" ntyp = %d,\n" % (len(set(cr.get_atomic_numbers())))) pwin_k=['ecutwfc','ibrav','nbnd','occupations','degauss','smearing','ecutrho','nbnd'] # must also take into account degauss and smearing write_section_params(fh, pwin_k, p) fh.write(' /\n') # ---------------------------------------------------------- # ELECTRONS section # ---------------------------------------------------------- fh.write(' &ELECTRONS\n') # must also take into account mixing_beta mixing_mode and diagonalization pwin_k=['conv_thr','mixing_beta','mixing_mode','diagonalization', 'mixing_ndim','electron_maxstep'] write_section_params(fh, pwin_k, p) fh.write(' /\n') # ---------------------------------------------------------- # | IONS section # ---------------------------------------------------------- if p['calc'] in ['vc-relax', 'vc-md', 'md', 'relax']: fh.write(' &IONS\n') pwin_k=['ion_dynamics','ion_positions', 'phase_space', 'pot_extrapolation'] write_section_params(fh, pwin_k, p) fh.write(' /\n') # ---------------------------------------------------------- # | CELL section # ---------------------------------------------------------- if p['calc'] in ['vc-relax', 'vc-md']: fh.write('&CELL\n') pwin_k=['cell_dynamics','press', 'cell_dofree'] write_section_params(fh, pwin_k, p) fh.write('/\n') # ---------------------------------------------------------- # Card: ATOMIC_SPECIES # ---------------------------------------------------------- fh.write('ATOMIC_SPECIES\n') xc=p['xc'] pp_type=p['pp_type'] pp_format=p['pp_format'] # we check if the desired potential exists. Get the PP locations if 'ESPRESSO_PSEUDO' in os.environ: pppaths = os.environ['ESPRESSO_PSEUDO'] else: pppaths = p['pseudo_dir'] # default pppaths = pppaths.split(':') # search for element PP in each location (in principle with QE only one location) for nm, mass in set(zip(cr.get_chemical_symbols(),cr.get_masses())): name = "%s_%s_%s.%s" % (nm, xc, pp_type, pp_format) found = False for path in pppaths: filename = os.path.join(path, name) match = glob.glob(filename) if match: # the exact name as expected found = True name = match[0] if not found: # a more permissive name with element.*.format name = "%s.*.%s" % (nm, pp_format) filename = os.path.join(path, name) match = glob.glob(filename) if match: found = True name = match[0] # the first match if not found: # a more permissive name with element_*.format name = "%s_*.%s" % (nm, pp_format) filename = os.path.join(path, name) match = glob.glob(filename) if match: found = True name = match[0] # the first match if not found: # a more permissive name with just the element_* name = "%s_*" % (nm) filename = os.path.join(path, name) match = glob.glob(filename) if match: found = True name = match[0] # the first match if not found: # a more permissive name with just the element.* name = "%s.*" % (nm) filename = os.path.join(path, name) match = glob.glob(filename) if match: found = True name = match[0] # the first match if not found: raise RuntimeError('Espresso: No pseudopotential for %s. Aborting.' % nm) fh.write(" %s %g %s \n" % (nm, mass, os.path.basename(name))) # ---------------------------------------------------------- # Card: ATOMIC_POSITIONS # ---------------------------------------------------------- fh.write('ATOMIC_POSITIONS crystal\n') # Now we can write it out for n,v in zip(cr.get_chemical_symbols(),cr.get_scaled_positions()): fh.write(" %s %g %g %g\n" % (n, v[0], v[1], v[2])) # ---------------------------------------------------------- # Card: CELL_PARAMETERS # ---------------------------------------------------------- # Write out only if ibrav==0 - no symmetry used if not p['use_symmetry'] or p['ibrav']==0: fh.write('CELL_PARAMETERS angstrom\n') for v in cr.get_cell(): fh.write(' %f %f %f\n' % tuple(v)) # ---------------------------------------------------------- # Card: K_POINTS # ---------------------------------------------------------- if p['kpt_type'] in ['automatic','edos'] : fh.write('K_POINTS %s\n' % 'automatic') else : fh.write('K_POINTS %s\n' % p['kpt_type']) if p['kpt_type'] is 'automatic': fh.write(' %d %d %d %d %d %d\n' % (tuple(p['kpts'])+tuple(p['kpt_shift']))) elif p['kpt_type'] is 'edos': fh.write(' %d %d %d %d %d %d\n' % (tuple(p['nkdos'])+tuple([0,0,0]))) elif not p['kpt_type'] is 'gamma': write_q_path(fh,p['qpath'],p['points']) fh.close()
def draw_BSsurf(interfc, bdcutoff=0.85, ascale=0.4, brad=0.1, outName=None, zLim=None, pseudoBond=False, hBond=False, title=''): plt.rcParams["font.family"] = "Dejavu Serif" print(' |- Drawing BS pic of \t%s to\t%s'%\ (interfc.tags, outName+'-bs.png')) atoms = interfc.get_allAtoms() if zLim is None: zLim = [(interfc.get_subPos()[:, 2].min() + interfc.get_subPos()[:, 2].max()) / 2, interfc.get_pos()[:, 2].max()] atoms = atoms[[a.index for a in atoms \ if zLim[0] < interfc.get_pos()[a.index][2] <= zLim[1]]] ori_cell = convert_cell(atoms.get_cell()) atoms.set_positions(atoms.get_positions() + (ori_cell[0] + ori_cell[1]) / 2) atoms.wrap() atoms = atoms * [2, 2, 1] atoms.set_positions(atoms.get_positions() - (ori_cell[0] + ori_cell[1]) / 2) atoms.set_pbc([0, 0, 0]) atoms = Atoms(sorted(atoms, key=lambda atm: atm.position[2])) allpos = atoms.get_positions() allnum = atoms.get_atomic_numbers() allpos = atoms.get_positions() allz = allpos[:, 2] # color gradient according to z # if max(allz) - min(allz) < zlim: # allc=[1]*len(allz) adsList = interfc.get_adsList() bufList = interfc.get_bufList() adsList = [i for i in range(len(atoms)) \ if atoms.get_positions()[i]-ori_cell[0]-ori_cell[1]\ in interfc.get_pos()[adsList]] bufList = [i for i in range(len(atoms)) \ if atoms.get_positions()[i]-ori_cell[0]-ori_cell[1]\ in interfc.get_pos()[bufList]] allc = [ 0 if i in adsList # Full color else 0.5 if i in bufList # Shallow color else 1.0 for i in range(len(atoms)) ] bdpair = [[i[0], i[1]] for i in get_bondpairs(atoms, bdcutoff)] hbpair = [] if hBond: hbpair = getHBonds(atoms, allnum, bdpair) if pseudoBond: hbpair = getPseudoBonds(atoms, allnum, bdpair) plotCell(ori_cell[0] * 0 + ori_cell[1] * 0, ori_cell[0] * 1 + ori_cell[1] * 0, ori_cell[0] * 1 + ori_cell[1] * 1, ori_cell[0] * 0 + ori_cell[1] * 1, linwt=5) for i in range(len(atoms)): anum = allnum[i] apos = allpos[i] plotBondHalf(allnum, allpos, bdpair, i,\ atomscale=ascale, bdrad=brad, mode='low',\ colorparam=allc[i], linwt=1.5-allc[i]/2) plotAtom(anum, apos, scale=ascale,\ colorparam=allc[i], linwt=1.5-allc[i]/2, enlarge=True) plotElliShine(anum, apos, atomscale=ascale, shift=0.3, scale=0.5, enlarge=True) plotBondHalf(allnum, allpos, bdpair, i,\ atomscale=ascale, bdrad=brad, mode='high',\ colorparam=allc[i], linwt=1.5-allc[i]/2) plothHbond(allnum, allpos, hbpair, i,\ atomscale=ascale) plt.axis('scaled') plt.xlim(min(ori_cell[0][0], ori_cell[1][0]), max(ori_cell[0][0], ori_cell[1][0])) plt.ylim(min(ori_cell[0][1], ori_cell[1][1]), max(ori_cell[0][1], ori_cell[1][1])) plt.tight_layout() plt.axis('off') plt.gca().xaxis.set_major_locator(plt.NullLocator()) plt.gca().yaxis.set_major_locator(plt.NullLocator()) plt.margins(0, 0) plt.title(title, fontsize='xx-large', fontweight='bold') if outName is not None: plt.savefig(outName+'-bs.png', dpi=100, \ bbox_inches = "tight", transparent=True, pad_inches = 0) else: plt.show() plt.close()
def test_gto_integration(self): """Tests that the completely analytical partial power spectrum with the GTO basis corresponds to the easier-to-code but less performant numerical integration done with python. """ sigma = 0.55 rcut = 2.0 nmax = 2 lmax = 2 # Limits for radius r1 = 0. r2 = rcut+5 # Limits for theta t1 = 0 t2 = np.pi # Limits for phi p1 = 0 p2 = 2*np.pi positions = np.array([[0.0, 0.0, 0.0], [-0.3, 0.5, 0.4]]) symbols = np.array(["H", "C"]) system = Atoms(positions=positions, symbols=symbols) atomic_numbers = system.get_atomic_numbers() elements = set(system.get_atomic_numbers()) n_elems = len(elements) # Calculate the analytical power spectrum and the weights and decays of # the radial basis functions. soap = SOAP(atomic_numbers=atomic_numbers, lmax=lmax, nmax=nmax, sigma=sigma, rcut=rcut, crossover=True, sparse=False) analytical_power_spectrum = soap.create(system, positions=[[0, 0, 0]])[0] alphagrid = np.reshape(soap._alphas, [10, nmax]) betagrid = np.reshape(soap._betas, [10, nmax, nmax]) coeffs = np.zeros((n_elems, nmax, lmax+1, 2*lmax+1)) for iZ, Z in enumerate(elements): indices = np.argwhere(atomic_numbers == Z)[0] elem_pos = positions[indices] for n in range(nmax): for l in range(lmax+1): for im, m in enumerate(range(-l, l+1)): # Calculate numerical coefficients def soap_coeff(phi, theta, r): # Regular spherical harmonic ylm_comp = scipy.special.sph_harm(np.abs(m), l, phi, theta) # NOTE: scipy swaps phi and theta # Construct real (tesseral) spherical harmonics for # easier integration without having to worry about the # imaginary part ylm_real = np.real(ylm_comp) ylm_imag = np.imag(ylm_comp) if m < 0: ylm = np.sqrt(2)*(-1)**m*ylm_imag elif m == 0: ylm = ylm_comp else: ylm = np.sqrt(2)*(-1)**m*ylm_real # Spherical gaussian type orbital gto = 0 for i in range(nmax): i_alpha = alphagrid[l, i] i_beta = betagrid[l, n, i] i_gto = i_beta*r**l*np.exp(-i_alpha*r**2) gto += i_gto # Atomic density rho = 0 for i_pos in elem_pos: ix = i_pos[0] iy = i_pos[1] iz = i_pos[2] ri_squared = ix**2+iy**2+iz**2 rho += np.exp(-1/(2*sigma**2)*(r**2 + ri_squared - 2*r*(np.sin(theta)*np.cos(phi)*ix + np.sin(theta)*np.sin(phi)*iy + np.cos(theta)*iz))) # Jacobian jacobian = np.sin(theta)*r**2 return gto*ylm*rho*jacobian cnlm = tplquad( soap_coeff, r1, r2, lambda r: t1, lambda r: t2, lambda r, theta: p1, lambda r, theta: p2, epsabs=0.001, epsrel=0.001, ) integral, error = cnlm coeffs[iZ, n, l, im] = integral # Calculate the partial power spectrum numerical_power_spectrum = [] for zi in range(n_elems): for zj in range(n_elems): for l in range(lmax+1): for ni in range(nmax): for nj in range(nmax): if nj >= ni: if zj >= zi: value = np.dot(coeffs[zi, ni, l, :], coeffs[zj, nj, l, :]) prefactor = np.pi*np.sqrt(8/(2*l+1)) value *= prefactor numerical_power_spectrum.append(value) # print("Numerical: {}".format(numerical_power_spectrum)) # print("Analytical: {}".format(analytical_power_spectrum)) self.assertTrue(np.allclose(numerical_power_spectrum, analytical_power_spectrum, atol=1e-15, rtol=0.01))
def test_poly_integration(self): """Tests that the partial power spectrum with the polynomial basis done with C corresponds to the easier-to-code but less performant integration done with python. """ sigma = 0.55 rcut = 2.0 nmax = 2 lmax = 2 # Limits for radius r1 = 0. r2 = rcut+5 # Limits for theta t1 = 0 t2 = np.pi # Limits for phi p1 = 0 p2 = 2*np.pi positions = np.array([[0.0, 0.0, 0.0], [-0.3, 0.5, 0.4]]) symbols = np.array(["H", "C"]) system = Atoms(positions=positions, symbols=symbols) atomic_numbers = system.get_atomic_numbers() elements = set(system.get_atomic_numbers()) n_elems = len(elements) # Calculate the overlap of the different polynomial functions in a # matrix S. These overlaps defined through the dot product over the # radial coordinate are analytically calculable: Integrate[(rc - r)^(a # + 2) (rc - r)^(b + 2) r^2, {r, 0, rc}]. Then the weights B that make # the basis orthonormal are given by B=S^{-1/2} S = np.zeros((nmax, nmax)) for i in range(1, nmax+1): for j in range(1, nmax+1): S[i-1, j-1] = (2*(rcut)**(7+i+j))/((5+i+j)*(6+i+j)*(7+i+j)) betas = sqrtm(np.linalg.inv(S)) # Calculate the analytical power spectrum and the weights and decays of # the radial basis functions. soap = SOAP(atomic_numbers=atomic_numbers, lmax=lmax, nmax=nmax, sigma=sigma, rcut=rcut, rbf="polynomial", crossover=True, sparse=False) analytical_power_spectrum = soap.create(system, positions=[[0, 0, 0]])[0] coeffs = np.zeros((n_elems, nmax, lmax+1, 2*lmax+1)) for iZ, Z in enumerate(elements): indices = np.argwhere(atomic_numbers == Z)[0] elem_pos = positions[indices] for n in range(nmax): for l in range(lmax+1): for im, m in enumerate(range(-l, l+1)): # Calculate numerical coefficients def soap_coeff(phi, theta, r): # Regular spherical harmonic ylm_comp = scipy.special.sph_harm(np.abs(m), l, phi, theta) # NOTE: scipy swaps phi and theta # Construct real (tesseral) spherical harmonics for # easier integration without having to worry about the # imaginary part ylm_real = np.real(ylm_comp) ylm_imag = np.imag(ylm_comp) if m < 0: ylm = np.sqrt(2)*(-1)**m*ylm_imag elif m == 0: ylm = ylm_comp else: ylm = np.sqrt(2)*(-1)**m*ylm_real # Polynomial basis poly = 0 for k in range(1, nmax+1): poly += betas[n, k-1]*(rcut-np.clip(r, 0, rcut))**(k+2) # Atomic density rho = 0 for i_pos in elem_pos: ix = i_pos[0] iy = i_pos[1] iz = i_pos[2] ri_squared = ix**2+iy**2+iz**2 rho += np.exp(-1/(2*sigma**2)*(r**2 + ri_squared - 2*r*(np.sin(theta)*np.cos(phi)*ix + np.sin(theta)*np.sin(phi)*iy + np.cos(theta)*iz))) # Jacobian jacobian = np.sin(theta)*r**2 return poly*ylm*rho*jacobian cnlm = tplquad( soap_coeff, r1, r2, lambda r: t1, lambda r: t2, lambda r, theta: p1, lambda r, theta: p2, epsabs=0.0001, epsrel=0.0001, ) integral, error = cnlm coeffs[iZ, n, l, im] = integral # Calculate the partial power spectrum numerical_power_spectrum = [] for zi in range(n_elems): for zj in range(n_elems): for l in range(lmax+1): for ni in range(nmax): for nj in range(nmax): if nj >= ni and zj >= zi: value = np.dot(coeffs[zi, ni, l, :], coeffs[zj, nj, l, :]) prefactor = np.pi*np.sqrt(8/(2*l+1)) value *= prefactor numerical_power_spectrum.append(value) # print("Numerical: {}".format(numerical_power_spectrum)) # print("Analytical: {}".format(analytical_power_spectrum)) self.assertTrue(np.allclose(numerical_power_spectrum, analytical_power_spectrum, atol=1e-15, rtol=0.01))
def parse_mol_info(fname, fcharges, axis, buffa, buffo, pbcbonds, printdih, ignorebonds, ignoreimproper): iaxis = {"x": 0, "y": 1, "z": 2} if axis in iaxis: repaxis = iaxis[axis] else: print("Error: invalid axis") sys.exit(0) if fcharges: chargesLabel = {} with open(fcharges, "r") as f: for line in f: chargesLabel[line.split()[0]] = float(line.split()[1]) # set openbabel file format base, ext = os.path.splitext(fname) obConversion = openbabel.OBConversion() obConversion.SetInAndOutFormats(ext[1:], "xyz") # trick to disable ring perception and make the ReadFile waaaay faster # Source: https://sourceforge.net/p/openbabel/mailman/openbabel-discuss/thread/56e1812d-396a-db7c-096d-d378a077853f%40ipcms.unistra.fr/#msg36225392 obConversion.AddOption("b", openbabel.OBConversion.INOPTIONS) # read molecule to OBMol object mol = openbabel.OBMol() obConversion.ReadFile(mol, fname) mol.ConnectTheDots() # necessary because of the 'b' INOPTION # split the molecules molecules = mol.Separate() # detect the molecules types mTypes = {} mapmTypes = {} atomIdToMol = {} nty = 0 for i, submol in enumerate(molecules, start=1): atomiter = openbabel.OBMolAtomIter(submol) atlist = [] for at in atomiter: atlist.append(at.GetAtomicNum()) atomIdToMol[at.GetId()] = i foundType = None for ty in mTypes: # check if there's already a molecule of this type if atlist == mTypes[ty]: foundType = ty # if not, create a new type if not foundType: nty += 1 foundType = nty mTypes[nty] = atlist mapmTypes[i] = foundType # get atomic labels from pdb idToAtomicLabel = {} if ext[1:] == "pdb": for res in openbabel.OBResidueIter(mol): for atom in openbabel.OBResidueAtomIter(res): if (atomIdToMol[atom.GetId()] > 1) and (len(mTypes) > 1): idToAtomicLabel[ atom.GetId()] = res.GetAtomID(atom).strip() + str( mapmTypes[atomIdToMol[atom.GetId()]]) else: idToAtomicLabel[atom.GetId()] = res.GetAtomID(atom).strip() else: if not ob3: etab = openbabel.OBElementTable() for atom in openbabel.OBMolAtomIter(mol): if (atomIdToMol[atom.GetId()] > 1) and (len(mTypes) > 1): if ob3: idToAtomicLabel[atom.GetId()] = openbabel.GetSymbol( atom.GetAtomicNum()) + str( mapmTypes[atomIdToMol[atom.GetId()]]) else: idToAtomicLabel[atom.GetId()] = etab.GetSymbol( atom.GetAtomicNum()) + str( mapmTypes[atomIdToMol[atom.GetId()]]) else: if ob3: idToAtomicLabel[atom.GetId()] = openbabel.GetSymbol( atom.GetAtomicNum()) else: idToAtomicLabel[atom.GetId()] = etab.GetSymbol( atom.GetAtomicNum()) # print(idToAtomicLabel) # identify atom types and get masses outMasses = "Masses\n\n" massTypes = {} mapTypes = {} nmassTypes = 0 atomIterator = openbabel.OBMolAtomIter(mol) for atom in atomIterator: i = atom.GetId() if idToAtomicLabel[i] not in massTypes: nmassTypes += 1 mapTypes[nmassTypes] = idToAtomicLabel[i] massTypes[idToAtomicLabel[i]] = nmassTypes outMasses += "\t%d\t%.3f\t# %s\n" % ( nmassTypes, atom.GetAtomicMass(), idToAtomicLabel[i]) # create atoms list outAtoms = "Atoms # full\n\n" xmin = float("inf") xmax = float("-inf") ymin = float("inf") ymax = float("-inf") zmin = float("inf") zmax = float("-inf") natoms = 0 acoords = [] for mnum, imol in enumerate(molecules, start=1): atomIterator = openbabel.OBMolAtomIter(imol) for atom in sorted(atomIterator, key=lambda x: x.GetId()): natoms += 1 i = atom.GetId() apos = (atom.GetX(), atom.GetY(), atom.GetZ()) acoords.append(Atom(atom.GetAtomicNum(), apos)) # look for the maximum and minimum x for the box (improve later with numpy and all coordinates) if apos[0] > xmax: xmax = apos[0] if apos[0] < xmin: xmin = apos[0] if apos[1] > ymax: ymax = apos[1] if apos[1] < ymin: ymin = apos[1] if apos[2] > zmax: zmax = apos[2] if apos[2] < zmin: zmin = apos[2] if fcharges: outAtoms += "\t%d\t%d\t%d\t%.6f\t%.4f\t%.4f\t%.4f\t# %s\n" % ( i + 1, mnum, massTypes[idToAtomicLabel[i]], chargesLabel[idToAtomicLabel[i]], atom.GetX(), atom.GetY(), atom.GetZ(), idToAtomicLabel[i]) else: outAtoms += "\t%d\t%d\t%d\tX.XXXXXX\t%.4f\t%.4f\t%.4f\t# %s\n" % ( i + 1, mnum, massTypes[idToAtomicLabel[i]], atom.GetX(), atom.GetY(), atom.GetZ(), idToAtomicLabel[i]) # define box shape and size try: fromBounds = False rcell = mol.GetData(12) cell = openbabel.toUnitCell(rcell) v1 = [ cell.GetCellVectors()[0].GetX(), cell.GetCellVectors()[0].GetY(), cell.GetCellVectors()[0].GetZ() ] v2 = [ cell.GetCellVectors()[1].GetX(), cell.GetCellVectors()[1].GetY(), cell.GetCellVectors()[1].GetZ() ] v3 = [ cell.GetCellVectors()[2].GetX(), cell.GetCellVectors()[2].GetY(), cell.GetCellVectors()[2].GetZ() ] boxinfo = [v1, v2, v3] orthogonal = True for i, array in enumerate(boxinfo): for j in range(3): if i == j: continue if not math.isclose(0., array[j], abs_tol=1e-6): orthogonal = False except: fromBounds = True v1 = [xmax - xmin, 0., 0.] v2 = [0., ymax - ymin, 0.] v3 = [0., 0., zmax - zmin] orthogonal = True # add buffer if orthogonal: buf = [] boxinfo = [v1, v2, v3] for i, val in enumerate(boxinfo[repaxis]): if i == repaxis: buf.append(val + buffa) else: buf.append(val) boxinfo[repaxis] = buf for i in range(3): if i == repaxis: continue buf = [] for j, val in enumerate(boxinfo[i]): if j == i: buf.append(val + buffo) else: buf.append(val) boxinfo[i] = buf # print(boxinfo) # Duplicate to get the bonds in the PBC. Taken from (method _crd2bond): # https://github.com/tongzhugroup/mddatasetbuilder/blob/66eb0f15e972be0f5534dcda27af253cd8891ff2/mddatasetbuilder/detect.py#L213 if pbcbonds: acoords = Atoms(acoords, cell=boxinfo, pbc=True) repatoms = acoords.repeat( 2 )[natoms:] # repeat the unit cell in each direction (len(repatoms) = 7*natoms) tree = cKDTree(acoords.get_positions()) d = tree.query(repatoms.get_positions(), k=1)[0] nearest = d < 8. ghost_atoms = repatoms[nearest] realnumber = np.where(nearest)[0] % natoms acoords += ghost_atoms write("replicated.xyz", acoords) # write the structure with the replicated atoms # write new mol with new bonds nmol = openbabel.OBMol() nmol.BeginModify() for idx, (num, position) in enumerate( zip(acoords.get_atomic_numbers(), acoords.positions)): a = nmol.NewAtom(idx) a.SetAtomicNum(int(num)) a.SetVector(*position) nmol.ConnectTheDots() # nmol.PerceiveBondOrders() # super slow becauses it looks for rings nmol.EndModify() else: acoords = Atoms(acoords, cell=boxinfo, pbc=False) nmol = openbabel.OBMol() nmol.BeginModify() for idx, (num, position) in enumerate( zip(acoords.get_atomic_numbers(), acoords.positions)): a = nmol.NewAtom(idx) a.SetAtomicNum(int(num)) a.SetVector(*position) nmol.ConnectTheDots() # nmol.PerceiveBondOrders() # super slow becauses it looks for rings nmol.EndModify() # identify bond types and create bond list outBonds = "Bonds # harmonic\n\n" bondTypes = {} mapbTypes = {} nbondTypes = 0 nbonds = 0 bondsToDelete = [] bondIterators = [] if ignorebonds: sepmols = nmol.Separate() for smol in sepmols[1:]: bondIterators.append(openbabel.OBMolBondIter(smol)) else: bondIterators.append(openbabel.OBMolBondIter(nmol)) lastidx = 1 for iterator in bondIterators: for i, bond in enumerate(iterator, lastidx): b1 = bond.GetBeginAtom().GetId() b2 = bond.GetEndAtom().GetId() # check if its a bond of the replica only if (b1 >= natoms) and (b2 >= natoms): bondsToDelete.append(bond) continue # remap to a real atom if needed if b1 >= natoms: b1 = realnumber[b1 - natoms] if b2 >= natoms: b2 = realnumber[b2 - natoms] # identify bond type btype1 = "%s - %s" % (idToAtomicLabel[b1], idToAtomicLabel[b2]) btype2 = "%s - %s" % (idToAtomicLabel[b2], idToAtomicLabel[b1]) if btype1 in bondTypes: bondid = bondTypes[btype1] bstring = btype1 elif btype2 in bondTypes: bondid = bondTypes[btype2] bstring = btype2 else: nbondTypes += 1 mapbTypes[nbondTypes] = btype1 bondid = nbondTypes bondTypes[btype1] = nbondTypes bstring = btype1 nbonds += 1 outBonds += "\t%d\t%d\t%d\t%d\t# %s\n" % (nbonds, bondid, b1 + 1, b2 + 1, bstring) lastidx = i # delete the bonds of atoms from other replicas for bond in bondsToDelete: nmol.DeleteBond(bond) # identify angle types and create angle list angleTypes = {} mapaTypes = {} nangleTypes = 0 nangles = 0 angleIterators = [] if ignorebonds: sepmols = nmol.Separate() for smol in sepmols[1:]: smol.FindAngles() angleIterators.append(openbabel.OBMolAngleIter(smol)) prevnumatoms = sepmols[0].NumAtoms() else: nmol.FindAngles() angleIterators.append(openbabel.OBMolAngleIter(nmol)) outAngles = "Angles # harmonic\n\n" lastidx = 1 for j, iterator in enumerate(angleIterators, 1): for i, angle in enumerate(iterator, lastidx): if ignorebonds: a1 = angle[1] + prevnumatoms a2 = angle[0] + prevnumatoms a3 = angle[2] + prevnumatoms else: a1 = angle[1] a2 = angle[0] a3 = angle[2] # remap to a real atom if needed if a1 >= natoms: a1 = realnumber[a1 - natoms] if a2 >= natoms: a2 = realnumber[a2 - natoms] if a3 >= natoms: a3 = realnumber[a3 - natoms] atype1 = "%s - %s - %s" % ( idToAtomicLabel[a1], idToAtomicLabel[a2], idToAtomicLabel[a3]) atype2 = "%s - %s - %s" % ( idToAtomicLabel[a3], idToAtomicLabel[a2], idToAtomicLabel[a1]) if atype1 in angleTypes: angleid = angleTypes[atype1] astring = atype1 elif atype2 in angleTypes: angleid = angleTypes[atype2] astring = atype2 else: nangleTypes += 1 mapaTypes[nangleTypes] = atype1 angleid = nangleTypes angleTypes[atype1] = nangleTypes astring = atype1 nangles += 1 outAngles += "\t%d\t%d\t%d\t%d\t%d\t# %s\n" % ( nangles, angleid, a1 + 1, a2 + 1, a3 + 1, astring) lastidx = i if ignorebonds: prevnumatoms += sepmols[j].NumAtoms() # identify dihedral types and create dihedral list if printdih: dihedralTypes = {} mapdTypes = {} ndihedralTypes = 0 ndihedrals = 0 dihedralIterators = [] if ignorebonds: sepmols = nmol.Separate() for smol in sepmols[1:]: smol.FindTorsions() dihedralIterators.append(openbabel.OBMolTorsionIter(smol)) else: nmol.FindTorsions() dihedralIterators.append(openbabel.OBMolTorsionIter(nmol)) outDihedrals = "Dihedrals # charmmfsw\n\n" lastidx = 1 for iterator in dihedralIterators: for i, dihedral in enumerate(iterator, lastidx): a1 = dihedral[0] a2 = dihedral[1] a3 = dihedral[2] a4 = dihedral[3] # remap to a real atom if needed if a1 >= natoms: a1 = realnumber[a1 - natoms] if a2 >= natoms: a2 = realnumber[a2 - natoms] if a3 >= natoms: a3 = realnumber[a3 - natoms] if a4 >= natoms: a4 = realnumber[a4 - natoms] dtype1 = "%s - %s - %s - %s" % ( idToAtomicLabel[a1], idToAtomicLabel[a2], idToAtomicLabel[a3], idToAtomicLabel[a4]) dtype2 = "%s - %s - %s - %s" % ( idToAtomicLabel[a4], idToAtomicLabel[a3], idToAtomicLabel[a2], idToAtomicLabel[a1]) if dtype1 in dihedralTypes: dihedralid = dihedralTypes[dtype1] dstring = dtype1 elif dtype2 in dihedralTypes: dihedralid = dihedralTypes[dtype2] dstring = dtype2 else: ndihedralTypes += 1 mapdTypes[ndihedralTypes] = dtype1 dihedralid = ndihedralTypes dihedralTypes[dtype1] = ndihedralTypes dstring = dtype1 ndihedrals += 1 outDihedrals += "\t%d\t%d\t%d\t%d\t%d\t%d\t# %s\n" % ( ndihedrals, dihedralid, a1 + 1, a2 + 1, a3 + 1, a4 + 1, dstring) lastidx = i if not ignoreimproper: # look for the improper dihedrals improperDihedralTypes = {} mapiDTypes = {} niDihedralTypes = 0 niDihedrals = 0 mollist = [] if ignorebonds: sepmols = nmol.Separate() for smol in sepmols[1:]: smol.PerceiveBondOrders() mollist.append(smol) else: nmol.PerceiveBondOrders() mollist.append(nmol) outImpropers = "Impropers # harmonic\n\n" for imol in mollist: atomIterator = openbabel.OBMolAtomIter(imol) for atom in atomIterator: try: # print(atom.GetHyb(), atom.GetAtomicNum(), atom.GetValence()) expDegree = atom.GetValence() except: # print(atom.GetHyb(), atom.GetAtomicNum(), atom.GetExplicitDegree()) expDegree = atom.GetExplicitDegree() # returns impropers for atoms with connected to other 3 atoms and SP2 hybridization if atom.GetHyb() == 2 and expDegree == 3: connectedAtoms = [] for atom2, depth in openbabel.OBMolAtomBFSIter( imol, atom.GetId() + 1): if depth == 2: connectedAtoms.append(atom2) torsional = [ atom.GetId() + 1, connectedAtoms[0].GetId() + 1, connectedAtoms[1].GetId() + 1, connectedAtoms[2].GetId() + 1 ] a1 = torsional[0] - 1 a2 = torsional[1] - 1 a3 = torsional[2] - 1 a4 = torsional[3] - 1 # remap to a real atom if needed if a1 >= natoms: a1 = realnumber[a1 - natoms] if a2 >= natoms: a2 = realnumber[a2 - natoms] if a3 >= natoms: a3 = realnumber[a3 - natoms] if a4 >= natoms: a4 = realnumber[a4 - natoms] dtype1 = "%s - %s - %s - %s" % ( idToAtomicLabel[a1], idToAtomicLabel[a2], idToAtomicLabel[a3], idToAtomicLabel[a4]) dtype2 = "%s - %s - %s - %s" % ( idToAtomicLabel[a4], idToAtomicLabel[a3], idToAtomicLabel[a2], idToAtomicLabel[a1]) if dtype1 in improperDihedralTypes: idihedralid = improperDihedralTypes[dtype1] dstring = dtype1 elif dtype2 in improperDihedralTypes: idihedralid = improperDihedralTypes[dtype2] dstring = dtype2 else: niDihedralTypes += 1 mapiDTypes[niDihedralTypes] = dtype1 idihedralid = niDihedralTypes improperDihedralTypes[dtype1] = niDihedralTypes dstring = dtype1 niDihedrals += 1 outImpropers += "\t%d\t%d\t%d\t%d\t%d\t%d\t# %s\n" % ( niDihedrals, idihedralid, a1 + 1, a2 + 1, a3 + 1, a4 + 1, dstring) # print header if printdih and (ndihedrals > 0): if ignoreimproper or (niDihedrals == 0): header = "LAMMPS topology created from %s using pdb2lmp.py - By Henrique Musseli Cezar, 2020\n\n\t%d atoms\n\t%d bonds\n\t%d angles\n\t%d dihedrals\n\n\t%d atom types\n\t%d bond types\n\t%d angle types\n\t%d dihedral types\n\n" % ( fname, natoms, nbonds, nangles, ndihedrals, nmassTypes, nbondTypes, nangleTypes, ndihedralTypes) else: header = "LAMMPS topology created from %s using pdb2lmp.py - By Henrique Musseli Cezar, 2020\n\n\t%d atoms\n\t%d bonds\n\t%d angles\n\t%d dihedrals\n\t%d impropers\n\n\t%d atom types\n\t%d bond types\n\t%d angle types\n\t%d dihedral types\n\t%d improper types\n\n" % ( fname, natoms, nbonds, nangles, ndihedrals, niDihedrals, nmassTypes, nbondTypes, nangleTypes, ndihedralTypes, niDihedralTypes) else: header = "LAMMPS topology created from %s using pdb2lmp.py - By Henrique Musseli Cezar, 2020\n\n\t%d atoms\n\t%d bonds\n\t%d angles\n\n\t%d atom types\n\t%d bond types\n\t%d angle types\n\n" % ( fname, natoms, nbonds, nangles, nmassTypes, nbondTypes, nangleTypes) # add box info if fromBounds: boxsize = [(xmin, xmax), (ymin, ymax), (zmin, zmax)] boxsize[repaxis] = (boxsize[repaxis][0] - buffa / 2., boxsize[repaxis][1] + buffa / 2.) for i in range(3): if i == repaxis: continue boxsize[i] = (boxsize[i][0] - buffo / 2., boxsize[i][1] + buffo / 2.) header += "\t%.8f\t%.8f\t xlo xhi\n\t%.8f\t%.8f\t ylo yhi\n\t%.8f\t%.8f\t zlo zhi\n" % ( boxsize[0][0], boxsize[0][1], boxsize[1][0], boxsize[1][1], boxsize[2][0], boxsize[2][1]) else: if orthogonal: header += "\t%.8f\t%.8f\t xlo xhi\n\t%.8f\t%.8f\t ylo yhi\n\t%.8f\t%.8f\t zlo zhi\n" % ( 0., boxinfo[0][0], 0., boxinfo[1][1], 0., boxinfo[2][2]) else: header += "\t%.8f\t%.8f\t xlo xhi\n\t%.8f\t%.8f\t ylo yhi\n\t%.8f\t%.8f\t zlo zhi\n\t%.8f\t%.8f\t%.8f\t xy xz yz\n" % ( 0., boxinfo[0][0], 0., boxinfo[1][1], 0., boxinfo[2][2], boxinfo[1][0], boxinfo[2][0], boxinfo[2][1]) # print Coeffs outCoeffs = "Pair Coeffs\n\n" for i in range(1, nmassTypes + 1): outCoeffs += "\t%d\teps\tsig\t# %s\n" % (i, mapTypes[i]) outCoeffs += "\nBond Coeffs\n\n" for i in range(1, nbondTypes + 1): outCoeffs += "\t%d\tK\tr_0\t# %s\n" % (i, mapbTypes[i]) outCoeffs += "\nAngle Coeffs\n\n" for i in range(1, nangleTypes + 1): outCoeffs += "\t%d\tK\ttetha_0 (deg)\t# %s\n" % (i, mapaTypes[i]) if printdih and (ndihedrals > 0): outCoeffs += "\nDihedral Coeffs\n\n" for i in range(1, ndihedralTypes + 1): outCoeffs += "\t%d\tK\tn\tphi_0 (deg)\tw\t# %s\n" % (i, mapdTypes[i]) if not ignoreimproper and (niDihedralTypes > 0): outCoeffs += "\nImproper Coeffs\n\n" for i in range(1, niDihedralTypes + 1): outCoeffs += "\t%d\tK\txi_0 (deg)\t# %s\n" % (i, mapiDTypes[i]) if printdih and (ndihedrals > 0): if ignoreimproper or (niDihedralTypes == 0): return header + "\n" + outMasses + "\n" + outCoeffs + "\n" + outAtoms + "\n" + outBonds + "\n" + outAngles + "\n" + outDihedrals else: return header + "\n" + outMasses + "\n" + outCoeffs + "\n" + outAtoms + "\n" + outBonds + "\n" + outAngles + "\n" + outDihedrals + "\n" + outImpropers else: return header + "\n" + outMasses + "\n" + outCoeffs + "\n" + outAtoms + "\n" + outBonds + "\n" + outAngles