def add_hydrogens(atoms): cov_radii = [covalent_radii[a.number] for a in atoms] nl = NeighborList(cov_radii, bothways = True, self_interaction = False) nl.update(atoms) need_a_H = [] for a in atoms: nlist=nl.get_neighbors(a.index)[0] if len(nlist)<3: if a.symbol=='C': need_a_H.append(a.index) print("Added missing Hydrogen atoms: ", need_a_H) dCH=1.1 for a in need_a_H: vec = np.zeros(3) indices, offsets = nl.get_neighbors(atoms[a].index) for i, offset in zip(indices, offsets): vec += -atoms[a].position +(atoms.positions[i] + np.dot(offset, atoms.get_cell())) vec = -vec/np.linalg.norm(vec)*dCH vec += atoms[a].position htoadd = ase.Atom('H',vec) atoms.append(htoadd)
def add_cap_ox(clust): # TODO: fix bug where adds multiple oxygen's to the same place """ Args: clust: """ nl = NeighborList(natural_cutoffs(clust), bothways=True, self_interaction=False) nl.update(clust) new_clust = clust cap_inds = get_cap_si(clust) for ind in cap_inds: while len(nl.get_neighbors(ind)[0]) < 4: neighb = nl.get_neighbors(ind)[0][ -1] # last index in the list of neighbor indicies direction = clust.get_positions()[ind] - clust.get_positions()[ neighb] # vector pointing from neighbor to Si ox_pos = clust.get_positions( )[ind] + 1.6 * direction / np.linalg.norm(direction) new_ox = Atom('O', position=ox_pos) new_clust.append(new_ox) nl = NeighborList(natural_cutoffs(clust), bothways=True, self_interaction=False) nl.update(clust) return (new_clust)
def test_fcc(): x = bulk('X', 'fcc', a=2**0.5) nl = NeighborList([0.5], skin=0.01, bothways=True, self_interaction=False) nl.update(x) assert len(nl.get_neighbors(0)[0]) == 12 nl = NeighborList([0.5] * 27, skin=0.01, bothways=True, self_interaction=False) nl.update(x * (3, 3, 3)) for a in range(27): assert len(nl.get_neighbors(a)[0]) == 12 assert not np.any(nl.get_neighbors(13)[1]) c = 0.0058 for NeighborListClass in [PrimitiveNeighborList, NewPrimitiveNeighborList]: nl = NeighborListClass([c, c], skin=0.0, sorted=True, self_interaction=False, use_scaled_positions=True) nl.update([True, True, True], np.eye(3) * 7.56, np.array([[0, 0, 0], [0, 0, 0.99875]])) n0, d0 = nl.get_neighbors(0) n1, d1 = nl.get_neighbors(1) # != is xor assert (np.all(n0 == [0]) and np.all(d0 == [0, 0, 1])) != \ (np.all(n1 == [1]) and np.all(d1 == [0, 0, -1]))
def test_H2(): h2 = Atoms('H2', positions=[(0, 0, 0), (0, 0, 1)]) nl = NeighborList([0.5, 0.5], skin=0.1, sorted=True, self_interaction=False) nl2 = NeighborList([0.5, 0.5], skin=0.1, sorted=True, self_interaction=False, primitive=NewPrimitiveNeighborList) assert nl2.update(h2) assert nl.update(h2) assert not nl.update(h2) assert (nl.get_neighbors(0)[0] == [1]).all() m = np.zeros((2, 2)) m[0, 1] = 1 assert np.array_equal(nl.get_connectivity_matrix(sparse=False), m) assert np.array_equal(nl.get_connectivity_matrix(sparse=True).todense(), m) assert np.array_equal(nl.get_connectivity_matrix().todense(), nl2.get_connectivity_matrix().todense()) h2[1].z += 0.09 assert not nl.update(h2) assert (nl.get_neighbors(0)[0] == [1]).all() h2[1].z += 0.09 assert nl.update(h2) assert (nl.get_neighbors(0)[0] == []).all() assert nl.nupdates == 2
def test_small_cell_and_large_cutoff(): # See: https://gitlab.com/ase/ase/-/issues/441 cutoff = 50 atoms = bulk('Cu', 'fcc', a=3.6) atoms *= (2, 2, 2) atoms.set_pbc(False) radii = cutoff * np.ones(len(atoms.get_atomic_numbers())) neighborhood_new = NeighborList(radii, skin=0.0, self_interaction=False, bothways=True, primitive=NewPrimitiveNeighborList) neighborhood_old = NeighborList(radii, skin=0.0, self_interaction=False, bothways=True, primitive=PrimitiveNeighborList) neighborhood_new.update(atoms) neighborhood_old.update(atoms) n0, d0 = neighborhood_new.get_neighbors(0) n1, d1 = neighborhood_old.get_neighbors(0) assert np.all(n0 == n1) assert np.all(d0 == d1)
def find_tmpo(atoms): """ Args: atoms: """ tmpo_indices = [] p_index = None for a in atoms: if a.symbol == 'P': p_index = a.index break tmpo_indices.append(p_index) if p_index is None: return tmpo_indices nl = NeighborList(natural_cutoffs(atoms), bothways=True, self_interaction=False) nl.update(atoms) p_nl = nl.get_neighbors(p_index)[0] tmpo_indices.extend(p_nl) for i in p_nl: if atoms[i].symbol == 'C': tmpo_indices.extend(nl.get_neighbors(i)[0]) return tmpo_indices
def generate_site_type(atoms, surface_mask, normals, coordination, unallowed_elements=[]): cutoffs = natural_cutoffs(atoms) nl = NeighborList(cutoffs, self_interaction=False, bothways=True) nl.update(atoms) surface_mask = [ index for index in surface_mask if atoms[index].symbol not in unallowed_elements ] possible = list(combinations(set(surface_mask), coordination)) valid = [] sites = [] for cycle in possible: for start, end in combinations(cycle, 2): if end not in nl.get_neighbors(start)[0]: break else: # All were valid valid.append(list(cycle)) for cycle in valid: tracked = np.array(atoms[cycle[0]].position, dtype=float) known = np.zeros(shape=(coordination, 3), dtype=float) known[0] = tracked for index, (start, end) in enumerate(zip(cycle[:-1], cycle[1:])): for neighbor, offset in zip(*nl.get_neighbors(start)): if neighbor == end: tracked += relative_position( atoms, neighbor, offset) - atoms[start].position known[index + 1] = tracked average = np.average(known, axis=0) normal = np.zeros(3) for index in cycle: neighbors = len(nl.get_neighbors(index)[0])**2 normal += normals[index] * neighbors normal = normalize(normal) if coord == 2: average[2] = average[2] - 0.5 if coord == 3: average[2] = average[2] - 0.7 #print(average) #print(average[2]) site_ads = Site(cycle=cycle, position=average, normal=normal) sites.append(site_ads) return sites
def test_neighborlist_initialization(): atoms = bulk('Al', 'fcc', a=4) nl = NeighborList([8] * len(atoms), skin=0, self_interaction=False) with pytest.raises(Exception, match="Must call update"): nl.get_neighbors(0) with pytest.raises(Exception, match="Must call update"): nl.get_connectivity_matrix() nl.update(atoms) indices, offsets = nl.get_neighbors(0)
def find_positions(atoms, n_site=0): nO = 10 nl = NeighborList([3.3 / 2] * len(atoms), self_interaction=False, bothways=True) nl.update(atoms) com = atoms.get_center_of_mass() hollow = {} n_hollow = 0 bridge = {} n_bridge = 0 top = {} n_top = 0 sites = {} for i in range(len(atoms)): i_indices, i_offsets = nl.get_neighbors(i) for j in i_indices: #if i < j: # bpos = (atoms.positions[i]+atoms.positions[j]) / 2.0 # sites[n_site] = ['bridge',bpos, bpos-com] # n_site += 1 j_indices, j_offsets = nl.get_neighbors(j) c = common_member(i_indices, j_indices) if c is not None: for k in c: k_indices, k_offsets = nl.get_neighbors(k) if len(i_indices) < 10 and len(j_indices) < 10 and len( k_indices) < 10 and i < j and j < k: #n=n+1 pos = (atoms.positions[i] + atoms.positions[j] + atoms.positions[k]) / 3. v1 = atoms.positions[j] - atoms.positions[i] v2 = atoms.positions[k] - atoms.positions[i] vn = np.cross(v1, v2) vn = vn / np.linalg.norm(vn) vo = pos - com d = np.dot(vo, vn) sites[n_site] = ['hollow', pos, vn, d] n_site += 1 temp = copy.deepcopy(sites) """ for i in range(n_site-1): for j in range(i+1,n_site): dist =np.linalg.norm(sites[i][1] - sites[j][1]) if dist<0.5: print 'duplicated sites:', i, j, dist del temp[j] """ return temp, n_site
def is_connected(reference, indices, cutoff=graphene_cutoff): """Return True if indices in atoms are connected. Args: reference (Atoms or NeighborList): Structure containing atoms for test. The NeighborList must be constructed with bothways=True. indices (List[int]): Indices of the possibly connected atoms. cutoff (int): Radius defining neighbors in a NeighborList. Only relevent when reference is of the Atoms type. """ if isinstance(reference, Atoms): nblist = NeighborList([cutoff for i in range(len(reference))], bothways=True, self_interaction=False) nblist.update(reference) else: nblist = reference if len(indices) == 0: return True connected = [indices[0]] for c in connected: neighbs = nblist.get_neighbors(c)[0] for n in neighbs: if n in indices and n not in connected: connected.append(n) return set(indices) == set(connected)
def get_all_Z_TM(self, d_Z_TM, TM_type): """ :param d_Z_TM: Z-TM distance with Z being the T sites on the zeolite framework and TM being extraframework atom to be inserted :return: a dictionary of structures for each T site name """ dict_Z_TM = {} for site_name, all_zeo_with_same_T in self.dict_1Al_replaced.items(): atoms = copy.copy(all_zeo_with_same_T[0]) nl = NeighborList(natural_cutoffs(atoms), bothways=True, self_interaction=False) nl.update(atoms) index_Al = [a.index for a in atoms if a.symbol == 'Al'] indices, offsets = nl.get_neighbors(index_Al[0]) assert len(indices) == 4 traj = [] pairs = list(itertools.combinations(indices, 2)) for i, pair in enumerate(pairs): atoms = copy.copy(all_zeo_with_same_T[0]) v = self._get_direction_of_insertion(atoms, index_Al[0], pair[0], pair[1], tag='TM') atoms = atoms + Atoms( TM_type, positions=[atoms[index_Al[0]].position] + v * d_Z_TM) traj.append(atoms) dict_Z_TM[site_name] = traj return dict_Z_TM
def get_Z_TM(self, atoms, d_Z_TM, TM_type): # todo: improve flexibility to allow Z-TM-H or Z-TM-OH insertions while avoid overlapping nl = NeighborList(natural_cutoffs(atoms), bothways=True, self_interaction=False) nl.update(atoms) index_Al = [a.index for a in atoms if a.symbol == 'Al'] indices, offsets = nl.get_neighbors(index_Al[0]) indices = [val for val in indices if atoms[val].symbol == 'O'] assert len(indices) == 4 dict_Z_TM = {} original_atoms = copy.copy(atoms) pairs = list(itertools.combinations(indices, 2)) for i, pair in enumerate(pairs): atoms = copy.copy(original_atoms) v = self._get_direction_of_insertion(atoms, index_Al[0], pair[0], pair[1], tag='TM') atoms = atoms + Atoms( TM_type, positions=[atoms[index_Al[0]].position] + v * d_Z_TM) atoms.wrap() key_tag = 'O' + str(pair[0]) + '_O' + str(pair[1]) dict_Z_TM[key_tag] = atoms return dict_Z_TM
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 test0(self): import warnings warnings.filterwarnings('ignore') a = 3.6 Rc = 5 atoms = bulk('Cu', 'bcc', a=a).repeat((1, 1, 1)) atoms.rattle(0.02) nl = NeighborList([Rc] * len(atoms), skin=0.0, self_interaction=False, bothways=False) nl.update(atoms) inds, dists, N = get_neighbors_oneway(atoms.positions, atoms.cell, 2 * Rc, skin=0.0) with self.test_session() as sess: inds, dists, N = sess.run([inds, dists, N]) for i in range(len(atoms)): ase_inds, ase_offs = nl.get_neighbors(i) these_inds = np.array([x[1] for x in inds if x[0] == i]) these_offs = N[np.where(inds[:, 0] == i)] self.assertAllClose(ase_inds, these_inds) self.assertAllClose(ase_offs, these_offs)
def test_structure_repeats(self): 'Check several structures and repeats for consistency with ase.' for repeat in ((1, 1, 1), (2, 1, 1), (1, 2, 1), (1, 1, 2), (1, 2, 3), (4, 1, 1)): for structure in ('fcc', 'bcc', 'sc', 'hcp', 'diamond'): a = 3.6 # Float tolerances are tricky. The 0.01 in the next line is important. # This test fails without it due to subtle differences in computed # positions. Rc = 2 * a + 0.01 atoms = bulk('Cu', structure, a=a).repeat(repeat) nl = NeighborList([Rc] * len(atoms), skin=0.0, self_interaction=False, bothways=True) nl.update(atoms) distances = get_distances({'cutoff_radius': 2 * Rc}, atoms.positions, atoms.cell, np.ones((len(atoms), 1))) mask = (distances <= 2 * Rc) & (distances > 0) tf_nneighbors = tf.reduce_sum(tf.cast(mask, tf.int32), axis=[1, 2]) with self.test_session(): for i, atom in enumerate(atoms): inds, disps = nl.get_neighbors(i) ase_nneighbors = len(inds) self.assertEqual(ase_nneighbors, tf_nneighbors.eval()[i]) # These are the indices of each neighbor in the atom list. tf_inds = tf.where(mask[i])[:, 0].eval() self.assertCountEqual(inds, tf_inds)
def test_atom_types(self): """Tests if the neighbor indices agree with ase. This is important to find the chemical element associated with a specific neighbor. """ a = 3.6 Rc = a / np.sqrt(2) / 2 + 0.01 atoms = bulk('Cu', 'fcc', a=a).repeat((3, 1, 1)) atoms[1].symbol = 'Au' nl = NeighborList([Rc] * len(atoms), skin=0.01, self_interaction=False, bothways=True) nl.update(atoms) nns = [nl.get_neighbors(i) for i in range(len(atoms))] ase_nau = [np.sum(atoms.numbers[inds] == 79) for inds, offs in nns] au_mask = tf.convert_to_tensor(atoms.numbers == 79, tf.int32) distances = get_distances({'cutoff_radius': 2 * Rc}, atoms.positions, atoms.cell) mask = (distances <= (2 * Rc)) & (distances > 0) nau = tf.reduce_sum(tf.cast(mask, tf.int32) * au_mask[:, None], [1, 2]) with self.test_session(): self.assertTrue(np.all(ase_nau == nau.eval()))
def main(): arg = sys.argv atoms = read(filename=arg[1], format='xyz') cutoff = [] coord_n = [] n_pt = 0 n_au = 0 for i in range(len(atoms)): cutoff.append(float(arg[2])) nl = NeighborList(cutoff, skin=0, bothways=True, self_interaction=False) nl.update(atoms) for i in range(len(atoms)): indices, offsets = nl.get_neighbors(i) coord_n.append([i, len(indices)]) #coord_n[i]=len(indices) #coord_n.append(len(indices)) if len(indices) <= 9: if atoms[i].symbol == 'Pt': n_pt += 1 if atoms[i].symbol == 'Au': n_au += 1 atoms[i].symbol = 'Cu' if len(indices) == 10: atoms[i].symbol = 'Pd' write('new.xyz', images=atoms, format='xyz') print float(n_pt) / float(n_pt + n_au), float(n_au) / float(n_pt + n_au)
def get_bondpairs(atoms, radius=1.1): """Get all pairs of bonding atoms Return all pairs of atoms which are closer than radius times the sum of their respective covalent radii. The pairs are returned as tuples:: (a, b, (i1, i2, i3)) so that atoms a bonds to atom b displaced by the vector:: _ _ _ i c + i c + i c , 1 1 2 2 3 3 where c1, c2 and c3 are the unit cell vectors and i1, i2, i3 are integers.""" from ase.data import covalent_radii from ase.neighborlist import NeighborList cutoffs = radius * covalent_radii[atoms.numbers] nl = NeighborList(cutoffs=cutoffs, self_interaction=False) nl.update(atoms) bondpairs = [] for a in range(len(atoms)): indices, offsets = nl.get_neighbors(a) bondpairs.extend([(a, a2, offset) for a2, offset in zip(indices, offsets)]) return bondpairs
def test0(self): """check one-way neighborlist for fcc on different repeats.""" a = 3.6 for rep in ((1, 1, 1), (2, 1, 1), (1, 2, 1), (1, 1, 2), (1, 2, 2), (2, 1, 1), (2, 2, 1), (2, 2, 2), (1, 2, 3), (4, 1, 1)): for cutoff_radius in np.linspace(a / 2.1, 5 * a, 5): atoms = bulk('Cu', 'fcc', a=a).repeat(rep) # It is important to rattle the atoms off the lattice points. # Otherwise, float tolerances makes it hard to count correctly. atoms.rattle(0.02) nl = NeighborList( [cutoff_radius / 2] * len(atoms), skin=0.0, self_interaction=False, bothways=False) nl.update(atoms) neighbors, displacements = get_neighbors_oneway( atoms.positions, atoms.cell, cutoff_radius, skin=0.0) for i in range(len(atoms)): an, ad = nl.get_neighbors(i) # Check the same number of neighbors self.assertEqual(len(neighbors[i]), len(an)) # Check the same indices self.assertCountEqual(neighbors[i], an)
def main(): imgs = read('train.traj', index='0::10', format='traj') natoms = len(imgs[0]) cutoff = 3.3 mindist = 2.7 traj = Trajectory('traj.traj', 'w') for atoms in imgs: nl = NeighborList([cutoff / 2] * len(atoms), self_interaction=False, bothways=False) nl.update(atoms) neighbor_info = {} n_pairs = 0 for i in range(natoms): i_indices, i_offsets = nl.get_neighbors(i) neighbor_info[i] = i_indices n_pairs += len(i_indices) print len(i_indices) #randomvalues=np.random.random((n_pairs,)) + mindist randomvalues = np.random.normal(mindist, 0.5, (n_pairs, )) pullcloser = {} pointer = 0 for key in neighbor_info.keys(): pullcloser[key] = randomvalues[pointer:len(neighbor_info[key]) + pointer:1] pointer += len(neighbor_info[key]) atoms.set_positions( pull_closer(atoms.get_positions(), neighbor_info, pullcloser)) #write('CONTCAR',atoms,format='vasp') traj.write(atoms)
def compute_bonds(atoms, atom_radii, scale_radii=1.5): """Compute bonds for atoms.""" from ase.neighborlist import NeighborList nl = NeighborList(atom_radii * scale_radii, skin=0, self_interaction=False) nl.update(atoms) nbonds = nl.nneighbors + nl.npbcneighbors bonds = np.empty((nbonds, 5), int) if nbonds == 0: return bonds n1 = 0 for a in range(len(atoms)): indices, offsets = nl.get_neighbors(a) n2 = n1 + len(indices) bonds[n1:n2, 0] = a bonds[n1:n2, 1] = indices bonds[n1:n2, 2:] = offsets n1 = n2 i = bonds[:n2, 2:].any(1) pbc_bonds = bonds[:n2][i] bonds[n2:, 0] = pbc_bonds[:, 1] bonds[n2:, 1] = pbc_bonds[:, 0] bonds[n2:, 2:] = -pbc_bonds[:, 2:] return bonds
def ase_neighborlist(atoms, cutoffs=None): """Make dict of neighboring atoms using ase function. This provides a wrapper for the ASE neighborlist generator. Currently default values are used. Parameters ---------- atoms : object Target ase atoms object on which to get neighbor list. cutoffs : list A list of radii for each atom in atoms. rtol : float The tolerance factor to allow for small variation in the cutoff radii. Returns ------- neighborlist : dict A dictionary containing the atom index and each neighbor index. """ if cutoffs is None: cutoffs = [get_radius(a.number) for a in atoms] nl = NeighborList( cutoffs, skin=0., sorted=False, self_interaction=False, bothways=True) nl.update(atoms) neighborlist = {} for i, _ in enumerate(atoms): neighborlist[i] = sorted(list(map(int, nl.get_neighbors(i)[0]))) return neighborlist
def relax(self, individual): """Relaxes the individual using a hard-sphere cutoff method. Args: individual (Individual): the individual to relax """ rank = gparameters.mpi.rank print("Relaxing individual {} on rank {} with hard-sphere cutoff method".format(individual.id, rank)) radii = [2.0 for atom in individual] nl = NeighborList(radii, bothways=True, self_interaction=False) nl.update(individual) ntries = 0 modified = True while modified and ntries < 100: modified = False for atom in individual: indices, offsets = nl.get_neighbors(atom.index) for neigh in indices: if individual.get_distance(atom.index, neigh) < self.cutoff: individual.set_distance(atom.index, neigh, self.cutoff, fix=0.5) modified = True nl.update(individual) individual.wrap() ntries += 1 if ntries == 100: print("WARNING! Iterated through the hard-sphere cutoff relaxation 100 times and it still did not converge!")
def get_bondpairs(atoms, radius=1.1): """Get all pairs of bonding atoms Return all pairs of atoms which are closer than radius times the sum of their respective covalent radii. The pairs are returned as tuples:: (a, b, (i1, i2, i3)) so that atoms a bonds to atom b displaced by the vector:: _ _ _ i c + i c + i c , 1 1 2 2 3 3 where c1, c2 and c3 are the unit cell vectors and i1, i2, i3 are integers.""" from ase.data import covalent_radii from ase.neighborlist import NeighborList cutoffs = radius * covalent_radii[atoms.numbers] nl = NeighborList(cutoffs=cutoffs, self_interaction=False) nl.update(atoms) bondpairs = [] for a in range(len(atoms)): indices, offsets = nl.get_neighbors(a) bondpairs.extend([(a, a2, offset) for a2, offset in zip(indices, offsets)]) return bondpairs
def all_connected_to(id_atom, atoms, exclude): cov_radii = [covalent_radii[a.number] for a in atoms] atoms.set_pbc([False, False, False]) nl_no_pbc = NeighborList(cov_radii, bothways=True, self_interaction=False) nl_no_pbc.update(atoms) atoms.set_pbc([True, True, True]) tofollow = [] followed = [] isconnected = [] tofollow.append(id_atom) isconnected.append(id_atom) while len(tofollow) > 0: indices, offsets = nl_no_pbc.get_neighbors(tofollow[0]) indices = list(indices) followed.append(tofollow[0]) for i in indices: if (i not in isconnected) and (atoms[i].symbol not in exclude): tofollow.append(i) isconnected.append(i) for i in followed: if i in tofollow: ### do not remove this check tofollow.remove(i) #try: # tofollow.remove(i) #except: # pass # return isconnected
def bind(self, frame): if not self.ui.get_widget('/MenuBar/ViewMenu/ShowBonds').get_active(): self.bonds = np.empty((0, 5), int) return from ase.atoms import Atoms from ase.neighborlist import NeighborList nl = NeighborList(self.images.r * 1.5, skin=0, self_interaction=False) nl.update( Atoms(positions=self.images.P[frame], cell=(self.images.repeat[:, np.newaxis] * self.images.A[frame]), pbc=self.images.pbc)) nb = nl.nneighbors + nl.npbcneighbors self.bonds = np.empty((nb, 5), int) self.coordination = np.zeros((self.images.natoms), dtype=int) if nb == 0: return n1 = 0 for a in range(self.images.natoms): indices, offsets = nl.get_neighbors(a) self.coordination[a] += len(indices) for a2 in indices: self.coordination[a2] += 1 n2 = n1 + len(indices) self.bonds[n1:n2, 0] = a self.bonds[n1:n2, 1] = indices self.bonds[n1:n2, 2:] = offsets n1 = n2 i = self.bonds[:n2, 2:].any(1) self.bonds[n2:, 0] = self.bonds[i, 1] self.bonds[n2:, 1] = self.bonds[i, 0] self.bonds[n2:, 2:] = -self.bonds[i, 2:]
def bind(self, frame): if not self.ui.get_widget('/MenuBar/ViewMenu/ShowBonds' ).get_active(): self.bonds = np.empty((0, 5), int) return from ase.atoms import Atoms from ase.neighborlist import NeighborList nl = NeighborList(self.images.r * 1.5, skin=0, self_interaction=False) nl.update(Atoms(positions=self.images.P[frame], cell=(self.images.repeat[:, np.newaxis] * self.images.A[frame]), pbc=self.images.pbc)) nb = nl.nneighbors + nl.npbcneighbors self.bonds = np.empty((nb, 5), int) self.coordination = np.zeros((self.images.natoms), dtype=int) if nb == 0: return n1 = 0 for a in range(self.images.natoms): indices, offsets = nl.get_neighbors(a) self.coordination[a] += len(indices) for a2 in indices: self.coordination[a2] += 1 n2 = n1 + len(indices) self.bonds[n1:n2, 0] = a self.bonds[n1:n2, 1] = indices self.bonds[n1:n2, 2:] = offsets n1 = n2 i = self.bonds[:n2, 2:].any(1) self.bonds[n2:, 0] = self.bonds[i, 1] self.bonds[n2:, 1] = self.bonds[i, 0] self.bonds[n2:, 2:] = -self.bonds[i, 2:]
def get_bonds(atoms, covalent_radii): from ase.neighborlist import NeighborList nl = NeighborList(covalent_radii * 1.5, skin=0, self_interaction=False) nl.update(atoms) nbonds = nl.nneighbors + nl.npbcneighbors bonds = np.empty((nbonds, 5), int) if nbonds == 0: return bonds n1 = 0 for a in range(len(atoms)): indices, offsets = nl.get_neighbors(a) n2 = n1 + len(indices) bonds[n1:n2, 0] = a bonds[n1:n2, 1] = indices bonds[n1:n2, 2:] = offsets n1 = n2 i = bonds[:n2, 2:].any(1) pbcbonds = bonds[:n2][i] bonds[n2:, 0] = pbcbonds[:, 1] bonds[n2:, 1] = pbcbonds[:, 0] bonds[n2:, 2:] = -pbcbonds[:, 2:] return bonds
def generate_normals(atoms, surface_normal=0.5, normalize_final=True, adsorbate_atoms=[]): normals = np.zeros(shape=(len(atoms), 3), dtype=float) atoms = atoms.copy() del atoms[adsorbate_atoms] cutoffs = natural_cutoffs(atoms) nl = NeighborList(cutoffs, self_interaction=False, bothways=True) nl.update(atoms) cell = atoms.get_cell() for index, atom in enumerate(atoms): normal = np.array([0, 0, 0], dtype=float) for neighbor, offset in zip(*nl.get_neighbors(index)): direction = atom.position - relative_position( atoms, neighbor, offset) normal += direction if norm(normal) > surface_normal: normals[index, :] = normalize( normal) if normalize_final else normal surface_mask = [ index for index in range(len(atoms)) if norm(normals[index]) > 1e-5 ] return normals, surface_mask
def find_layers(atoms): cutoff = 3.2 nlayer = 0 layers = {} while True: if len(atoms) == 0: break nl=NeighborList([cutoff/2]*len(atoms), self_interaction=False, bothways=True) nl.update(atoms) com=atoms.get_center_of_mass() core = [] for i in range(len(atoms)): #first_layer (most outer layer) i_indices, i_offsets = nl.get_neighbors(i) if i==13 or i==15 or i==3: print i, len(i_indices) if len(i_indices) < 10: if nlayer not in layers.keys(): layers[nlayer] = [i] else: layers[nlayer].append(i) else: core.append(i) #layers[nlayer].sort(reverse=True) shell = atoms.copy() del shell[core] del atoms[layers[nlayer]] write('shell_'+str(nlayer)+'.xyz',shell,format='xyz') write('core_'+str(nlayer)+'.xyz',atoms,format='xyz') layers[nlayer].append(shell.copy()) print nlayer print " ",layers[nlayer] nlayer += 1 return layers
def main(): args = sys.argv imgs = read(args[1], index="::40") #layers = find_layers(atoms.copy()) traj = Trajectory('traj.traj','w') H_indices = random.sample([a.index for a in imgs[0] if a.symbol == 'H'],8) n_img = 0 for atoms in imgs: nl=NeighborList([2.5/2]*len(atoms), self_interaction=False, bothways=True) nl.update(atoms) pair_selected = [] for H_index in H_indices: nl_indices, nl_offsets = nl.get_neighbors(H_index) pair_selected.append([H_index, random.sample(nl_indices, 1)[0]]) for HPd_dist in [1.0, 1.1, 1.2, 1.3, 1.4]: img = atoms.copy() for pair in pair_selected: H_index = pair[0] Pd_selected = pair[1] v = atoms[H_index].position - atoms[Pd_selected].position vn = v/np.linalg.norm(v) del img[H_index] img.append(Atom('H',atoms[Pd_selected].position + vn * HPd_dist)) traj.write(img) print n_img n_img+=1
def get_donor_vec(self, donor_index): """finds direction of lone pair electrons on an adsorbate donor atom :return: vector direction of donor atoms Args: donor_index: """ nl = NeighborList(natural_cutoffs(self), self_interaction=False, bothways=True) nl.update(self) # gets neighbors of donor atom and adds the vectors from neighbor to donor # for most donor atoms this is roughly in the proper binding direction donor_neighbors = nl.get_neighbors(donor_index)[0] # neighbor's index donor_vec = np.array([0, 0, 0]) for i in donor_neighbors: a = self.get_distance(i, donor_index, vector=True) donor_vec = donor_vec + a if np.linalg.norm(donor_vec) == 0: warnings.warn( "donor vector with magnitude 0 found, providing default vector" ) return np.array([1, 0, 0]) donor_vec = donor_vec / np.linalg.norm( donor_vec) # normalizes donor vec return donor_vec
def get_bonds(atoms, covalent_radii): from ase.neighborlist import NeighborList nl = NeighborList(covalent_radii * 1.5, skin=0, self_interaction=False) nl.update(atoms) nbonds = nl.nneighbors + nl.npbcneighbors bonds = np.empty((nbonds, 5), int) if nbonds == 0: return bonds n1 = 0 for a in range(len(atoms)): indices, offsets = nl.get_neighbors(a) n2 = n1 + len(indices) bonds[n1:n2, 0] = a bonds[n1:n2, 1] = indices bonds[n1:n2, 2:] = offsets n1 = n2 i = bonds[:n2, 2:].any(1) pbcbonds = bonds[:n2][i] bonds[n2:, 0] = pbcbonds[:, 1] bonds[n2:, 1] = pbcbonds[:, 0] bonds[n2:, 2:] = -pbcbonds[:, 2:] return bonds
def get_external_internal(atoms, symbols=None): # Atoms to include in external and internal indices if symbols is None: symbols = list(set(atoms.symbols)) try: symbols.pop(symbols.index('H')) except ValueError as e: print(e) # symbols = list(symbols) # Define list of neighbors cov_radii = [covalent_radii[a.number] for a in atoms] nl = NeighborList(cov_radii, bothways=True, self_interaction=False) nl.update(atoms) external_i = [] internal_i = [] for a in atoms: if a.symbol not in symbols: continue nlist = nl.get_neighbors(a.index)[0] n_is_H = [True if atoms[n0].symbol == 'H' else False for n0 in nlist] if any(n_is_H): external_i.append(a.index) else: internal_i.append(a.index) return external_i, internal_i
def get_color_scalars(self, frame=None): if self.colormode == 'tag': return self.atoms.get_tags() if self.colormode == 'force': f = (self.get_forces()**2).sum(1)**0.5 return f * self.images.get_dynamic(self.atoms) elif self.colormode == 'velocity': return (self.atoms.get_velocities()**2).sum(1)**0.5 elif self.colormode == 'initial charge': return self.atoms.get_initial_charges() elif self.colormode == 'magmom': return get_magmoms(self.atoms) elif self.colormode == 'neighbors': from ase.neighborlist import NeighborList n = len(self.atoms) nl = NeighborList(self.get_covalent_radii(self.atoms) * 1.5, skin=0, self_interaction=False, bothways=True) nl.update(self.atoms) return [len(nl.get_neighbors(i)[0]) for i in range(n)] else: scalars = np.array(self.atoms.get_array(self.colormode), dtype=float) return np.ma.array(scalars, mask=np.isnan(scalars))
def exafs_first_shell(S02, energy_shift, absorber, ignore_elements, edge, neighbor_cutoff, trajectory): feff_options = { 'RMAX':str(neighbor_cutoff), 'HOLE':'%i %.4f' % (feff_edge_number(edge), S02), 'CORRECTIONS':'%.4f %.4f' % (energy_shift, 0.0), } #get the bulk reference state path = exafs_reference_path(absorber, feff_options) k = None chi_total = None counter = -1 interactions = 0 nl = None for step, atoms in enumerate(trajectory): if COMM_WORLD.rank == 0: time_stamp = strftime("%F %T") print '[%s] step %i/%i' % (time_stamp, step+1, len(trajectory)) atoms = atoms.copy() if ignore_elements: ignore_indicies = [atom.index for atom in atoms if atom.symbol in ignore_elements] del atoms[ignore_indicies] if nl is None: nl = NeighborList(len(atoms)*[neighbor_cutoff], skin=0.3, self_interaction=False) nl.update(atoms) for i in xrange(len(atoms)): if atoms[i].symbol != absorber: continue indicies, offsets = nl.get_neighbors(i) for j, offset in zip(indicies, offsets): counter += 1 if counter % COMM_WORLD.size != COMM_WORLD.rank: continue r = atoms.get_distance(i,j,True) if r >= neighbor_cutoff: continue interactions += 1 k, chi = chi_path(path, r, 0.0, energy_shift, S02, 1) if chi_total is not None: chi_total += chi else: chi_total = chi chi_total = COMM_WORLD.allreduce(chi_total) chi_total /= atoms.get_chemical_symbols().count(absorber) chi_total /= len(trajectory) chi_total *= 2 return k, chi_total
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
nl2 = NeighborList(atoms2.numbers * 0.2 + 0.5, skin=0.0, sorted=sorted) nl2.update(atoms2) d2, c2 = count(nl2, atoms2) c2.shape = (-1, 10) dd = d * (p1 + 1) * (p2 + 1) * (p3 + 1) - d2 # print(dd) # print(c2 - c) assert abs(dd) < 1e-10 assert not (c2 - c).any() h2 = Atoms('H2', positions=[(0, 0, 0), (0, 0, 1)]) nl = NeighborList([0.5, 0.5], skin=0.1, sorted=True, self_interaction=False) assert nl.update(h2) assert not nl.update(h2) assert (nl.get_neighbors(0)[0] == [1]).all() h2[1].z += 0.09 assert not nl.update(h2) assert (nl.get_neighbors(0)[0] == [1]).all() h2[1].z += 0.09 assert nl.update(h2) assert (nl.get_neighbors(0)[0] == []).all() assert nl.nupdates == 2 h2 = Atoms('H2', positions=[(0, 0, 0), (0, 0, 1)]) nl = NeighborList([0.1, 0.1], skin=0.1, bothways=True, self_interaction=False) assert nl.update(h2) assert nl.get_neighbors(0)[1].shape == (0, 3) assert nl.get_neighbors(0)[1].dtype == int
class LennardJones(Calculator): implemented_properties = ['energy', 'forces', 'stress'] default_parameters = {'epsilon': 1.0, 'sigma': 1.0, 'rc': None} nolabel = True def __init__(self, **kwargs): Calculator.__init__(self, **kwargs) def calculate(self, atoms=None, properties=['energy'], system_changes=all_changes): Calculator.calculate(self, atoms, properties, system_changes) natoms = len(self.atoms) sigma = self.parameters.sigma epsilon = self.parameters.epsilon rc = self.parameters.rc if rc is None: rc = 3 * sigma if 'numbers' in system_changes: self.nl = NeighborList([rc / 2] * natoms, self_interaction=False) self.nl.update(self.atoms) positions = self.atoms.positions cell = self.atoms.cell e0 = 4 * epsilon * ((sigma / rc)**12 - (sigma / rc)**6) energy = 0.0 forces = np.zeros((natoms, 3)) stress = np.zeros((3, 3)) for a1 in range(natoms): neighbors, offsets = self.nl.get_neighbors(a1) cells = np.dot(offsets, cell) d = positions[neighbors] + cells - positions[a1] r2 = (d**2).sum(1) c6 = (sigma**2 / r2)**3 c6[r2 > rc**2] = 0.0 energy -= e0 * (c6 != 0.0).sum() c12 = c6**2 energy += 4 * epsilon * (c12 - c6).sum() f = (24 * epsilon * (2 * c12 - c6) / r2)[:, np.newaxis] * d forces[a1] -= f.sum(axis=0) for a2, f2 in zip(neighbors, f): forces[a2] += f2 stress += np.dot(f.T, d) if 'stress' in properties: if self.atoms.number_of_lattice_vectors == 3: stress += stress.T.copy() stress *= -0.5 / self.atoms.get_volume() self.results['stress'] = stress.flat[[0, 4, 8, 5, 2, 1]] else: raise PropertyNotImplementedError self.results['energy'] = energy self.results['free_energy'] = energy self.results['forces'] = forces
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
class EMT(Calculator): """Python implementation of the Effective Medium Potential. Supports the following standard EMT metals: Al, Cu, Ag, Au, Ni, Pd and Pt. In addition, the following elements are supported. They are NOT well described by EMT, and the parameters are not for any serious use: H, C, N, O The potential takes a single argument, ``asap_cutoff`` (default: False). If set to True, the cutoff mimics how Asap does it; most importantly the global cutoff is chosen from the largest atom present in the simulation, if False it is chosen from the largest atom in the parameter table. True gives the behaviour of the Asap code and older EMT implementations, although the results are not bitwise identical. """ implemented_properties = ['energy', 'forces'] nolabel = True default_parameters = {'asap_cutoff': False} def __init__(self, **kwargs): Calculator.__init__(self, **kwargs) def initialize(self, atoms): self.par = {} self.rc = 0.0 self.numbers = atoms.get_atomic_numbers() if self.parameters.asap_cutoff: relevant_pars = {} for symb, p in parameters.items(): if atomic_numbers[symb] in self.numbers: relevant_pars[symb] = p else: relevant_pars = parameters maxseq = max(par[1] for par in relevant_pars.values()) * Bohr rc = self.rc = beta * maxseq * 0.5 * (sqrt(3) + sqrt(4)) rr = rc * 2 * sqrt(4) / (sqrt(3) + sqrt(4)) self.acut = np.log(9999.0) / (rr - rc) if self.parameters.asap_cutoff: self.rc_list = self.rc * 1.045 else: self.rc_list = self.rc + 0.5 for Z in self.numbers: if Z not in self.par: sym = chemical_symbols[Z] if sym not in parameters: raise NotImplementedError('No EMT-potential for {0}' .format(sym)) p = parameters[sym] s0 = p[1] * Bohr eta2 = p[3] / Bohr kappa = p[4] / Bohr x = eta2 * beta * s0 gamma1 = 0.0 gamma2 = 0.0 for i, n in enumerate([12, 6, 24]): r = s0 * beta * sqrt(i + 1) x = n / (12 * (1.0 + exp(self.acut * (r - rc)))) gamma1 += x * exp(-eta2 * (r - beta * s0)) gamma2 += x * exp(-kappa / beta * (r - beta * s0)) self.par[Z] = {'E0': p[0], 's0': s0, 'V0': p[2], 'eta2': eta2, 'kappa': kappa, 'lambda': p[5] / Bohr, 'n0': p[6] / Bohr**3, 'rc': rc, 'gamma1': gamma1, 'gamma2': gamma2} self.ksi = {} for s1, p1 in self.par.items(): self.ksi[s1] = {} for s2, p2 in self.par.items(): self.ksi[s1][s2] = p2['n0'] / p1['n0'] self.forces = np.empty((len(atoms), 3)) self.sigma1 = np.empty(len(atoms)) self.deds = np.empty(len(atoms)) self.nl = NeighborList([0.5 * self.rc_list] * len(atoms), self_interaction=False) def calculate(self, atoms=None, properties=['energy'], system_changes=all_changes): Calculator.calculate(self, atoms, properties, system_changes) if 'numbers' in system_changes: self.initialize(self.atoms) positions = self.atoms.positions numbers = self.atoms.numbers cell = self.atoms.cell self.nl.update(self.atoms) self.energy = 0.0 self.sigma1[:] = 0.0 self.forces[:] = 0.0 natoms = len(self.atoms) for a1 in range(natoms): Z1 = numbers[a1] p1 = self.par[Z1] ksi = self.ksi[Z1] neighbors, offsets = self.nl.get_neighbors(a1) offsets = np.dot(offsets, cell) for a2, offset in zip(neighbors, offsets): d = positions[a2] + offset - positions[a1] r = sqrt(np.dot(d, d)) if r < self.rc_list: Z2 = numbers[a2] p2 = self.par[Z2] self.interact1(a1, a2, d, r, p1, p2, ksi[Z2]) for a in range(natoms): Z = numbers[a] p = self.par[Z] try: ds = -log(self.sigma1[a] / 12) / (beta * p['eta2']) except (OverflowError, ValueError): self.deds[a] = 0.0 self.energy -= p['E0'] continue x = p['lambda'] * ds y = exp(-x) z = 6 * p['V0'] * exp(-p['kappa'] * ds) self.deds[a] = ((x * y * p['E0'] * p['lambda'] + p['kappa'] * z) / (self.sigma1[a] * beta * p['eta2'])) self.energy += p['E0'] * ((1 + x) * y - 1) + z for a1 in range(natoms): Z1 = numbers[a1] p1 = self.par[Z1] ksi = self.ksi[Z1] neighbors, offsets = self.nl.get_neighbors(a1) offsets = np.dot(offsets, cell) for a2, offset in zip(neighbors, offsets): d = positions[a2] + offset - positions[a1] r = sqrt(np.dot(d, d)) if r < self.rc_list: Z2 = numbers[a2] p2 = self.par[Z2] self.interact2(a1, a2, d, r, p1, p2, ksi[Z2]) self.results['energy'] = self.energy self.results['free_energy'] = self.energy self.results['forces'] = self.forces def interact1(self, a1, a2, d, r, p1, p2, ksi): x = exp(self.acut * (r - self.rc)) theta = 1.0 / (1.0 + x) y1 = (0.5 * p1['V0'] * exp(-p2['kappa'] * (r / beta - p2['s0'])) * ksi / p1['gamma2'] * theta) y2 = (0.5 * p2['V0'] * exp(-p1['kappa'] * (r / beta - p1['s0'])) / ksi / p2['gamma2'] * theta) self.energy -= y1 + y2 f = ((y1 * p2['kappa'] + y2 * p1['kappa']) / beta + (y1 + y2) * self.acut * theta * x) * d / r self.forces[a1] += f self.forces[a2] -= f self.sigma1[a1] += (exp(-p2['eta2'] * (r - beta * p2['s0'])) * ksi * theta / p1['gamma1']) self.sigma1[a2] += (exp(-p1['eta2'] * (r - beta * p1['s0'])) / ksi * theta / p2['gamma1']) def interact2(self, a1, a2, d, r, p1, p2, ksi): x = exp(self.acut * (r - self.rc)) theta = 1.0 / (1.0 + x) y1 = (exp(-p2['eta2'] * (r - beta * p2['s0'])) * ksi / p1['gamma1'] * theta * self.deds[a1]) y2 = (exp(-p1['eta2'] * (r - beta * p1['s0'])) / ksi / p2['gamma1'] * theta * self.deds[a2]) f = ((y1 * p2['eta2'] + y2 * p1['eta2']) + (y1 + y2) * self.acut * theta * x) * d / r self.forces[a1] -= f self.forces[a2] += f
class EAM(Calculator): r""" EAM Interface Documentation Introduction ============ The Embedded Atom Method (EAM) [1]_ is a classical potential which is good for modelling metals, particularly fcc materials. Because it is an equiaxial potential the EAM does not model directional bonds well. However, the Angular Dependent Potential (ADP) [2]_ which is an extended version of EAM is able to model directional bonds and is also included in the EAM calculator. Generally all that is required to use this calculator is to supply a potential file or as a set of functions that describe the potential. The files containing the potentials for this calculator are not included but many suitable potentials can be downloaded from The Interatomic Potentials Repository Project at http://www.ctcms.nist.gov/potentials/ Theory ====== A single element EAM potential is defined by three functions: the embedded energy, electron density and the pair potential. A two element alloy contains the individual three functions for each element plus cross pair interactions. The ADP potential has two additional sets of data to define the dipole and quadrupole directional terms for each alloy and their cross interactions. The total energy `E_{\rm tot}` of an arbitrary arrangement of atoms is given by the EAM potential as .. math:: E_\text{tot} = \sum_i F(\bar\rho_i) + \frac{1}{2}\sum_{i\ne j} \phi(r_{ij}) and .. math:: \bar\rho_i = \sum_j \rho(r_{ij}) where `F` is an embedding function, namely the energy to embed an atom `i` in the combined electron density `\bar\rho_i` which is contributed from each of its neighbouring atoms `j` by an amount `\rho(r_{ij})`, `\phi(r_{ij})` is the pair potential function representing the energy in bond `ij` which is due to the short-range electro-static interaction between atoms, and `r_{ij}` is the distance between an atom and its neighbour for that bond. The ADP potential is defined as .. math:: E_\text{tot} = \sum_i F(\bar\rho_i) + \frac{1}{2}\sum_{i\ne j} \phi(r_{ij}) + \frac{1}{2} \sum_{i,\alpha} (\mu_i^\alpha)^2 + \frac{1}{2} \sum_{i,\alpha,\beta} (\lambda_i^{\alpha\beta})^2 - \frac{1}{6} \sum_i \nu_i^2 where `\mu_i^\alpha` is the dipole vector, `\lambda_i^{\alpha\beta}` is the quadrupole tensor and `\nu_i` is the trace of `\lambda_i^{\alpha\beta}`. Running the Calculator ====================== EAM calculates the cohesive atom energy and forces. Internally the potential functions are defined by splines which may be directly supplied or created by reading the spline points from a data file from which a spline function is created. The LAMMPS compatible ``.alloy`` and ``.adp`` formats are supported. The LAMMPS ``.eam`` format is slightly different from the ``.alloy`` format and is currently not supported. For example:: from ase.calculators.eam import EAM mishin = EAM(potential='Al99.eam.alloy') mishin.write_potential('new.eam.alloy') mishin.plot() slab.set_calculator(mishin) slab.get_potential_energy() slab.get_forces() The breakdown of energy contribution from the indvidual components are stored in the calculator instance ``.results['energy_components']`` Arguments ========= ========================= ==================================================== Keyword Description ========================= ==================================================== ``potential`` file of potential in ``.alloy`` or ``.adp`` format (This is generally all you need to supply) ``elements[N]`` array of N element abbreviations ``embedded_energy[N]`` arrays of embedded energy functions ``electron_density[N]`` arrays of electron density functions ``phi[N,N]`` arrays of pair potential functions ``d_embedded_energy[N]`` arrays of derivative embedded energy functions ``d_electron_density[N]`` arrays of derivative electron density functions ``d_phi[N,N]`` arrays of derivative pair potentials functions ``d[N,N], q[N,N]`` ADP dipole and quadrupole function ``d_d[N,N], d_q[N,N]`` ADP dipole and quadrupole derivative functions ``skin`` skin distance passed to NeighborList(). If no atom has moved more than the skin-distance since the last call to the ``update()`` method then the neighbor list can be reused. Defaults to 1.0. ``form`` the form of the potential ``alloy`` or ``adp``. This will be determined from the file suffix or must be set if using equations ========================= ==================================================== Additional parameters for writing potential files ================================================= The following parameters are only required for writing a potential in ``.alloy`` or ``.adp`` format file. ========================= ==================================================== Keyword Description ========================= ==================================================== ``header`` Three line text header. Default is standard message. ``Z[N]`` Array of atomic number of each element ``mass[N]`` Atomic mass of each element ``a[N]`` Array of lattice parameters for each element ``lattice[N]`` Lattice type ``nrho`` No. of rho samples along embedded energy curve ``drho`` Increment for sampling density ``nr`` No. of radial points along density and pair potential curves ``dr`` Increment for sampling radius ========================= ==================================================== Special features ================ ``.plot()`` Plots the individual functions. This may be called from multiple EAM potentials to compare the shape of the individual curves. This function requires the installation of the Matplotlib libraries. Notes/Issues ============= * Although currently not fast, this calculator can be good for trying small calculations or for creating new potentials by matching baseline data such as from DFT results. The format for these potentials is compatible with LAMMPS_ and so can be used either directly by LAMMPS or with the ASE LAMMPS calculator interface. * Supported formats are the LAMMPS_ ``.alloy`` and ``.adp``. The ``.eam`` format is currently not supported. The form of the potential will be determined from the file suffix. * Any supplied values will override values read from the file. * The derivative functions, if supplied, are only used to calculate forces. * There is a bug in early versions of scipy that will cause eam.py to crash when trying to evaluate splines of a potential with one neighbor such as caused by evaluating a dimer. .. _LAMMPS: http://lammps.sandia.gov/ .. [1] M.S. Daw and M.I. Baskes, Phys. Rev. Letters 50 (1983) 1285. .. [2] Y. Mishin, M.J. Mehl, and D.A. Papaconstantopoulos, Acta Materialia 53 2005 4029--4041. End EAM Interface Documentation """ implemented_properties = ['energy', 'forces'] default_parameters = dict( skin=1.0, potential=None, header=[b'EAM/ADP potential file\n', b'Generated from eam.py\n', b'blank\n']) def __init__(self, restart=None, ignore_bad_restart_file=False, label=os.curdir, atoms=None, **kwargs): if 'potential' in kwargs: self.read_potential(kwargs['potential']) Calculator.__init__(self, restart, ignore_bad_restart_file, label, atoms, **kwargs) valid_args = ('potential', 'elements', 'header', 'drho', 'dr', 'cutoff', 'atomic_number', 'mass', 'a', 'lattice', 'embedded_energy', 'electron_density', 'phi', # derivatives 'd_embedded_energy', 'd_electron_density', 'd_phi', 'd', 'q', 'd_d', 'd_q', # adp terms 'skin', 'form', 'Z', 'nr', 'nrho', 'mass') # set any additional keyword arguments for arg, val in self.parameters.items(): if arg in valid_args: setattr(self, arg, val) else: raise RuntimeError('unknown keyword arg "%s" : not in %s' % (arg, valid_args)) def set_form(self, fileobj): """set the form variable based on the file name suffix""" extension = os.path.splitext(fileobj)[1] if extension == '.eam': self.form = 'eam' elif extension == '.alloy': self.form = 'alloy' elif extension == '.adp': self.form = 'adp' else: raise RuntimeError('unknown file extension type: %s' % extension) def read_potential(self, fileobj): """Reads a LAMMPS EAM file in alloy or adp format and creates the interpolation functions from the data """ if isinstance(fileobj, basestring): f = open(fileobj) self.set_form(fileobj) else: f = fileobj def lines_to_list(lines): """Make the data one long line so as not to care how its formatted """ data = [] for line in lines: data.extend(line.split()) return data lines = f.readlines() if self.form == 'eam': # single element eam file (aka funcfl) self.header = lines[:1] data = lines_to_list(lines[1:]) # eam form is just like an alloy form for one element self.Nelements = 1 self.Z = np.array([data[0]], dtype=int) self.mass = np.array([data[1]]) self.a = np.array([data[2]]) self.lattice = [data[3]] self.nrho = int(data[4]) self.drho = float(data[5]) self.nr = int(data[6]) self.dr = float(data[7]) self.cutoff = float(data[8]) n = 9 + self.nrho self.embedded_data = np.array([np.float_(data[9:n])]) self.rphi_data = np.zeros([self.Nelements, self.Nelements, self.nr]) effective_charge = np.float_(data[n:n + self.nr]) # convert effective charges to rphi according to # http://lammps.sandia.gov/doc/pair_eam.html self.rphi_data[0, 0] = Bohr * Hartree * (effective_charge**2) self.density_data = np.array( [np.float_(data[n + self.nr:n + 2 * self.nr])]) else: self.header = lines[:3] i = 3 data = lines_to_list(lines[i:]) self.Nelements = int(data[0]) d = 1 self.elements = data[d:d + self.Nelements] d += self.Nelements self.nrho = int(data[d]) self.drho = float(data[d + 1]) self.nr = int(data[d + 2]) self.dr = float(data[d + 3]) self.cutoff = float(data[d + 4]) self.embedded_data = np.zeros([self.Nelements, self.nrho]) self.density_data = np.zeros([self.Nelements, self.nr]) self.Z = np.zeros([self.Nelements], dtype=int) self.mass = np.zeros([self.Nelements]) self.a = np.zeros([self.Nelements]) self.lattice = [] d += 5 # reads in the part of the eam file for each element for elem in range(self.Nelements): self.Z[elem] = int(data[d]) self.mass[elem] = float(data[d + 1]) self.a[elem] = float(data[d + 2]) self.lattice.append(data[d + 3]) d += 4 self.embedded_data[elem] = np.float_( data[d:(d + self.nrho)]) d += self.nrho self.density_data[elem] = np.float_(data[d:(d + self.nr)]) d += self.nr # reads in the r*phi data for each interaction between elements self.rphi_data = np.zeros([self.Nelements, self.Nelements, self.nr]) for i in range(self.Nelements): for j in range(i + 1): self.rphi_data[j, i] = np.float_(data[d:(d + self.nr)]) d += self.nr self.r = np.arange(0, self.nr) * self.dr self.rho = np.arange(0, self.nrho) * self.drho self.set_splines() if (self.form == 'adp'): self.read_adp_data(data, d) self.set_adp_splines() def set_splines(self): # this section turns the file data into three functions (and # derivative functions) that define the potential self.embedded_energy = np.empty(self.Nelements, object) self.electron_density = np.empty(self.Nelements, object) self.d_embedded_energy = np.empty(self.Nelements, object) self.d_electron_density = np.empty(self.Nelements, object) for i in range(self.Nelements): self.embedded_energy[i] = spline(self.rho, self.embedded_data[i], k=3) self.electron_density[i] = spline(self.r, self.density_data[i], k=3) self.d_embedded_energy[i] = self.deriv(self.embedded_energy[i]) self.d_electron_density[i] = self.deriv(self.electron_density[i]) self.phi = np.empty([self.Nelements, self.Nelements], object) self.d_phi = np.empty([self.Nelements, self.Nelements], object) # ignore the first point of the phi data because it is forced # to go through zero due to the r*phi format in alloy and adp for i in range(self.Nelements): for j in range(i, self.Nelements): self.phi[i, j] = spline( self.r[1:], self.rphi_data[i, j][1:] / self.r[1:], k=3) self.d_phi[i, j] = self.deriv(self.phi[i, j]) if j != i: self.phi[j, i] = self.phi[i, j] self.d_phi[j, i] = self.d_phi[i, j] def set_adp_splines(self): self.d = np.empty([self.Nelements, self.Nelements], object) self.d_d = np.empty([self.Nelements, self.Nelements], object) self.q = np.empty([self.Nelements, self.Nelements], object) self.d_q = np.empty([self.Nelements, self.Nelements], object) for i in range(self.Nelements): for j in range(i, self.Nelements): self.d[i, j] = spline(self.r[1:], self.d_data[i, j][1:], k=3) self.d_d[i, j] = self.deriv(self.d[i, j]) self.q[i, j] = spline(self.r[1:], self.q_data[i, j][1:], k=3) self.d_q[i, j] = self.deriv(self.q[i, j]) # make symmetrical if j != i: self.d[j, i] = self.d[i, j] self.d_d[j, i] = self.d_d[i, j] self.q[j, i] = self.q[i, j] self.d_q[j, i] = self.d_q[i, j] def read_adp_data(self, data, d): """read in the extra adp data from the potential file""" self.d_data = np.zeros([self.Nelements, self.Nelements, self.nr]) # should be non symmetrical combinations of 2 for i in range(self.Nelements): for j in range(i + 1): self.d_data[j, i] = data[d:d + self.nr] d += self.nr self.q_data = np.zeros([self.Nelements, self.Nelements, self.nr]) # should be non symmetrical combinations of 2 for i in range(self.Nelements): for j in range(i + 1): self.q_data[j, i] = data[d:d + self.nr] d += self.nr def write_potential(self, filename, nc=1, numformat='%.8e'): """Writes out the potential in the format given by the form variable to 'filename' with a data format that is nc columns wide. Note: array lengths need to be an exact multiple of nc """ f = open(filename, 'wb') assert self.nr % nc == 0 assert self.nrho % nc == 0 for line in self.header: f.write(line) f.write('{0} '.format(self.Nelements).encode()) f.write(' '.join(self.elements).encode() + b'\n') f.write(('%d %f %d %f %f \n' % (self.nrho, self.drho, self.nr, self.dr, self.cutoff)).encode()) # start of each section for each element # rs = np.linspace(0, self.nr * self.dr, self.nr) # rhos = np.linspace(0, self.nrho * self.drho, self.nrho) rs = np.arange(0, self.nr) * self.dr rhos = np.arange(0, self.nrho) * self.drho for i in range(self.Nelements): f.write(('%d %f %f %s\n' % (self.Z[i], self.mass[i], self.a[i], str(self.lattice[i]))).encode()) np.savetxt(f, self.embedded_energy[i](rhos).reshape(self.nrho // nc, nc), fmt=nc * [numformat]) np.savetxt(f, self.electron_density[i](rs).reshape(self.nr // nc, nc), fmt=nc * [numformat]) # write out the pair potentials in Lammps DYNAMO setfl format # as r*phi for alloy format for i in range(self.Nelements): for j in range(i, self.Nelements): np.savetxt(f, (rs * self.phi[i, j](rs)).reshape(self.nr // nc, nc), fmt=nc * [numformat]) if self.form == 'adp': # these are the u(r) or dipole values for i in range(self.Nelements): for j in range(i + 1): np.savetxt(f, self.d_data[i, j]) # these are the w(r) or quadrupole values for i in range(self.Nelements): for j in range(i + 1): np.savetxt(f, self.q_data[i, j]) f.close() def update(self, atoms): # check all the elements are available in the potential self.Nelements = len(self.elements) elements = np.unique(atoms.get_chemical_symbols()) unavailable = np.logical_not( np.array([item in self.elements for item in elements])) if np.any(unavailable): raise RuntimeError('These elements are not in the potential: %s' % elements[unavailable]) # cutoffs need to be a vector for NeighborList cutoffs = self.cutoff * np.ones(len(atoms)) # convert the elements to an index of the position # in the eam format self.index = np.array([self.elements.index(el) for el in atoms.get_chemical_symbols()]) self.pbc = atoms.get_pbc() # since we need the contribution of all neighbors to the # local electron density we cannot just calculate and use # one way neighbors self.neighbors = NeighborList(cutoffs, skin=self.parameters.skin, self_interaction=False, bothways=True) self.neighbors.update(atoms) def calculate(self, atoms=None, properties=['energy'], system_changes=all_changes): """EAM Calculator atoms: Atoms object Contains positions, unit-cell, ... properties: list of str List of what needs to be calculated. Can be any combination of 'energy', 'forces' system_changes: list of str List of what has changed since last calculation. Can be any combination of these five: 'positions', 'numbers', 'cell', 'pbc', 'initial_charges' and 'initial_magmoms'. """ Calculator.calculate(self, atoms, properties, system_changes) # we shouldn't really recalc if charges or magmos change if len(system_changes) > 0: # something wrong with this way self.update(self.atoms) self.calculate_energy(self.atoms) if 'forces' in properties: self.calculate_forces(self.atoms) # check we have all the properties requested for property in properties: if property not in self.results: if property is 'energy': self.calculate_energy(self.atoms) if property is 'forces': self.calculate_forces(self.atoms) # we need to remember the previous state of parameters # if 'potential' in parameter_changes and potential != None: # self.read_potential(potential) def calculate_energy(self, atoms): """Calculate the energy the energy is made up of the ionic or pair interaction and the embedding energy of each atom into the electron cloud generated by its neighbors """ pair_energy = 0.0 embedding_energy = 0.0 mu_energy = 0.0 lam_energy = 0.0 trace_energy = 0.0 self.total_density = np.zeros(len(atoms)) if (self.form == 'adp'): self.mu = np.zeros([len(atoms), 3]) self.lam = np.zeros([len(atoms), 3, 3]) for i in range(len(atoms)): # this is the atom to be embedded neighbors, offsets = self.neighbors.get_neighbors(i) offset = np.dot(offsets, atoms.get_cell()) rvec = (atoms.positions[neighbors] + offset - atoms.positions[i]) # calculate the distance to the nearest neighbors r = np.sqrt(np.sum(np.square(rvec), axis=1)) # fast # r = np.apply_along_axis(np.linalg.norm, 1, rvec) # sloow nearest = np.arange(len(r))[r <= self.cutoff] for j_index in range(self.Nelements): use = self.index[neighbors[nearest]] == j_index if not use.any(): continue pair_energy += np.sum(self.phi[self.index[i], j_index]( r[nearest][use])) / 2. density = np.sum( self.electron_density[j_index](r[nearest][use])) self.total_density[i] += density if self.form == 'adp': self.mu[i] += self.adp_dipole( r[nearest][use], rvec[nearest][use], self.d[self.index[i], j_index]) self.lam[i] += self.adp_quadrupole( r[nearest][use], rvec[nearest][use], self.q[self.index[i], j_index]) # add in the electron embedding energy embedding_energy += self.embedded_energy[self.index[i]]( self.total_density[i]) components = dict(pair=pair_energy, embedding=embedding_energy) if self.form == 'adp': mu_energy += np.sum(self.mu ** 2) / 2. lam_energy += np.sum(self.lam ** 2) / 2. for i in range(len(atoms)): # this is the atom to be embedded trace_energy -= np.sum(self.lam[i].trace() ** 2) / 6. adp_result = dict(adp_mu=mu_energy, adp_lam=lam_energy, adp_trace=trace_energy) components.update(adp_result) self.positions = atoms.positions.copy() self.cell = atoms.get_cell().copy() energy = 0.0 for i in components.keys(): energy += components[i] self.energy_free = energy self.energy_zero = energy self.results['energy_components'] = components self.results['energy'] = energy def calculate_forces(self, atoms): # calculate the forces based on derivatives of the three EAM functions self.update(atoms) self.results['forces'] = np.zeros((len(atoms), 3)) for i in range(len(atoms)): # this is the atom to be embedded neighbors, offsets = self.neighbors.get_neighbors(i) offset = np.dot(offsets, atoms.get_cell()) # create a vector of relative positions of neighbors rvec = atoms.positions[neighbors] + offset - atoms.positions[i] r = np.sqrt(np.sum(np.square(rvec), axis=1)) nearest = np.arange(len(r))[r < self.cutoff] d_embedded_energy_i = self.d_embedded_energy[ self.index[i]](self.total_density[i]) urvec = rvec.copy() # unit directional vector for j in np.arange(len(neighbors)): urvec[j] = urvec[j] / r[j] for j_index in range(self.Nelements): use = self.index[neighbors[nearest]] == j_index if not use.any(): continue rnuse = r[nearest][use] density_j = self.total_density[neighbors[nearest][use]] scale = (self.d_phi[self.index[i], j_index](rnuse) + (d_embedded_energy_i * self.d_electron_density[j_index](rnuse)) + (self.d_embedded_energy[j_index](density_j) * self.d_electron_density[self.index[i]](rnuse))) self.results['forces'][i] += np.dot(scale, urvec[nearest][use]) if (self.form == 'adp'): adp_forces = self.angular_forces( self.mu[i], self.mu[neighbors[nearest][use]], self.lam[i], self.lam[neighbors[nearest][use]], rnuse, rvec[nearest][use], self.index[i], j_index) self.results['forces'][i] += adp_forces def angular_forces(self, mu_i, mu, lam_i, lam, r, rvec, form1, form2): # calculate the extra components for the adp forces # rvec are the relative positions to atom i psi = np.zeros(mu.shape) for gamma in range(3): term1 = (mu_i[gamma] - mu[:, gamma]) * self.d[form1][form2](r) term2 = np.sum((mu_i - mu) * self.d_d[form1][form2](r)[:, np.newaxis] * (rvec * rvec[:, gamma][:, np.newaxis] / r[:, np.newaxis]), axis=1) term3 = 2 * np.sum((lam_i[:, gamma] + lam[:, :, gamma]) * rvec * self.q[form1][form2](r)[:, np.newaxis], axis=1) term4 = 0.0 for alpha in range(3): for beta in range(3): rs = rvec[:, alpha] * rvec[:, beta] * rvec[:, gamma] term4 += ((lam_i[alpha, beta] + lam[:, alpha, beta]) * self.d_q[form1][form2](r) * rs) / r term5 = ((lam_i.trace() + lam.trace(axis1=1, axis2=2)) * (self.d_q[form1][form2](r) * r + 2 * self.q[form1][form2](r)) * rvec[:, gamma]) / 3. # the minus for term5 is a correction on the adp # formulation given in the 2005 Mishin Paper and is posted # on the NIST website with the AlH potential psi[:, gamma] = term1 + term2 + term3 + term4 - term5 return np.sum(psi, axis=0) def adp_dipole(self, r, rvec, d): # calculate the dipole contribution mu = np.sum((rvec * d(r)[:, np.newaxis]), axis=0) return mu # sign to agree with lammps def adp_quadrupole(self, r, rvec, q): # slow way of calculating the quadrupole contribution r = np.sqrt(np.sum(rvec ** 2, axis=1)) lam = np.zeros([rvec.shape[0], 3, 3]) qr = q(r) for alpha in range(3): for beta in range(3): lam[:, alpha, beta] += qr * rvec[:, alpha] * rvec[:, beta] return np.sum(lam, axis=0) def deriv(self, spline): """Wrapper for extracting the derivative from a spline""" def d_spline(aspline): return spline(aspline, 1) return d_spline def plot(self, name=''): """Plot the individual curves""" try: import matplotlib.pyplot as plt except ImportError: raise NotAvailable('This needs matplotlib module.') if self.form == 'eam' or self.form == 'alloy': nrow = 2 elif self.form == 'adp': nrow = 3 else: raise RuntimeError('Unknown form of potential: %s' % self.form) if hasattr(self, 'r'): r = self.r else: r = np.linspace(0, self.cutoff, 50) if hasattr(self, 'rho'): rho = self.rho else: rho = np.linspace(0, 10.0, 50) plt.subplot(nrow, 2, 1) self.elem_subplot(rho, self.embedded_energy, r'$\rho$', r'Embedding Energy $F(\bar\rho)$', name, plt) plt.subplot(nrow, 2, 2) self.elem_subplot(r, self.electron_density, r'$r$', r'Electron Density $\rho(r)$', name, plt) plt.subplot(nrow, 2, 3) self.multielem_subplot(r, self.phi, r'$r$', r'Pair Potential $\phi(r)$', name, plt) plt.ylim(-1.0, 1.0) # need reasonable values if self.form == 'adp': plt.subplot(nrow, 2, 5) self.multielem_subplot(r, self.d, r'$r$', r'Dipole Energy', name, plt) plt.subplot(nrow, 2, 6) self.multielem_subplot(r, self.q, r'$r$', r'Quadrupole Energy', name, plt) plt.plot() def elem_subplot(self, curvex, curvey, xlabel, ylabel, name, plt): plt.xlabel(xlabel) plt.ylabel(ylabel) for i in np.arange(self.Nelements): label = name + ' ' + self.elements[i] plt.plot(curvex, curvey[i](curvex), label=label) plt.legend() def multielem_subplot(self, curvex, curvey, xlabel, ylabel, name, plt): plt.xlabel(xlabel) plt.ylabel(ylabel) for i in np.arange(self.Nelements): for j in np.arange(i + 1): label = name + ' ' + self.elements[i] + '-' + self.elements[j] plt.plot(curvex, curvey[i, j](curvex), label=label) plt.legend()
def get_neighbours(atoms, r_cut, self_interaction=False): """Return a list of pairs of atoms within a given distance of each other. If matscipy can be imported, then this will directly call matscipy's neighbourlist function. Otherwise it will use ASE's NeighborList object. Args: atoms: ase.atoms object to calculate neighbours for r_cut: cutoff radius (float). Pairs of atoms are considered neighbours if they are within a distance r_cut of each other (note that this is double the parameter used in the ASE's neighborlist module) Returns: a tuple (i_list, j_list, d_list, fixed_atoms): i_list, j_list: i and j indices of each neighbour pair d_list: absolute distance between the corresponding pair fixed_atoms: indices of any fixed atoms """ if isinstance(atoms, Filter): atoms = atoms.atoms if have_matscipy: i_list, j_list, d_list = neighbour_list('ijd', atoms, r_cut) else: radii = [r_cut / 2 for i in range(len(atoms))] nl = NeighborList(radii, sorted=False, self_interaction=False, bothways=True) nl.update(atoms) i_list = [] j_list = [] d_list = [] for i, atom in enumerate(atoms): posn_i = atom.position indices, offsets = nl.get_neighbors(i) assert len(indices) == len(offsets) for j, offset in zip(indices, offsets): # Offsets represent how far away an atom is from its pair in terms # of the repeating cell - for instance, an atom i might be in cell # (0, 0, 0) while the neighbouring atom j is in cell (0, 1, 1). To # get the true position we have to correct for the offset: posn_j = atoms.positions[j] + np.dot(offset, atoms.get_cell()) distance = np.sqrt(((posn_j - posn_i)**2).sum()) i_list.append(i) j_list.append(j) d_list.append(distance) i_list = np.array(i_list) j_list = np.array(j_list) d_list = np.array(d_list) # filter out self-interactions (across PBC) if not self_interaction: mask = i_list != j_list i_list = i_list[mask] j_list = j_list[mask] d_list = d_list[mask] # filter out bonds where 1st atom (i) in pair is fixed fixed_atoms = [] for constraint in atoms.constraints: if isinstance(constraint, FixAtoms): fixed_atoms.extend(list(constraint.index)) else: raise TypeError( 'only FixAtoms constraints are supported by Precon class') return i_list, j_list, d_list, fixed_atoms
class OPLSff: def __init__(self, fileobj=None, warnings=0): self.warnings = warnings self.data = {} if fileobj is not None: self.read(fileobj) def read(self, fileobj, comments='#'): if isinstance(fileobj, str): fileobj = open(fileobj) def read_block(name, symlen, nvalues): """Read a data block. name: name of the block to store in self.data symlen: length of the symbol nvalues: number of values expected """ if name not in self.data: self.data[name] = {} data = self.data[name] def add_line(): line = fileobj.readline().strip() if not len(line): # end of the block return False line = line.split('#')[0] # get rid of comments if len(line) > symlen: symbol = line[:symlen] words = line[symlen:].split() if len(words) >= nvalues: if nvalues == 1: data[symbol] = float(words[0]) else: data[symbol] = [float(word) for word in words[:nvalues]] return True while add_line(): pass read_block('one', 2, 3) read_block('bonds', 5, 2) read_block('angles', 8, 2) read_block('dihedrals', 11, 4) read_block('cutoffs', 5, 1) self.bonds = BondData(self.data['bonds']) self.angles = AnglesData(self.data['angles']) self.dihedrals = DihedralsData(self.data['dihedrals']) self.cutoffs = CutoffList(self.data['cutoffs']) def write_lammps(self, atoms, prefix='lammps'): """Write input for a LAMMPS calculation.""" self.prefix = prefix if hasattr(atoms, 'connectivities'): connectivities = atoms.connectivities else: btypes, blist = self.get_bonds(atoms) atypes, alist = self.get_angles() dtypes, dlist = self.get_dihedrals(alist, atypes) connectivities = { 'bonds': blist, 'bond types': btypes, 'angles': alist, 'angle types': atypes, 'dihedrals': dlist, 'dihedral types': dtypes, } self.write_lammps_definitions(atoms, btypes, atypes, dtypes) self.write_lammps_in() self.write_lammps_atoms(atoms, connectivities) def write_lammps_in(self): fileobj = self.prefix + '_in' if isinstance(fileobj, str): fileobj = open(fileobj, 'w') fileobj.write("""# LAMMPS relaxation (written by ASE) units metal atom_style full boundary p p p #boundary p p f """) fileobj.write('read_data ' + self.prefix + '_atoms\n') fileobj.write('include ' + self.prefix + '_opls\n') fileobj.write(""" kspace_style pppm 1e-5 #kspace_modify slab 3.0 neighbor 1.0 bin neigh_modify delay 0 every 1 check yes thermo 1000 thermo_style custom step temp press cpu pxx pyy pzz pxy pxz pyz ke pe etotal vol lx ly lz atoms dump 1 all xyz 1000 dump_relax.xyz dump_modify 1 sort id restart 100000 test_relax min_style fire minimize 1.0e-14 1.0e-5 100000 100000 """) fileobj.close() def write_lammps_atoms(self, atoms, connectivities): """Write atoms input for LAMMPS""" fname = self.prefix + '_atoms' fileobj = open(fname, 'w') # header fileobj.write(fileobj.name + ' (by ' + str(self.__class__) + ')\n\n') fileobj.write(str(len(atoms)) + ' atoms\n') fileobj.write(str(len(atoms.types)) + ' atom types\n') blist = connectivities['bonds'] if len(blist): btypes = connectivities['bond types'] fileobj.write(str(len(blist)) + ' bonds\n') fileobj.write(str(len(btypes)) + ' bond types\n') alist = connectivities['angles'] if len(alist): atypes = connectivities['angle types'] fileobj.write(str(len(alist)) + ' angles\n') fileobj.write(str(len(atypes)) + ' angle types\n') dlist = connectivities['dihedrals'] if len(dlist): dtypes = connectivities['dihedral types'] fileobj.write(str(len(dlist)) + ' dihedrals\n') fileobj.write(str(len(dtypes)) + ' dihedral types\n') # cell p = Prism(atoms.get_cell()) xhi, yhi, zhi, xy, xz, yz = p.get_lammps_prism_str() fileobj.write('\n0.0 %s xlo xhi\n' % xhi) fileobj.write('0.0 %s ylo yhi\n' % yhi) fileobj.write('0.0 %s zlo zhi\n' % zhi) # atoms fileobj.write('\nAtoms\n\n') tag = atoms.get_tags() if atoms.has('molid'): molid = atoms.get_array('molid') else: molid = [1] * len(atoms) for i, r in enumerate(map(p.pos_to_lammps_str, atoms.get_positions())): q = self.data['one'][atoms.types[tag[i]]][2] fileobj.write('%6d %3d %3d %s %s %s %s' % ((i + 1, molid[i], tag[i] + 1, q) + tuple(r))) fileobj.write(' # ' + atoms.types[tag[i]] + '\n') # velocities velocities = atoms.get_velocities() if velocities is not None: fileobj.write('\nVelocities\n\n') for i, v in enumerate(velocities): fileobj.write('%6d %g %g %g\n' % (i + 1, v[0], v[1], v[2])) # masses fileobj.write('\nMasses\n\n') for i, typ in enumerate(atoms.types): cs = atoms.split_symbol(typ)[0] fileobj.write('%6d %g # %s -> %s\n' % (i + 1, atomic_masses[chemical_symbols.index(cs)], typ, cs)) # bonds if len(blist): fileobj.write('\nBonds\n\n') for ib, bvals in enumerate(blist): fileobj.write('%8d %6d %6d %6d ' % (ib + 1, bvals[0] + 1, bvals[1] + 1, bvals[2] + 1)) try: fileobj.write('# ' + btypes[bvals[0]]) except: pass fileobj.write('\n') # angles if len(alist): fileobj.write('\nAngles\n\n') for ia, avals in enumerate(alist): fileobj.write('%8d %6d %6d %6d %6d ' % (ia + 1, avals[0] + 1, avals[1] + 1, avals[2] + 1, avals[3] + 1)) try: fileobj.write('# ' + atypes[avals[0]]) except: pass fileobj.write('\n') # dihedrals if len(dlist): fileobj.write('\nDihedrals\n\n') for i, dvals in enumerate(dlist): fileobj.write('%8d %6d %6d %6d %6d %6d ' % (i + 1, dvals[0] + 1, dvals[1] + 1, dvals[2] + 1, dvals[3] + 1, dvals[4] + 1)) try: fileobj.write('# ' + dtypes[dvals[0]]) except: pass fileobj.write('\n') def update_neighbor_list(self, atoms): cut = 0.5 * max(self.data['cutoffs'].values()) self.nl = NeighborList([cut] * len(atoms), skin=0, bothways=True, self_interaction=False) self.nl.update(atoms) self.atoms = atoms def get_bonds(self, atoms): """Find bonds and return them and their types""" cutoffs = CutoffList(self.data['cutoffs']) self.update_neighbor_list(atoms) types = atoms.get_types() tags = atoms.get_tags() cell = atoms.get_cell() bond_list = [] bond_types = [] for i, atom in enumerate(atoms): iname = types[tags[i]] indices, offsets = self.nl.get_neighbors(i) for j, offset in zip(indices, offsets): if j <= i: continue # do not double count jname = types[tags[j]] cut = cutoffs.value(iname, jname) if cut is None: if self.warnings > 1: print('Warning: cutoff %s-%s not found' % (iname, jname)) continue # don't have it dist = np.linalg.norm(atom.position - atoms[j].position - np.dot(offset, cell)) if dist > cut: continue # too far away name, val = self.bonds.name_value(iname, jname) if name is None: if self.warnings: print('Warning: potential %s-%s not found' % (iname, jname)) continue # don't have it if name not in bond_types: bond_types.append(name) bond_list.append([bond_types.index(name), i, j]) return bond_types, bond_list def get_angles(self, atoms=None): cutoffs = CutoffList(self.data['cutoffs']) if atoms is not None: self.update_neighbor_list(atoms) else: atoms = self.atoms types = atoms.get_types() tags = atoms.get_tags() cell = atoms.get_cell() ang_list = [] ang_types = [] # center atom *-i-* for i, atom in enumerate(atoms): iname = types[tags[i]] indicesi, offsetsi = self.nl.get_neighbors(i) # search for first neighbor j-i-* for j, offsetj in zip(indicesi, offsetsi): jname = types[tags[j]] cut = cutoffs.value(iname, jname) if cut is None: continue # don't have it dist = np.linalg.norm(atom.position - atoms[j].position - np.dot(offsetj, cell)) if dist > cut: continue # too far away # search for second neighbor j-i-k for k, offsetk in zip(indicesi, offsetsi): if k <= j: continue # avoid double count kname = types[tags[k]] cut = cutoffs.value(iname, kname) if cut is None: continue # don't have it dist = np.linalg.norm(atom.position - np.dot(offsetk, cell) - atoms[k].position) if dist > cut: continue # too far away name, val = self.angles.name_value(jname, iname, kname) if name is None: if self.warnings > 1: print('Warning: angles %s-%s-%s not found' % (jname, iname, kname)) continue # don't have it if name not in ang_types: ang_types.append(name) ang_list.append([ang_types.index(name), j, i, k]) return ang_types, ang_list def get_dihedrals(self, ang_types, ang_list): 'Dihedrals derived from angles.' cutoffs = CutoffList(self.data['cutoffs']) atoms = self.atoms types = atoms.get_types() tags = atoms.get_tags() cell = atoms.get_cell() dih_list = [] dih_types = [] def append(name, i, j, k, l): if name not in dih_types: dih_types.append(name) index = dih_types.index(name) if (([index, i, j, k, l] not in dih_list) and ([index, l, k, j, i] not in dih_list)): dih_list.append([index, i, j, k, l]) for angle in ang_types: l, i, j, k = angle iname = types[tags[i]] jname = types[tags[j]] kname = types[tags[k]] # search for l-i-j-k indicesi, offsetsi = self.nl.get_neighbors(i) for l, offsetl in zip(indicesi, offsetsi): if l == j: continue # avoid double count lname = types[tags[l]] cut = cutoffs.value(iname, lname) if cut is None: continue # don't have it dist = np.linalg.norm(atoms[i].position - atoms[l].position - np.dot(offsetl, cell)) if dist > cut: continue # too far away name, val = self.dihedrals.name_value(lname, iname, jname, kname) if name is None: continue # don't have it append(name, l, i, j, k) # search for i-j-k-l indicesk, offsetsk = self.nl.get_neighbors(k) for l, offsetl in zip(indicesk, offsetsk): if l == j: continue # avoid double count lname = types[tags[l]] cut = cutoffs.value(kname, lname) if cut is None: continue # don't have it dist = np.linalg.norm(atoms[k].position - atoms[l].position - np.dot(offsetl, cell)) if dist > cut: continue # too far away name, val = self.dihedrals.name_value(iname, jname, kname, lname) if name is None: continue # don't have it append(name, i, j, k, l) return dih_types, dih_list def write_lammps_definitions(self, atoms, btypes, atypes, dtypes): """Write force field definitions for LAMMPS.""" fileobj = self.prefix + '_opls' if isinstance(fileobj, str): fileobj = open(fileobj, 'w') fileobj.write('# OPLS potential\n') fileobj.write('# write_lammps' + str(time.asctime( time.localtime(time.time())))) # bonds if len(btypes): fileobj.write('\n# bonds\n') fileobj.write('bond_style harmonic\n') for ib, btype in enumerate(btypes): fileobj.write('bond_coeff %6d' % (ib + 1)) for value in self.bonds.nvh[btype]: fileobj.write(' ' + str(value)) fileobj.write(' # ' + btype + '\n') # angles if len(atypes): fileobj.write('\n# angles\n') fileobj.write('angle_style harmonic\n') for ia, atype in enumerate(atypes): fileobj.write('angle_coeff %6d' % (ia + 1)) for value in self.angles.nvh[atype]: fileobj.write(' ' + str(value)) fileobj.write(' # ' + atype + '\n') # dihedrals if len(dtypes): fileobj.write('\n# dihedrals\n') fileobj.write('dihedral_style opls\n') for i, dtype in enumerate(dtypes): fileobj.write('dihedral_coeff %6d' % (i + 1)) for value in self.dihedrals.nvh[dtype]: fileobj.write(' ' + str(value)) fileobj.write(' # ' + dtype + '\n') # Lennard Jones settings fileobj.write('\n# L-J parameters\n') fileobj.write('pair_style lj/cut/coul/long 10.0 7.4' + ' # consider changing these parameters\n') fileobj.write('special_bonds lj/coul 0.0 0.0 0.5\n') data = self.data['one'] for ia, atype in enumerate(atoms.types): if len(atype) < 2: atype = atype + ' ' fileobj.write('pair_coeff ' + str(ia + 1) + ' ' + str(ia + 1)) for value in data[atype][:2]: fileobj.write(' ' + str(value)) fileobj.write(' # ' + atype + '\n') fileobj.write('pair_modify shift yes mix geometric\n') # Charges fileobj.write('\n# charges\n') for ia, atype in enumerate(atoms.types): if len(atype) < 2: atype = atype + ' ' fileobj.write('set type ' + str(ia + 1)) fileobj.write(' charge ' + str(data[atype][2])) fileobj.write(' # ' + atype + '\n')
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 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