def randomize_biatom_13(atoms, type_a, type_b, ratio): """ replace randomly by clusters of 13 atoms to acheive target conc """ n_A = 0 n_B = 0 for atom in atoms: if atom.symbol == type_a: n_A += 1 elif atom.symbol == type_b: n_B += 1 else: raise Exception('Extra chemical element %s!'%atom.symbol) #print n_A, n_B N = len(atoms) nl = NeighborList([1.5]*N, self_interaction=False, bothways=True) # 2*1.5=3 Angstr. radius nl.build(atoms) #print "conc", n_A *1.0 / N r = random.Random() while n_A < ratio*N: # add A atoms randomly index = r.randint(0, N-1) if (atoms[index].symbol != type_a): #print "changing atom #"+str(index)+" to "+type_a #if (r.randint(0, 1000) < 500): atoms[index].symbol = type_a n_A += 1 indeces, offsets = nl.get_neighbors(index) for ia in indeces : if (atoms[ia].symbol != type_a)&(n_A < ratio*N): atoms[ia].symbol = type_a n_A += 1 return atoms
def get_localEnv(frame, centerIdx, cutoff, onlyDict=False): ''' Get the local atomic environment around an atom in an atomic frame. :param frame: ase or quippy Atoms object :param centerIdx: int Index of the local environment center. :param cutoff: float Cutoff radius of the local environment. :return: ase Atoms object Local atomic environment. The center atom is in first position. ''' import ase.atoms import quippy.atoms from ase.neighborlist import NeighborList from ase import Atoms as aseAtoms if isinstance(frame, quippy.atoms.Atoms): atoms = qp2ase(frame) elif isinstance(frame, ase.atoms.Atoms): atoms = frame else: raise ValueError n = len(atoms.get_atomic_numbers()) nl = NeighborList([ cutoff / 2., ] * n, skin=0., sorted=False, self_interaction=False, bothways=True) nl.build(atoms) cell = atoms.get_cell() pbc = atoms.get_pbc() pos = atoms.get_positions() positions = [ pos[centerIdx], ] zList = atoms.get_atomic_numbers() numbers = [ zList[centerIdx], ] indices, offsets = nl.get_neighbors(centerIdx) # print offsets,len(atom.get_atomic_numbers()) for i, offset in zip(indices, offsets): positions.append(pos[i] + np.dot(offset, cell)) numbers.append(zList[i]) atomsParam = dict(numbers=numbers, cell=cell, positions=positions, pbc=pbc) if onlyDict: return atomsParam else: return aseAtoms(**atomsParam)
def hop_shuffle(atoms, A, B, count=10, R=3.0): """ Shuffle atoms in given structure by swapping atom types within first coordination shell Parameters ---------- atoms: ase.Atoms ase Atoms object, containing atomic cluster. A, B: string symbols of atoms to swap count: integer number of shuffles R: float radius of coordination shell, were atoms will be swapped Returns ------- Function returns ASE atoms object whith shuffled atoms """ n_atoms = len(atoms) nswaps = 0 neiblist = NeighborList( [ R ] * n_atoms, self_interaction=False, bothways=True ) neiblist.build( atoms ) rnd = random.Random() while nswaps < count: i = rnd.randint(0, n_atoms-1) indeces, offsets = neiblist.get_neighbors( i ) if (atoms[i].symbol == B): candidates = [] for ii in indeces: if atoms[ii].symbol == A: candidates.append( ii ) if len(candidates) > 0: j = random.choice(candidates) atoms[i].symbol = A atoms[j].symbol = B nswaps += 1 neiblist.build( atoms ) elif (atoms[i].symbol == B): candidates = [] for ii in indeces: if atoms[ii].symbol == A: candidates.append( ii ) if len(candidates) > 0: j = random.choice(candidates) atoms[i].symbol = B atoms[j].symbol = A nswaps += 1 neiblist.build( atoms ) return atoms
def load_oqmd_data(num_dist_basis, dtype=float, cutoff=2.0, filter_query=None): """ Returns tuple (Z, D, y, num_species) where Z is a matrix, each row in Z corresponds to a molecule and the elements are atomic numbers D is a 4D tensor giving the Gaussian feature expansion of distances between atoms y is the energy of each molecule in kcal/mol num_species is the integer number of different atomic species in Z """ import ase.db con = ase.db.connect("oqmd_all_entries.db") sel = filter_query # Create sorted list of species all_species = sorted( list( reduce(lambda x, y: x.union(set(y.numbers)), con.select(sel), set()))) # Insert dummy species 0 all_species.insert(0, 0) # Create mapping from species to species index species_map = dict([(x, i) for i, x in enumerate(all_species)]) # Find max number of atoms max_atoms = 0 num_molecules = 0 for row in con.select(sel): num_molecules += 1 atoms = row.toatoms() max_atoms = max(max_atoms, len(atoms)) # Gaussian basis function parameters mu_min = -1 mu_max = 2 * cutoff + 1 step = (mu_max - mu_min) / num_dist_basis sigma = step print "Number of molecules %d, max atoms %d" % (num_molecules, max_atoms) k = np.arange(num_dist_basis) D = np.zeros((num_molecules, max_atoms, max_atoms, num_dist_basis), dtype=dtype) Z = np.zeros((num_molecules, max_atoms), dtype=np.int8) y = np.zeros(num_molecules, dtype=dtype) for i_mol, row in enumerate(con.select(sel)): print "feature expansion %10d/%10d\r" % (i_mol + 1, num_molecules), atoms = row.toatoms() y[i_mol] = row.total_energy Z[i_mol, 0:len(row.numbers)] = map(lambda x: species_map[x], row.numbers) assert np.all(atoms.get_atomic_numbers() == row.numbers) neighborhood = NeighborList([cutoff] * len(atoms), self_interaction=False, bothways=False) neighborhood.build(atoms) for ii in range(len(atoms)): neighbor_indices, offset = neighborhood.get_neighbors(ii) for jj, offs in zip(neighbor_indices, offset): ii_pos = atoms.positions[ii] jj_pos = atoms.positions[jj] + np.dot(offs, atoms.get_cell()) dist = np.linalg.norm(ii_pos - jj_pos) d_expand = np.exp(-((dist - (mu_min + k * step))**2.0) / (2.0 * sigma**2.0)) D[i_mol, ii, jj, :] += d_expand if jj != ii: D[i_mol, jj, ii, :] += d_expand return Z, D, y, len(all_species)
def monoatomic(self, R1=3, calc_energy=False): r"""This routine analyzes atomic structure by the calculation of coordination numbers in cluster with only one type of atom. Parameters ---------- R1: float First coordination shell will icnlude all atoms with distance less then R1 [Angstrom]. Default value is 3. calc_energy: bool Flag used for calculation of potential energy with EMT calculator. The default value is False, so that energy is not calculated. Returns ------- N: int number of atoms in cluster R: float radius of the cluster CN: float average coord number E: float potential energy, -1 if calc_energy is False Ncore: number of atoms in core region (number of atoms with all 12 neighbors) CNshell: average coordination number for surface atoms only Notes ----- The radius of the cluster is roughly determined as maximum the distance from the center to most distant atom in the cluster. Example -------- >>> atoms = FaceCenteredCubic('Ag', [(1, 0, 0), (1, 1, 0), (1, 1, 1)], [7,8,7], 4.09) >>> qsar = QSAR(atoms) >>> qsar.monoatomic(R1=3.0) >>> print "average CN is ", qsar.CN """ self.chems = ['*'] # any element. For now used for report only N = len(self.atoms) nl = NeighborList( [0.5 * R1] * N, self_interaction=False, bothways=True ) nl.build(self.atoms) CN = 0 Ncore = 0 Nshell = 0 CNshell = 0 # average CN of surface atoms for i in xrange(0, N): indeces, offsets = nl.get_neighbors(i) CN += len(indeces) if len(indeces) < 12: Nshell += 1 CNshell += len(indeces) else: Ncore += 1 CN = CN * 1.0 / N CNshell = CNshell * 1.0 / Nshell #atoms.center() R = self.atoms.positions.max() / 2.0 if calc_energy: #from asap3 import EMT from ase.calculators.emt import EMT atoms.set_calculator(EMT()) E = atoms.get_potential_energy() else: E = -1 #return N, R, CN, E, Ncore, CNshell self.N = N #TODO: use array property CNs self.R = R self.CN = CN self.CNs = np.array([[CN]]) self.E = E self.Ncore = Ncore self.CNshell = CNshell
def biatomic(self, A, B, R1=3.0, calc_energy=False): r"""This routine analyzes atomic structure by the calculation of coordination numbers in cluster with atoms of two types (A and B). Parameters ---------- A: string atom type, like 'Ag', 'Pt', etc. B: string atom type, like 'Ag', 'Pt', etc. R1: float First coordination shell will icnlude all atoms with distance less then R1 [Angstrom]. Default value is 3. calc_energy: bool Flag used for calculation of potential energy with EMT calculator. The default value is False, so that energy is not calculated. Returns ------- N: int number of atoms in cluster nA: number of atoms of type A R: float radius of the cluster CN_AA: float average number of atoms A around atom A CN_AB: float average number of atoms A around atom B CN_BB: float average number of atoms B around atom B CN_BA: float average number of atoms B around atom A etha: float parameter of local ordering, -1 < etha < 1. Returns 999 if concentration of one of the component is too low. E: float potential energy NAcore: number of A atoms in core NBcore: number of B atoms in core CNshellAA: average CN of A-A for surface atoms only CNshellAB: average CN of A-B for surface atoms only CNshellBB: average CN of B-B for surface atoms only CNshellBA: average CN of B-A for surface atoms only Notes ----- The radius of the cluster is roughly determined as maximum the distance from the center to most distant atom in the cluster. Example -------- >>> atoms = FaceCenteredCubic('Ag', [(1, 0, 0), (1, 1, 0), (1, 1, 1)], [7,8,7], 4.09) >>> atoms = CoreShellFCC(atoms, 'Pt', 'Ag', 0.6, 4.09) >>> [N, nA, R, CN_AA, CN_AB, CN_BB, CN_BA, etha] = biatomic(atoms, 'Pt', 'Ag') >>> print "Short range order parameter: ", etha """ self.chems = [A, B] # for now used for report only N = len(self.atoms) nA = 0 nB = 0 for element in self.atoms.get_chemical_symbols(): if element == A: nA += 1 elif element == B: nB += 1 else: raise Exception('Extra element ' + element) if (nA + nB != N): raise Exception('Number of A (' + str(nA) + ') ' + 'and B (' + str(nB) + ') artoms mismatch!') nl = NeighborList([0.5 * R1] * N, self_interaction=False, bothways=True) nl.build(self.atoms) # initialize counters: CN_AA = 0 # averaged total coord. numbers CN_AB = 0 CN_BB = 0 CN_BA = 0 NAcore = 0 # number of atoms in core region NBcore = 0 CNshellAA = 0 # average coord. numbers for surface atoms CNshellAB = 0 CNshellBB = 0 CNshellBA = 0 for iatom in xrange(0, N): #print "central atom index:", iatom, " kind: ", self.atoms[iatom].symbol indeces, offsets = nl.get_neighbors(iatom) if self.atoms[iatom].symbol == B: CN_BB_temp = 0 CN_BA_temp = 0 for ii in indeces: #print "neighbor atom index:", ii, " kind: ", self.atoms[ii].symbol if self.atoms[ii].symbol == B: CN_BB_temp += 1 elif self.atoms[ii].symbol == A: CN_BA_temp += 1 else: print("Warning: unknown atom type %s. It will not be counted!"%self.atoms[ii].symbol) CN_BB += CN_BB_temp CN_BA += CN_BA_temp if len(indeces) < 12: # SHELL CNshellBB += CN_BB_temp CNshellBA += CN_BA_temp else: # CORE NBcore += 1 elif self.atoms[iatom].symbol == A: CN_AA_temp = 0 CN_AB_temp = 0 for i in indeces: #print "neighbor atom index:", i, " kind: ", self.atoms[i].symbol if self.atoms[i].symbol == A: CN_AA_temp += 1 elif self.atoms[i].symbol == B: CN_AB_temp += 1 else: print("Warning: unknown atom type %s. It will not be counted!"%self.atoms[i].symbol) CN_AA += CN_AA_temp CN_AB += CN_AB_temp if len(indeces) < 12: # SHELL CNshellAA += CN_AA_temp CNshellAB += CN_AB_temp else: # CORE NAcore += 1 else: #raise Exception("Un") print("Warning: unknown atom type %s. It will not be counted!"%self.atoms[iatom].symbol) # averaging: CN_AA = CN_AA * 1.0 / nA CN_AB = CN_AB * 1.0 / nA CN_BB = CN_BB * 1.0 / nB CN_BA = CN_BA * 1.0 / nB znam = (nA - NAcore) if znam > 0.0001: CNshellAA = CNshellAA * 1.0 / znam CNshellAB = CNshellAB * 1.0 / znam else: CNshellAA = 0 CNshellAB = 0 znam = (nB - NBcore) if znam > 0.0001: CNshellBB = CNshellBB * 1.0 / znam CNshellBA = CNshellBA * 1.0 / znam else: CNshellBB = 0 CNshellBA = 0 # calc concentrations: concB = nB * 1.0 / N znam = concB * (CN_AA + CN_AB) if znam < 0.0001: #print "WARNING! Too low B concentration: ",concB etha = 999 else: etha = 1 - CN_AB / znam R = self.atoms.positions.max() / 2.0 if calc_energy: #from asap3 import EMT from ase.calculators.emt import EMT self.atoms.set_calculator(EMT()) E = self.atoms.get_potential_energy() else: E = -1 #return N, nA, R, CN_AA, CN_AB, CN_BB, CN_BA, etha, E, NAcore, \ # NBcore, CNshellAA, CNshellAB, CNshellBB, CNshellBA self.N = N self.nA = nA self.R = R self.CN_AA = CN_AA #TODO: use only arrays of CNs self.CN_AB = CN_AB self.CN_BB = CN_BB self.CN_BA = CN_BA self.CNs = np.array([ [CN_AA, CN_AB], [CN_BA, CN_BB] ]) self.etha = etha self.E = E self.NAcore = NAcore self.NBcore = NBcore self.CNshellAA = CNshellAA self.CNshellAB = CNshellAB self.CNshellBB = CNshellBB self.CNshellBA = CNshellBA
def CoreShellCN(atoms, type_a, type_b, ratio, R_min = 1.5, CN_max=12, n_depth=-1): r"""This routine generates cluster with ideal core-shell architecture, so that atoms of type_a are placed on the surface and atoms of type_b are forming the core of nanoparticle. The 'surface' of nanoparticle is defined as atoms with unfinished first coordination shell. Used algorithm without need for explicit knowledge of far coordination shells parameters, as it was required in CoreShellFCC(..) Parameters ---------- atoms: ase.Atoms ase Atoms object, containing atomic cluster. type_a: string Symbol of chemical element to be placed on the shell. type_b: string Symbol of chemical element to be placed in the core. ratio: float Guards the number of shell atoms, type_a:type_b = ratio:(1-ratio) R_min: float Typical bond length. Neighboring atoms within this value are counted as coordination numbers. Default is 3.0. CN_max: float Maximum possible coordination number (bulk coordination number). Default is 12. n_depth: int Number of layers of the shell formed by atoms ratio. Default value -1 is ignored and n_depth is calculated according ratio. If n_depth is set then value of ratio is ignored. Returns ------- Function returns ASE atoms object which contains bimetallic core-shell cluster Example -------- >>> atoms = FaceCenteredCubic('Ag', [(1, 0, 0), (1, 1, 0), (1, 1, 1)], [7,8,7], 4.09) >>> atoms = CoreShellCN(atoms, 'Pt', 'Ag', 0.5) >>> view(atoms) """ # 0 < ratio < 1 target_x = ratio if n_depth != -1: target_x = 1 n_atoms = len(atoms) n_a = (np.array(atoms.get_chemical_symbols()) == type_a).sum() #n_b = (atoms.get_chemical_symbols() == type_b).sum() #print n_a n_shell = 0 # depth of the shell while (n_a < n_atoms * target_x): n_shell += 1 print ("shell: ", n_shell) if (n_depth != -1)and(n_shell > n_depth): break neiblist = NeighborList( [ R_min ] * n_atoms, self_interaction=False, bothways=True ) neiblist.build( atoms ) for i in xrange( n_atoms ): indeces, offsets = neiblist.get_neighbors(i) if (atoms[i].symbol == type_b): CN_temp = 0 for ii in indeces: if atoms[ii].symbol == type_b: CN_temp += 1 #print "CN_temp: ", CN_temp if (CN_temp < CN_max): # coord shell is not full, swap type to type_a! atoms[i].tag = n_shell # not swap yet, but mark # swap atom types now. Stop if target ratio achieved for atom in atoms: if (atom.tag > 0)&(atom.symbol == type_b): if n_a < n_atoms * target_x: atom.symbol = type_a n_a += 1 #print "n_A: ", n_a # end while # check number of atoms checkn_a = 0 for element in atoms.get_chemical_symbols(): if element == type_a: checkn_a += 1 #print "Should be equal: ", n_a, checkn_a assert n_a == checkn_a return atoms
def CoreShellFCC(atoms, type_a, type_b, ratio, a_cell, n_depth=-1): r"""This routine generates cluster with ideal core-shell architecture, so that atoms of type_a are placed on the surface and atoms of type_b are forming the core of nanoparticle. The 'surface' of nanoparticle is defined as atoms with unfinished coordination shell. Parameters ---------- atoms: ase.Atoms ase Atoms object, containing atomic cluster. type_a: string Symbol of chemical element to be placed on the shell. type_b: string Symbol of chemical element to be placed in the core. ratio: float Guards the number of shell atoms, type_a:type_b = ratio:(1-ratio) a_cell: float Parameter of FCC cell, in Angstrom. Required for calculation of neighbor distances in for infinite crystal. n_depth: int Number of layers of the shell formed by atoms ratio. Default value -1 is ignored and n_depth is calculated according ratio. If n_depth is set then value of ratio is ignored. Returns ------- Function returns ASE atoms object which contains bimetallic core-shell cluster Notes ----- The criterion of the atom beeing on the surface is incompletnes of it's coordination shell. For the most outer atoms the first coordination shell will be incomplete (coordination number is less then 12 for FCC), for the second layer -- second coordination shell( CN1 + CN2 < 12 + 6) and so on. In this algorithm each layer is tagged by the number ('depth'), take care if used with other routines dealing with tags (add_adsorbate etc). First, atoms with unfinished first shell are replaced by atoms type_a, then second, and so on. The last depth surface layer is replaced by random to maintain given ratio value. Example -------- >>> atoms = FaceCenteredCubic('Ag', [(1, 0, 0), (1, 1, 0), (1, 1, 1)], [7,8,7], 4.09) >>> atoms = CoreShellFCC(atoms, 'Pt', 'Ag', 0.6, 4.09) >>> view(atoms) """ # 0 < ratio < 1 target_x = ratio if n_depth != -1: target_x = 1 # needed to label all needed layeres def fill_by_tag(atoms, chems, tag): """Replaces all atoms within selected layer""" for i in xrange(0, len(atoms)): if atoms[i].tag == tag: chems[i] = type_a return # coord numbers for FCC: coord_nums = [1, 12, 6, 24, 12, 24, 8, 48, 6, 36, 24, 24, 24, 72, 48, 12, 48, 30, 72, 24] # coordination radii obtained from this array as R = sqrt(coord_radii)*a/2 coord_radii = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 30, 32, 34, 36, 38, 40] ## generate FCC cluster ## #atoms = FaceCenteredCubic(type_b, surfaces, layers, a_cell) n_atoms = len(atoms) ## tag layers ## positions = [0] # number of positions in layer n_tag = 0 # number of tags to check if there is enought layers n_shell = 0 # depth of the shell while (n_tag < n_atoms * target_x): n_shell += 1 if (n_depth != -1)and(n_shell > n_depth): break neiblist = NeighborList( [ a_cell / 2.0 * sqrt(coord_radii[n_shell]) / 2.0 + 0.0001 ] * n_atoms, self_interaction=False, bothways=True ) neiblist.build(atoms) for i in xrange(0, n_atoms): indeces, offsets = neiblist.get_neighbors(i) if (atoms[i].tag == 0): if (len(indeces) < sum(coord_nums[1:n_shell + 1])): # coord shell is not full -> atom is on surface! atoms[i].tag = n_shell n_tag += 1 # save the count of positions at each layer: positions.append(n_tag - sum(positions[0:n_shell])) ## populate layers ## chems = atoms.get_chemical_symbols() n_type_a = 0 # number of changes B -> A if (n_tag < n_atoms * target_x)and(n_depth == -1): # raise exception? return None else: n_filled = n_shell - 1 # number of totally filled layers ilayer = 1 while (ilayer < n_filled + 1): fill_by_tag(atoms, chems, ilayer) n_type_a += positions[ilayer] ilayer += 1 while (n_type_a < n_atoms * target_x)and(n_depth == -1): i = random.randint(0, n_atoms - 1) if (atoms[i].tag == n_shell): if (chems[i] == type_b): chems[i] = type_a n_type_a += 1 atoms.set_chemical_symbols(chems) ## check number of atoms ## checkn_a = 0 for element in chems: if element == type_a: checkn_a += 1 assert n_type_a == checkn_a return atoms