def check_min_dist(Optimizer, totalsol, type='Defect', nat=None, min_len=0.7, STR=''): if type=='Defect' or type=='Crystal' or type=='Surface': if nat==None: nat=len(totalsol) cutoffs=[2.0 for one in totalsol] nl=NeighborList(cutoffs,bothways=True,self_interaction=False) nl.update(totalsol) for one in totalsol[0:nat]: nbatoms=Atoms() nbatoms.append(one) indices, offsets=nl.get_neighbors(one.index) for index, d in zip(indices,offsets): index = int(index) sym=totalsol[index].symbol pos=totalsol[index].position + numpy.dot(d,totalsol.get_cell()) at=Atom(symbol=sym,position=pos) nbatoms.append(at) while True: dflag=False for i in range(1,len(nbatoms)): d=nbatoms.get_distance(0,i) if d < min_len: nbatoms.set_distance(0,i,min_len+.01,fix=0.5) STR+='--- WARNING: Atoms too close (<0.7A) - Implement Move ---\n' dflag=True if dflag==False: break for i in range(len(indices)): totalsol[indices[i]].position=nbatoms[i+1].position totalsol[one.index].position=nbatoms[0].position nl.update(totalsol) elif type=='Cluster': if not 'LAMMPS' in Optimizer.modules: for i in range(len(totalsol)): for j in range(len(totalsol)): if i != j: d=totalsol.get_distance(i,j) if d < min_len: totalsol.set_distance(i,j,min_len,fix=0.5) STR+='--- WARNING: Atoms too close (<0.7A) - Implement Move ---\n' else: rank = MPI.COMM_WORLD.Get_rank() #logger = logging.getLogger(Optimizer.loggername) R = totalsol.arrays['positions'] tol = 0.01 epsilon = 0.05 fix = 0.5 closelist = numpy.arange(len(totalsol)) iter = 0 while len(closelist) > 0 and iter<2: iter+=1 closelist = [] dist=spatial.distance.cdist(R,R) numpy.fill_diagonal(dist,1.0) smalldist = numpy.where(dist < min_len-tol) for ind in range(len(smalldist[0])): i = smalldist[0][ind] j = smalldist[1][ind] if i < j and dist[i][j] < min_len-tol: closelist.append(i) closelist.append(j) if dist[i][j] > epsilon: x = 1.0 - min_len / dist[i][j] D = R[j]-R[i] R[i] += (x * fix) * D R[j] -= (x * (1.0 - fix)) * D else: R[i] += [0.2, 0.0, 0.0] R[j] -= [0.2, 0.0, 0.0] R2P = [R[i],R[j]] dist2P=spatial.distance.cdist(R2P,R) dist[i] = dist2P[0] dist[j] = dist2P[1] for k in range(len(R)): dist[k][i] = dist[i][k] dist[k][j] = dist[j][k] closelist=list(set(closelist)) closelist.sort() #if len(closelist) != 0: # logger.info('M:iter {0}, closelist size {1}'.format(iter,len(closelist))) else: print 'WARNING: In Check_Min_Dist in EvalEnergy: Structure Type not recognized' return totalsol, STR
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.calculators.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) if nb == 0: return n1 = 0 for a in range(self.images.natoms): indices, offsets = nl.get_neighbors(a) 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 __init__(self, filename): atoms = ase.io.read(filename, format='vasp') cell = atoms.get_cell() symb = atoms.get_atomic_numbers() cutoffs = np.ones((len(atoms))) * 1.35 # radius around each atom (half the max bondlength) nl = NeighborList(cutoffs, 0., self_interaction = False, bothways = False) nl.update(atoms) std_len = 2.35805097505 # Loop over atoms bond_no = 0 bonds = np.zeros((nl.nneighbors,4)) for atom1idx, (indices, offsets, atom1pos) in enumerate(zip(nl.neighbors, nl.displacements, nl.positions)): # Loop over bonds for atom2idx, offset in zip(indices, offsets): #if symb[atom1idx] == symb[atom2idx] == 'Si': atom2pos = nl.positions[atom2idx] + np.dot(offset, cell) #print "pos[%i] = %.2f %.2f %.2f" % (atom1idx, atom1pos[0],atom1pos[1],atom1pos[2]) #print "pos[%i] = %.2f %.2f %.2f" % (atom2idx, atom2pos[0],atom2pos[1],atom2pos[2]) bond = atom2pos - atom1pos bond_center = atom1pos + bond/2. bond_z = bond_center[2] bondlength = np.sqrt(np.dot(bond,bond)) bonds[bond_no] = [bond_z, bondlength, symb[atom1idx], symb[atom2idx]] bond_no += 1 self.bonds = bonds print bonds
def __init__(self, filename): atoms = ase.io.read(filename, format='vasp') cell = atoms.get_cell() symb = atoms.get_atomic_numbers() cutoffs = np.ones( (len(atoms) )) * 1.35 # radius around each atom (half the max bondlength) nl = NeighborList(cutoffs, 0., self_interaction=False, bothways=False) nl.update(atoms) std_len = 2.35805097505 # Loop over atoms bond_no = 0 bonds = np.zeros((nl.nneighbors, 4)) for atom1idx, (indices, offsets, atom1pos) in enumerate( zip(nl.neighbors, nl.displacements, nl.positions)): # Loop over bonds for atom2idx, offset in zip(indices, offsets): #if symb[atom1idx] == symb[atom2idx] == 'Si': atom2pos = nl.positions[atom2idx] + np.dot(offset, cell) #print "pos[%i] = %.2f %.2f %.2f" % (atom1idx, atom1pos[0],atom1pos[1],atom1pos[2]) #print "pos[%i] = %.2f %.2f %.2f" % (atom2idx, atom2pos[0],atom2pos[1],atom2pos[2]) bond = atom2pos - atom1pos bond_center = atom1pos + bond / 2. bond_z = bond_center[2] bondlength = np.sqrt(np.dot(bond, bond)) bonds[bond_no] = [ bond_z, bondlength, symb[atom1idx], symb[atom2idx] ] bond_no += 1 self.bonds = bonds print bonds
def get_fingerprints(atoms): elements =['O','Ru'] Gs = make_symmetry_functions(elements) fp = np.zeros([2, len(Gs['O'])]) cutoff ={'name': 'Cosine', 'kwargs': {'Rc': 6.5}} cutoff_globle = 6.5 _nl = NeighborList(cutoffs=([cutoff_globle / 2.] * len(atoms)), self_interaction=False, bothways=True, skin=0.) _nl.update(atoms) amp_obj = FingerprintCalculator(neighborlist=_nl, Gs=Gs, cutoff=cutoff,fortran=False) amp_obj.initialize(fortran=False,atoms=atoms) n=0 fingerprint = [] for index in range(12,14): symbol = atoms[index].symbol neighborindices, neighboroffsets = _nl.get_neighbors(index) neighborsymbols = [atoms[_].symbol for _ in neighborindices] neighborpositions = [atoms.positions[neighbor] + np.dot(offset, atoms.cell) for (neighbor, offset) in zip(neighborindices, neighboroffsets)] indexfp = amp_obj.get_fingerprint( index, symbol, neighborsymbols, neighborpositions) fp[n] = indexfp[1] n = n+1 return fp
def find_tip_coordination(a, bondlength=2.6, bulk_nn=4): """ Find position of tip in crack cluster from coordination """ nl = NeighborList([bondlength / 2.0] * len(a), skin=0.0, self_interaction=False, bothways=True) nl.update(a) nn = np.array([len(nl.get_neighbors(i)[0]) for i in range(len(a))]) a.set_array('n_neighb', nn) g = a.get_array('groups') y = a.positions[:, 1] above = (nn < bulk_nn) & (g != 0) & (y > a.cell[1, 1] / 2.0) below = (nn < bulk_nn) & (g != 0) & (y < a.cell[1, 1] / 2.0) a.set_array('above', above) a.set_array('below', below) bond1 = np.asscalar(above.nonzero()[0][a.positions[above, 0].argmax()]) bond2 = np.asscalar(below.nonzero()[0][a.positions[below, 0].argmax()]) # These need to be ints, otherwise they are no JSON serializable. a.info['bond1'] = bond1 a.info['bond2'] = bond2 return bond1, bond2
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.calculators.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 calculate_image_center(atoms, watchindex, pdirs): """Calculates the center of the image in the pdir basis coordinates.""" # The first moment of the image around the center atom is calculated. atoms = atoms.copy() from ase.calculators.neighborlist import NeighborList _nl = NeighborList(cutoffs=([6.5 / 2.] * len(atoms)), self_interaction=False, bothways=True, skin=0.) _nl.update(atoms) position = atoms.positions[watchindex] # Step 1: Calculating neighbors of atom. n_indices, n_offsets = _nl.get_neighbors(watchindex) Rs = [atoms.positions[n_index] + np.dot(n_offset, atoms.get_cell()) - position for n_index, n_offset in zip(n_indices, n_offsets)] xtilde = 0. ytilde = 0. ztilde = 0. for rs in Rs: xtilde += rs[0] * np.dot([1, 0, 0], pdirs[0]) xtilde += rs[1] * np.dot([0, 1, 0], pdirs[0]) xtilde += rs[2] * np.dot([0, 0, 1], pdirs[0]) ytilde += rs[0] * np.dot([1, 0, 0], pdirs[1]) ytilde += rs[1] * np.dot([0, 1, 0], pdirs[1]) ytilde += rs[2] * np.dot([0, 0, 1], pdirs[1]) ztilde += rs[0] * np.dot([1, 0, 0], pdirs[2]) ztilde += rs[1] * np.dot([0, 1, 0], pdirs[2]) ztilde += rs[2] * np.dot([0, 0, 1], pdirs[2]) return xtilde, ytilde, ztilde
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.calculators.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) if nb == 0: return n1 = 0 for a in range(self.images.natoms): indices, offsets = nl.get_neighbors(a) 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 find_tip_coordination(a, bondlength=2.6, bulk_nn=4): """ Find position of tip in crack cluster from coordination """ nl = NeighborList([bondlength/2.0]*len(a), skin=0.0, self_interaction=False, bothways=True) nl.update(a) nn = np.array([len(nl.get_neighbors(i)[0]) for i in range(len(a))]) a.set_array('n_neighb', nn) g = a.get_array('groups') y = a.positions[:, 1] above = (nn < bulk_nn) & (g != 0) & (y > a.cell[1,1]/2.0) below = (nn < bulk_nn) & (g != 0) & (y < a.cell[1,1]/2.0) a.set_array('above', above) a.set_array('below', below) bond1 = np.asscalar(above.nonzero()[0][a.positions[above, 0].argmax()]) bond2 = np.asscalar(below.nonzero()[0][a.positions[below, 0].argmax()]) # These need to be ints, otherwise they are no JSON serializable. a.info['bond1'] = bond1 a.info['bond2'] = bond2 return bond1, bond2
def calculate(self, image, key): cutoff = self.globals.cutoff n = NeighborList(cutoffs=[cutoff / 2.] * len(image), self_interaction=False, bothways=True, skin=0.) n.update(image) return [n.get_neighbors(index) for index in xrange(len(image))]
def vacancy_energy(bulk, remove_index=0): Nat = bulk.get_number_of_atoms() vac = bulk.copy() vac.set_calculator(bulk.get_calculator()) nl = NeighborList([a0*sqrt(3.0)/4*0.6]*len(bulk), self_interaction=False, bothways=True) nl.update(bulk) indices, offsets = nl.get_neighbors(remove_index) offset_factor=0.13 for i, offset in zip(indices, offsets): ri = vac.positions[remove_index] - (vac.positions[i] + dot(offset, vac.get_cell())) vac.positions[i] += offset_factor*ri offset_factor += 0.01 del vac[remove_index] # remove an atom to introduce a vacancy # perturb positions vac.rattle(0.1) ## try: model.calculator.set(local_gap_error='local_gap_error') vac.get_potential_energy() unrelaxed_local_gap_error_max = amax(model.calculator.results['local_gap_error']) unrelaxed_local_gap_error_sum = sum(model.calculator.results['local_gap_error']) except: unrelaxed_local_gap_error_max = None unrelaxed_local_gap_error_sum = None if isinstance(model, Potential): model.calculator.set(local_gap_error='') # relax atom positions, holding cell fixed vac = relax_atoms(vac, tol=fmax, traj_file="model-"+model.name+"-test-vacancy-energy.opt.xyz") vac.write("model-"+model.name+"test-vacancy-energy-relaxed.xyz") ## try: model.calculator.set(local_gap_error='local_gap_error') vac.get_potential_energy() relaxed_local_gap_error_max = amax(model.calculator.results['local_gap_error']) relaxed_local_gap_error_sum = sum(model.calculator.results['local_gap_error']) except: relaxed_local_gap_error_max = None relaxed_local_gap_error_sum = None if isinstance(model, Potential): model.calculator.set(local_gap_error='') ## # compute vacancy formation energy as difference of bulk and vac energies print 'bulk cell energy', bulk_energy print 'vacancy cell energy', vac.get_potential_energy() e_form = vac.get_potential_energy() - bulk_energy*vac.get_number_of_atoms()/bulk.get_number_of_atoms() print 'vacancy formation energy', e_form return (e_form, unrelaxed_local_gap_error_max, unrelaxed_local_gap_error_sum, relaxed_local_gap_error_max, relaxed_local_gap_error_sum)
def buildNeighborList(self): atoms = self.atoms nl = NeighborList([0.8 for atom in atoms], self_interaction=False, bothways=True) nl.update(atoms) self.nl = nl neigh = [] for i in range(self.natom): neigh.append(self.sort_nei(i)) # print neigh[i] self.neigh = neigh
def testNeighborlist(self): atoms=read('range',format='lammps') atoms. set_pbc((1, 1, 1)) nl = NeighborList([0.8 for atom in atoms],self_interaction=False,bothways=True) nl.update(atoms) ang=[] for i in xrange(3): indices, offsets = nl. get_neighbors(i) angs=[] for j, offset in zip(indices, offsets): pos= atoms. positions[j] + dot(offset, atoms. get_cell())-atoms.positions[i] ang1=atan2(pos[1],pos[0])+pi angs.append((j,ang1)) newangs=sorted(angs,key=lambda d:d[1]) print newangs
def find_connected(self, index, dmax=None, scale=1.5): """Find the atoms connected to self[index] and return them. If dmax is not None: Atoms are defined to be connected if they are nearer than dmax to each other. If dmax is None: Atoms are defined to be connected if they are nearer than the sum of their covalent radii * scale to each other. """ # set neighbor lists neighborlist = [] if dmax is None: # define neighbors according to covalent radii radii = scale * covalent_radii[self.get_atomic_numbers()] for atom in self: positions = self.positions - atom.position distances = np.sqrt(np.sum(positions**2, axis=1)) radius = scale * covalent_radii[atom.get_atomic_number()] neighborlist.append(np.where(distances < radii + radius)[0]) else: # define neighbors according to distance nl = NeighborList([0.5 * dmax] * len(self), skin=0) nl.update(self) for i, atom in enumerate(self): neighborlist.append(list(nl.get_neighbors(i)[0])) connected = list(neighborlist[index]) isolated = False while not isolated: isolated = True for i in connected: for j in neighborlist[i]: if j in connected: pass else: connected.append(j) isolated = False atoms = Cluster() for i in connected: atoms.append(self[i]) return atoms
def testNeighborlist(self): atoms = read('range', format='lammps') atoms.set_pbc((1, 1, 1)) nl = NeighborList([0.8 for atom in atoms], self_interaction=False, bothways=True) nl.update(atoms) ang = [] for i in xrange(3): indices, offsets = nl.get_neighbors(i) angs = [] for j, offset in zip(indices, offsets): pos = atoms.positions[j] + dot( offset, atoms.get_cell()) - atoms.positions[i] ang1 = atan2(pos[1], pos[0]) + pi angs.append((j, ang1)) newangs = sorted(angs, key=lambda d: d[1]) print newangs
def check_min_dist(totalsol, type='Defect', nat=None, min_len=0.7, STR=''): if type == 'Defect' or type == 'Crystal' or type == 'Surface': if nat == None: nat = len(totalsol) cutoffs = [2.0 for one in totalsol] nl = NeighborList(cutoffs, bothways=True, self_interaction=False) nl.update(totalsol) for one in totalsol[0:nat]: nbatoms = Atoms() nbatoms.append(one) indices, offsets = nl.get_neighbors(one.index) for index, d in zip(indices, offsets): index = int(index) sym = totalsol[index].symbol pos = totalsol[index].position + numpy.dot( d, totalsol.get_cell()) at = Atom(symbol=sym, position=pos) nbatoms.append(at) while True: dflag = False for i in range(1, len(nbatoms)): d = nbatoms.get_distance(0, i) if d < min_len: nbatoms.set_distance(0, i, min_len + .01, fix=0.5) STR += '--- WARNING: Atoms too close (<0.7A) - Implement Move ---\n' dflag = True if dflag == False: break for i in range(len(indices)): totalsol[indices[i]].position = nbatoms[i + 1].position totalsol[one.index].position = nbatoms[0].position nl.update(totalsol) elif type == 'Cluster': for i in range(len(totalsol)): for j in range(len(totalsol)): if i != j: d = totalsol.get_distance(i, j) if d < min_len: totalsol.set_distance(i, j, min_len, fix=0.5) STR += '--- WARNING: Atoms too close (<0.7A) - Implement Move ---\n' else: print 'WARNING: In Check_Min_Dist in EvalEnergy: Structure Type not recognized' return totalsol, STR
def find_connected(self, index, dmax=None, scale=1.5): """Find the atoms connected to self[index] and return them. If dmax is not None: Atoms are defined to be connected if they are nearer than dmax to each other. If dmax is None: Atoms are defined to be connected if they are nearer than the sum of their covalent radii * scale to each other. """ if index < 0: index = len(self) + index # set neighbor lists if dmax is None: # define neighbors according to covalent radii radii = scale * covalent_radii[self.get_atomic_numbers()] else: # define neighbors according to distance radii = [0.5 * dmax] * len(self) nl = NeighborList(radii, skin=0, self_interaction=False, bothways=True) nl.update(self) connected = [index] + list(nl.get_neighbors(index)[0]) isolated = False while not isolated: isolated = True for i in connected: for j in nl.get_neighbors(i)[0]: if j in connected: pass else: connected.append(j) isolated = False atoms = Cluster() for i in connected: atoms.append(self[i]) return atoms
class NeighborPairs: """Class for looping over pairs of atoms using a neighbor list.""" def __init__(self, cutoff_a, cell_cv, pbc_c, self_interaction): self.neighbors = NeighborList(cutoff_a, skin=0, sorted=True, self_interaction=self_interaction) self.atoms = Atoms('X%d' % len(cutoff_a), cell=cell_cv, pbc=pbc_c) # Warning: never use self.atoms.get_scaled_positions() for # anything. Those positions suffer from roundoff errors! def set_positions(self, spos_ac): self.spos_ac = spos_ac self.atoms.set_scaled_positions(spos_ac) self.neighbors.update(self.atoms) def iter(self): cell_cv = self.atoms.cell for a1, spos1_c in enumerate(self.spos_ac): a2_a, offsets = self.neighbors.get_neighbors(a1) for a2, offset in zip(a2_a, offsets): spos2_c = self.spos_ac[a2] + offset R_c = np.dot(spos2_c - spos1_c, cell_cv) yield a1, a2, R_c, offset
i, offsets = nl.get_neighbors(a) for j in i: c[j] += 1 c[a] += len(i) d += (((R[i] + np.dot(offsets, cell) - R[a])**2).sum(1)**0.5).sum() return d, c for sorted in [False, True]: for p1 in range(2): for p2 in range(2): for p3 in range(2): print p1, p2, p3 atoms.set_pbc((p1, p2, p3)) nl = NeighborList(atoms.numbers * 0.2 + 0.5, skin=0.0, sorted=sorted) nl.update(atoms) d, c = count(nl, atoms) atoms2 = atoms.repeat((p1 + 1, p2 + 1, p3 + 1)) 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)
class EMT: disabled = False # Set to True to disable (asap does this). def __init__(self): self.energy = None if self.disabled: print >> sys.stderr, """ ase.EMT has been disabled by Asap. Most likely, you intended to use Asap's EMT calculator, but accidentally imported ase's EMT calculator after Asap's. This could happen if your script contains the lines from asap3 import * from ase.calculators.emt import EMT Swap the two lines to solve the problem. (or 'from ase import *' in older versions of ASE.) Swap the two lines to solve the problem. In the UNLIKELY event that you actually wanted to use ase.calculators.emt.EMT although asap3 is loaded into memory, please reactivate it with the command ase.calculators.emt.EMT.disabled = False """ raise RuntimeError('ase.EMT has been disabled. ' + 'See message printed above.') def get_spin_polarized(self): return False def initialize(self, atoms): self.par = {} self.rc = 0.0 self.numbers = atoms.get_atomic_numbers() maxseq = 0.0 seen = {} for Z in self.numbers: if Z not in seen: seen[Z] = True ss = parameters[chemical_symbols[Z]][1] * Bohr if maxseq < ss: maxseq = ss 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) for Z in self.numbers: if Z not in self.par: p = parameters[chemical_symbols[Z]] 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} #if rc + 0.5 > self.rc: # self.rc = rc + 0.5 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'] * # exp(eta1 * (p1['s0'] - p2['s0']))) 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 + 0.25] * len(atoms), self_interaction=False) def update(self, atoms): if (self.energy is None or len(self.numbers) != len(atoms) or (self.numbers != atoms.get_atomic_numbers()).any()): self.initialize(atoms) self.calculate(atoms) elif ((self.positions != atoms.get_positions()).any() or (self.pbc != atoms.get_pbc()).any() or (self.cell != atoms.get_cell()).any()): self.calculate(atoms) def calculation_required(self, atoms, quantities): if len(quantities) == 0: return False return (self.energy is None or len(self.numbers) != len(atoms) or (self.numbers != atoms.get_atomic_numbers()).any() or (self.positions != atoms.get_positions()).any() or (self.pbc != atoms.get_pbc()).any() or (self.cell != atoms.get_cell()).any()) def get_potential_energy(self, atoms): self.update(atoms) return self.energy def get_numeric_forces(self, atoms): self.update(atoms) p = atoms.positions p0 = p.copy() forces = np.empty_like(p) eps = 0.0001 for a in range(len(p)): for c in range(3): p[a, c] += eps self.calculate(atoms) de = self.energy p[a, c] -= 2 * eps self.calculate(atoms) de -= self.energy p[a, c] += eps forces[a, c] = -de / (2 * eps) p[:] = p0 return forces def get_forces(self, atoms): self.update(atoms) return self.forces.copy() def get_stress(self, atoms): raise NotImplementedError def calculate(self, atoms): self.positions = atoms.get_positions().copy() self.cell = atoms.get_cell().copy() self.pbc = atoms.get_pbc().copy() self.nl.update(atoms) self.energy = 0.0 self.sigma1[:] = 0.0 self.forces[:] = 0.0 natoms = len(atoms) for a1 in range(natoms): Z1 = self.numbers[a1] p1 = self.par[Z1] ksi = self.ksi[Z1] neighbors, offsets = self.nl.get_neighbors(a1) offsets = np.dot(offsets, atoms.cell) for a2, offset in zip(neighbors, offsets): d = self.positions[a2] + offset - self.positions[a1] r = sqrt(np.dot(d, d)) if r < self.rc + 0.5: Z2 = self.numbers[a2] p2 = self.par[Z2] self.interact1(a1, a2, d, r, p1, p2, ksi[Z2]) for a in range(natoms): Z = self.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'])) e = p['E0'] * ((1 + x) * y - 1) + z self.energy += p['E0'] * ((1 + x) * y - 1) + z for a1 in range(natoms): Z1 = self.numbers[a1] p1 = self.par[Z1] ksi = self.ksi[Z1] neighbors, offsets = self.nl.get_neighbors(a1) offsets = np.dot(offsets, atoms.cell) for a2, offset in zip(neighbors, offsets): d = self.positions[a2] + offset - self.positions[a1] r = sqrt(np.dot(d, d)) if r < self.rc + 0.5: Z2 = self.numbers[a2] p2 = self.par[Z2] self.interact2(a1, a2, d, r, p1, p2, ksi[Z2]) 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 def set_atoms(self,*args,**kwargs): 'empty function for compatibility with other calculators and tests' pass
def lattice_alteration_nn(indiv, Optimizer): """Move function to perform random move along random axis for nearest neighbor distance to random atoms Inputs: indiv = Individual class object to be altered Optimizer = Optimizer class object with needed parameters Outputs: indiv = Altered Individual class object """ if 'MU' in Optimizer.debug: debug = True else: debug = False if Optimizer.structure=='Defect': if Optimizer.isolate_mutation: indc,indb,vacant,swaps,stro = find_defects(indiv[0],Optimizer.solidbulk,0) ind = indc.copy() ind.extend(indb) else: ind=indiv[0].copy() indc=indiv[0].copy() else: ind=indiv[0].copy() indc=indiv[0].copy() if len(indc) != 0: ctoff1 = [1.0 for one in ind] nl = NeighborList(ctoff1, bothways=True, self_interaction=False) nl.update(ind) try: natomsmove=random.randint(1,len(indc)/2) except ValueError: natomsmove=1 passn=0 for count in range(natomsmove): try: indexmv = random.choice([i for i in range(len(indc))]) indices, offsets = nl.get_neighbors(indexmv) nns = Atoms() nns.append(ind[indexmv]) for index, d in zip(indices,offsets): index = int(index) pos = ind[index].position + numpy.dot(d,ind.get_cell()) nns.append(Atom(symbol=ind[index].symbol, position=pos)) dist = [nns.get_distance(0,i) for i in range(1, len(nns))] r = sum(dist)/len(dist) dir = random.choice([[1,0,0],[-1,0,0],[0,1,0],[0,-1,0],[0,0,1],[0,0,-1]]) ind[indexmv].position += [i*r for i in dir] except: passn+=1 indiv[0]=ind.copy() else: natomsmove=0 passn=0 Optimizer.output.write('Lattice Alteration NN Mutation performed on individual\n') Optimizer.output.write('Index = '+repr(indiv.index)+'\n') natomsmove-=passn Optimizer.output.write('Number of atoms moved = '+repr(natomsmove)+'\n') Optimizer.output.write(repr(indiv[0])+'\n') muttype='LANN'+repr(natomsmove) if indiv.energy==0: indiv.history_index=indiv.history_index+'m'+muttype else: indiv.history_index=repr(indiv.index)+'m'+muttype return indiv
def find_defects(solid, bulko, rcutoff, atomlistcheck=False, trackvacs=False, trackswaps=False, debug=False, dcheck=0.6): """Function to find interstitials, vacancies, and substitutional atoms (swaps) in a defected structure. Identifies species by comparison to perfect structure. Inputs: solid = ASE atoms class for defected structure bulko = ASE atoms class for perfect structure rcutoff = float value of distance to surrounding atoms to include atomlistcheck = False/list of atom types and concentrations according to atomlist format trackvacs = True/False whether or not to identify vacancies in defect trackswaps = True/False whether or not to identify substitutional defects debug = False/file object to write debug structures""" # Combine perfect and defect structures together b = bulko.copy() b.extend(solid) b.set_pbc(True) #Debug: Write solid and bulko to file if debug: print len(bulko) write_xyz(debug, b, 'Find Ints: Solid and Bulko') # Identify nearest neighbor atoms for each atom in perfect structure ntot = len(bulko) ctoff1 = [1.2 for one in b] nl = NeighborList(ctoff1, bothways=True, self_interaction=False) nl.update(b) slist = [] blist = [] wlist = [] #Loop over each atom in perfect structure for one in range(ntot): indices, offsets = nl.get_neighbors(one) for index, d in zip(indices, offsets): index = int(index) if index >= ntot: pos = b[index].position + numpy.dot(d, bulko.get_cell()) natm1 = Atom(position=pos) dist, dx, dy, dz = calc_dist(b[one], natm1) if dist <= dcheck: #Assume atoms closer than 0.6 Angstroms to be equivalent slist.append(index - ntot) blist.append(one) if b[one].symbol == b[index].symbol: wlist.append(index - ntot) #Identify those atoms corresponding to interstitials, vacancies, and substitutions oslist = [atm.index for atm in solid if atm.index not in slist] vlist = [atm.index for atm in bulko if atm.index not in blist] swlist = [ atm.index for atm in solid if atm.index not in wlist and atm.index not in oslist ] # Create Atoms objects for each identified defect ntot = len(solid) cluster = Atoms() for one in oslist: cluster.append(solid[one]) vacant = Atoms() if trackvacs == True: for one in vlist: vacant.append( Atom(symbol=bulko[one].symbol, position=bulko[one].position)) solid.append(Atom(symbol='X', position=bulko[one].position)) oslist.append(len(solid) - 1) stro = 'Cluster Identified with length = {0}\nIdentified {1} vacancies\n'.format( len(cluster), len(vlist)) swaps = Atoms() if trackswaps == True: for one in swlist: swaps.append(solid[one]) oslist.append(one) stro = 'Cluster Identified with length = {0}\nIdentified {1} swaps\n'.format( len(cluster), len(swlist)) else: stro = 'Cluster Identified with length = {0}\n'.format(len(cluster)) #Debug: write cluster to file if debug: b = cluster.copy() write_xyz(debug, b, 'Find Ints: Cluster') debug.flush() print 'Found cluster size = ', len(b) # Identify atoms surrounding the identified defects in the defected structure box = Atoms() bulki = Atoms() if rcutoff != 0: if rcutoff > 2.0: cutoffs = [rcutoff for one in solid] else: cutoffs = [2.0 for one in solid] solid.set_pbc(True) nl = NeighborList(cutoffs, bothways=True, self_interaction=False) nl.update(solid) nbatmsd = [] repinds = [] for one in oslist: if one not in repinds: if one < ntot: nbatmsd.append((0, one)) repinds.append(one) for one in oslist: indices, offsets = nl.get_neighbors(one) for index, d in zip(indices, offsets): index = int(index) if index not in repinds and index < ntot: opos = copy.copy(solid[index].position) solid[index].position = solid[index].position + numpy.dot( d, solid.get_cell()) dist = solid.get_distance(one, index) solid[index].position = opos if dist <= rcutoff: nbatmsd.append((dist, index)) repinds.append(index) else: nbatmsd = [] repinds = [] for one in oslist: if one not in repinds: if one < ntot: nbatmsd.append((0, one)) repinds.append(one) nbatmsd = sorted(nbatmsd, key=lambda one: one[0], reverse=True) indices = [] natomsbox = 0 # Select only atoms closest to defects that satisfy concentrations specified by atomlist given in atomlistcheck if atomlistcheck: for sym, c, m, u in atomlistcheck: i = 0 nbsym = [one for one in nbatmsd if solid[one[1]].symbol == sym] if len(nbsym) > c: while i < c: a = nbsym.pop() box.append(solid[a[1]]) indices.append(a[1]) i += 1 else: for a in nbsym: box.append(solid[a[1]]) indices.append(a[1]) i += 1 if len(box) - natomsbox < c: try: while True: for n in range(len(nbatmsd) - 1, -1, -1): inds, offsets = nl.get_neighbors(nbatmsd[n][1]) for one, d in zip(inds, offsets): if len(box) - natomsbox < c: if one not in indices and one < ntot and solid[ one].symbol == sym: opos = copy.copy( solid[one].position) solid[one].position = solid[ one].position + numpy.dot( d, solid.get_cell()) dist = solid.get_distance( nbatmsd[n][1], one) solid[one].position = opos if dist <= rcutoff * 5.0: box.append(solid[one]) indices.append(one) else: raise StopIteration() for one, d in zip(inds, offsets): if len(box) - natomsbox < c: if one not in indices and one < ntot and solid[ one].symbol == sym: opos = copy.copy( solid[one].position) solid[one].position = solid[ one].position + numpy.dot( d, solid.get_cell()) dist = solid.get_distance( nbatmsd[n][1], one) solid[one].position = opos box.append(solid[one]) indices.append(one) else: raise StopIteration() except StopIteration: pass natomsbox = len(box) #Double check for sanity for sym, c, m, u in atomlistcheck: symsbox = [one for one in box if one.symbol == sym] if len(symsbox) != c: stro += 'WARNING!!!! : FAILURE IN FIND_DEFECTS TO MATCH PROVIDED ATOMLIST. DEBUG!!!!\n' # If atomlistcheck is False then use all the atoms in the given cutoff distance else: for a in nbatmsd: box.append(solid[a[1]]) indices.append(a[1]) # Add remaining atoms in defect to defected bulk atoms object for one in range(len(solid)): if one not in indices and one < ntot: bulki.append(solid[one]) #Check for accidental vacancy admissions #checklist=[atm for atm in box if atm.symbol=='X'] #checklist.extend([atm for atm in bulki if atm.symbol=='X']) #Set up new individual indiv = box.copy() bulki.set_cell(bulko.get_cell()) indiv.set_cell(bulko.get_cell()) bulki.set_pbc(True) indiv.set_pbc(True) stro += 'Atomlist check = {0}\n'.format(atomlistcheck) stro += 'Bulko = {0}\n'.format(bulko) stro += 'New individual ({0} atoms) : {1}\n'.format(len(indiv), indiv) stro += 'New bulki ({0} atoms) : {1}\n'.format(len(bulki), bulki) #Debug: write new indiv to file if debug: b = indiv.copy() write_xyz(debug, b, 'Find Ints: New Individual') #Debug: write new bulki to file b = bulki.copy() write_xyz(debug, b, 'Find Ints: New Bulki') debug.flush() print len(bulko) return indiv, bulki, vacant, swaps, stro
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 #print d #print r2**.5 #print offsets #print f #print neighbors forces[a1] -= f.sum(axis=0) for a2, f2 in zip(neighbors, f): forces[a2] += f2 stress += np.dot(f.T, d) #stress = np.dot(stress, cell) stress += stress.T.copy() stress *= -0.5 / self.atoms.get_volume() self.results['energy'] = energy self.results['forces'] = forces self.results['stress'] = stress.flat[[0, 4, 8, 5, 2, 1]]
def newclus(ind1, ind2, Optimizer): """Select a box in the cluster configuration""" if 'CX' in Optimizer.debug: debug = True else: debug = False Optimizer.output.write('Box Cluster Cx between individual {} and individual {}'.format(repr(ind1.index), repr(ind2.index))) # Perserve starting conditions of individual solid1 = ind1[0].copy() solid2 = ind2[0].copy() cello1 = ind1[0].get_cell() cello2 = ind2[0].get_cell() cell1 = numpy.maximum.reduce(solid1.get_positions()) cell1m = numpy.minimum.reduce(solid1.get_positions()) cell2 = numpy.maximum.reduce(solid2.get_positions()) cell2m = numpy.minimum.reduce(solid2.get_positions()) cell = numpy.minimum(cell1, cell2) pbc1 = solid1.get_pbc() pbc2 = solid2.get_pbc() # Get starting concentrations and number of atoms nat1 = len(solid1) nat2 = len(solid2) # Pick a origin point for box in the cell pt1 = random.choice(solid1) pt1f = [(pt1.position[i]-cell1m[i])/cell1[i] for i in range(3)] pt2 = [pt1f[i]*cell2[i]+cell2m[i] for i in range(3)] solid2.append(Atom(position=pt2)) pt2 = solid2[len(solid2)-1] # Find max neighborsize of circle cut r = random.uniform(0, min(nat1, nat2)/5.0) if debug: print('DEBUG CX: Point one = {}'.format(pt1.position)) print('DEBUG CX: Point two = {}'.format(pt2.position)) # Find atoms within sphere of neighborsize r for both individuals # Make sure that crossover is only selection of atoms not all while True: ctoff = [r for on in solid1] nl = NeighborList(ctoff, bothways=True, self_interaction=False) nl.update(solid1) indices1, offsets = nl.get_neighbors(pt1.index) if len(indices1) == 0: r = r*1.2 elif len(indices1) < nat1*.75: break else: r = r*0.8 if debug: print('Neighborsize of box = {}'.format(repr(r))) print('Position in solid1 = {}'.format(repr(pt1.position))) print('Position in solid2 = {}'.format(repr(pt2.position))) group1 = Atoms(cell=solid1.get_cell(), pbc=solid1.get_pbc()) group1.append(pt1) indices1a = [pt1.index] for index, d in zip(indices1, offsets): if index not in indices1a: index = int(index) pos = solid1[index].position + numpy.dot(d, solid1.get_cell()) group1.append(Atom(symbol=solid1[index].symbol, position=pos)) indices1a.append(index) indices1 = indices1a ctoff = [r for on in solid2] nl = NeighborList(ctoff, bothways=True, self_interaction=False) nl.update(solid2) indices2, offsets = nl.get_neighbors(pt2.index) group2 = Atoms(cell=solid2.get_cell(), pbc=solid2.get_pbc()) indices2a = [] for index, d in zip(indices2, offsets): if index not in indices2a: index = int(index) pos = solid2[index].position + numpy.dot(d, solid2.get_cell()) group2.append(Atom(symbol=solid2[index].symbol, position=pos)) indices2a.append(index) indices2 = indices2a if len(indices2) == 0: for one in group1: while True: sel = random.choice(solid2) if sel.symbol == one.symbol: if sel.index not in indices2: group2.append(sel) indices2.append(sel.index) break if Optimizer.forcing == 'Concentration': symlist = list(set(group1.get_chemical_symbols())) seplist = [[atm for atm in group2 if atm.symbol == sym] for sym in symlist] group2n = Atoms(cell=group2.get_cell(), pbc=group2.get_pbc()) indices2n = [] dellist = [] for one in group1: sym1 = one.symbol listpos = [i for i, s in enumerate(symlist) if s == sym1][0] if len(seplist[listpos]) > 0: pos = random.choice(range(len(seplist[listpos]))) group2n.append(seplist[listpos][pos]) indices2n.append(indices2[seplist[listpos][pos].index]) del seplist[listpos][pos] else: dellist.append(one.index) if len(dellist) != 0: dellist.sort(reverse=True) for one in dellist: del group1[one] del indices1[one] indices2n.append(pt2.index) indices2 = indices2n group2 = group2n.copy() else: dellist = [] while len(group2) < len(group1)-len(dellist): # Too many atoms in group 1 dellist.append(random.choice(group1).index) if len(dellist) != 0: dellist.sort(reverse=True) for one in dellist: del group1[one] del indices1[one] dellist = [] while len(group1) < len(group2)-len(dellist): # Too many atoms in group 2 dellist.append(random.choice(group2).index) if len(dellist) != 0: dellist.sort(reverse=True) for one in dellist: del group2[one] del indices2[one] other2 = Atoms(cell=solid2.get_cell(), pbc=solid2.get_pbc()) for one in solid2: if one.index not in indices2: other2.append(one) other1 = Atoms(cell=solid1.get_cell(), pbc=solid1.get_pbc()) for one in solid1: if one.index not in indices1: other1.append(one) # Exchange atoms in sphere and build new solids nsolid1 = other1.copy() nsolid1.extend(group2.copy()) nsolid2 = other2.copy() nsolid2.extend(group1.copy()) # DEBUG: Write crossover to file if debug: write_xyz(Optimizer.debugfile, nsolid1, 'CX(randalloybx):nsolid1') write_xyz(Optimizer.debugfile, nsolid2, 'CX(randalloybx):nsolid2') # DEBUG: Check structure of atoms exchanged for sym, c, m, u in Optimizer.atomlist: if Optimizer.structure == 'Defect': nc = len([atm for atm in nsolid1 if atm.symbol == sym]) nc += len([atm for atm in ind1.bulki if atm.symbol == sym]) oc = len([atm for atm in solid1 if atm.symbol == sym]) oc += len([atm for atm in ind1.bulki if atm.symbol == sym]) else: nc = len([atm for atm in nsolid1 if atm.symbol == sym]) oc = len([atm for atm in solid1 if atm.symbol == sym]) Optimizer.output.write('CX(clustbx):New solid1 contains '+repr(nc)+' '+repr(sym)+' atoms\n') if debug: print('DEBUG CX: New solid1 contains {} {} atoms'.format(repr(nc), repr(sym))) if oc != nc: # pdb.set_trace() print('CX: Issue in maintaining atom concentration\n Dropping new individual') Optimizer.output.write('CX: Issue in maintaining atom concentration\n Dropping new individual 1\n') nsolid1 = solid1 for sym, c, m, u in Optimizer.atomlist: if Optimizer.structure == 'Defect': nc = len([atm for atm in nsolid2 if atm.symbol == sym]) nc += len([atm for atm in ind2.bulki if atm.symbol == sym]) oc = len([atm for atm in solid2 if atm.symbol == sym]) oc += len([atm for atm in ind2.bulki if atm.symbol == sym]) else: nc = len([atm for atm in nsolid2 if atm.symbol == sym]) oc = len([atm for atm in solid2 if atm.symbol == sym]) Optimizer.output.write('CX(clustbx):New solid2 contains '+repr(nc)+' '+repr(sym)+' atoms\n') if debug: print('DEBUG CX: New solid2 contains {} and {} atoms'.format(repr(nc), repr(sym))) if oc != nc: #pdb.set_trace() print('CX: Issue in maintaining atom concentration\n Dropping new individual') Optimizer.output.write('CX: Issue in maintaining atom concentration\n Dropping new individual 2\n') solid2.pop() nsolid2 = solid2 if Optimizer.forcing != 'Concentration': for i in range(len(Optimizer.atomlist)): atms1 = [inds for inds in nsolid1 if inds.symbol == Optimizer.atomlist[i][0]] atms2 = [inds for inds in nsolid2 if inds.symbol == Optimizer.atomlist[i][0]] if len(atms1) == 0: if len(atms2) == 0: nsolid1[random.randint(0, len(indi1)-1)].symbol == Optimizer.atomlist[i][0] nsolid2[random.randint(0, len(indi2)-1)].symbol == Optimizer.atomlist[i][0] else: nsolid1.append(atms2[random.randint(0, len(atms2)-1)]) nsolid1.pop(random.randint(0, len(nsolid1)-2)) else: if len(atms2) == 0: nsolid2.append(atms1[random.randint(0, len(atms1)-1)]) nsolid2.pop(random.randint(0, len(nsolid2)-2)) nsolid1.set_cell(cello1) nsolid2.set_cell(cello2) nsolid1.set_pbc(pbc1) nsolid2.set_pbc(pbc2) ind1[0] = nsolid1.copy() ind2[0] = nsolid2.copy() return ind1, ind2
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 or 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 is part of the EAM calculator. A single element EAM potential is defined by three functions: the embedded energy, electron density and the pair potential (phi). A two element alloy contains the individual three functions for each element plus cross pair interactions. The ADP potential has additional two additional sets of data to define the dipole and quadrupole directional terms for each alloy and their cross interactions. 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/ 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 slpline 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 eam import EAM mishin = EAM('Al99.eam.alloy') mishin.write_file('new.eam.alloy') mishin.plot() slab.set_calculator(mishin) slab.get_potential_energy() slab.get_forces() Arguments ========= ========================= ==================================================== Keyword Description ========================= ==================================================== ``fileobj`` file of potential in ``.alloy`` or ``.adp`` format (This is generally all you need to supply) ``elements[N]`` array of N element abreviations ``embedded_energy[N]`` arrays of embedded energy functions ``electron_density[N]`` arrays of electron density functions ``phi[N,N]`` arrays of pair potential funtions ``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], q[N,N]`` ADP dipole and quadrupole function ``d_d[N,N], d_q[N,N]`` ADP dipole and quadrupole derivative functions ========================= ==================================================== Additional parameters for writing potential files ================================================= The following parameters are only required for writing a potential in ``.alloy`` or ``.adp`` format file. ========================= ==================================================== Keyword Description ========================= ==================================================== ``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 ``drho`` Increment for sampling density ``nr`` No. of radial points ``dr`` Increment for sampling radius ========================= ==================================================== Special features ================ ``.plot()`` Plots the individual funtions. 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 compatable 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. * Any supplied values will overide values read from the file. * The derivative functions, if supplied, are only used to calculate forces. .. _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 """ def __init__(self, fileobj=None, **kwargs): if fileobj != None: self.read_file(fileobj) valid_args = ('elements', 'header', 'drho', 'dr', 'cutoff', 'atomic_number', 'mass', 'a', 'lattice', 'embedded_energy', 'electron_density', 'phi', # adp terms 'd', 'q', 'd_d', 'd_q', # derivatives 'd_embedded_energy', 'd_electron_density', 'd_phi', 'skin', 'form', 'Z', 'nr', 'nrho', 'mass') # these variables need to be set, but are not read in if 'skin' not in kwargs: self.skin = 1.0 # set any additional keyword arguments for arg, val in kwargs.iteritems(): if arg in valid_args: setattr(self, arg, val) else: raise RuntimeError('unknown keyword arg "%s" : not in %s' % (arg, valid_args)) # initialise the state of the calculation self.Nelements = len(self.elements) self.energy = 0.0 self.positions = None self.cell = None self.pbc = None def set_form(self, fileobj): extension = os.path.splitext(fileobj)[1] if extension == '.eam': self.form = 'eam' raise NotImplementedError elif extension == '.alloy': self.form = 'alloy' elif extension == '.adp': self.form = 'adp' else: raise RuntimeError('unknown file extension type: %s' % extension) def read_file(self, fileobj): """Reads a LAMMPS EAM file in alloy format and creates the interpolation functions from the data """ if isinstance(fileobj, str): f = open(fileobj) self.set_form(fileobj) else: f = fileobj lines = f.readlines() self.header = lines[:3] i = 3 # make the data one long line so as not to care how its formatted data = [] for line in lines[i:]: data.extend(line.split()) 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, self.Nelements): self.rphi_data[i, j] = 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 # this section turns the file data into three functions (and # derivative functions) that define the potential self.embedded_energy = np.zeros(self.Nelements, object) self.electron_density = np.zeros(self.Nelements, object) self.d_embedded_energy = np.zeros(self.Nelements, object) self.d_electron_density = np.zeros(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.zeros([self.Nelements, self.Nelements], object) self.d_phi = np.zeros([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): if self.form == 'eam': # not stored as rphi # should we igore the first point for eam ? raise RuntimeError('.eam format not yet supported') self.phi[i, j] = spline( self.r[1:], self.rphi_data[i, j][1:], k=3) else: 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] if (self.form == 'adp'): self.read_adp_data(data, d) def read_adp_data(self, data, d): """reads in the extra data fro the adp format""" self.d_data = np.zeros([self.Nelements, self.Nelements, self.nr]) # should be non symetrical combinations of 2 for i in range(self.Nelements): for j in range(i, self.Nelements): self.d_data[i, j] = data[d:d + self.nr] d += self.nr self.q_data = np.zeros([self.Nelements, self.Nelements, self.nr]) # should be non symetrical combinations of 2 for i in range(self.Nelements): for j in range(i, self.Nelements): self.q_data[i, j] = data[d:d + self.nr] d += self.nr self.d = np.zeros([self.Nelements, self.Nelements], object) self.d_d = np.zeros([self.Nelements, self.Nelements], object) self.q = np.zeros([self.Nelements, self.Nelements], object) self.d_q = np.zeros([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 write_file(self, filename, nc=1, form='%.8e'): """Writes out the model of the eam_alloy in lammps alloy format to file with name = 'filename' with a data format that is nc columns wide. Note: array lengths need to be an exact multiple of nc """ f = open(filename, 'w') assert self.nr % nc == 0 assert self.nrho % nc == 0 if not hasattr(self, 'header'): self.header = """Unknown EAM potential file Generated from eam.py blank """ for line in self.header: f.write(line) f.write('%d ' % self.Nelements) for i in range(self.Nelements): f.write('%s ' % str(self.elements[i])) f.write('\n') f.write('%d %f %d %f %f \n' % (self.nrho, self.drho, self.nr, self.dr, self.cutoff)) # 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]))) np.savetxt(f, self.embedded_energy[i](rhos).reshape(self.nrho / nc, nc), fmt=nc * [form]) np.savetxt(f, self.electron_density[i](rs).reshape(self.nr / nc, nc), fmt=nc * [form]) # 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 * [form]) if self.form == 'adp': # these are the u(r) 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) 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 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]) # check if anything has changed to require re-calculation if (self.positions is None or len(self.positions) != len(atoms.get_positions()) or (self.positions != atoms.get_positions()).any() or (self.cell != atoms.get_cell()).any() or (self.pbc != atoms.get_pbc()).any()): # 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([atoms.calc.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.skin, self_interaction=False, bothways=True) self.neighbors.update(atoms) self.calculate(atoms) def get_forces(self, atoms): # calculate the forces based on derivatives of the three EAM functions self.update(atoms) self.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.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.forces[i] += adp_forces return self.forces.copy() def get_stress(self, atoms): ## currently not implemented raise NotImplementedError def calculate(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 energy = 0.0 adp_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. energy += pair_energy density = np.sum( self.electron_density[j_index](r[nearest][use])) self.total_density[i] += density if self.form == 'adp': self.mu[i] += self.eam_dipole( r[nearest][use], rvec[nearest][use], self.d[self.index[i], j_index]) self.lam[i] += self.eam_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]) # print 'pair energy',energy # print 'embed energy',embedding_energy energy += embedding_energy if self.form == 'adp': mu_energy = np.sum(self.mu**2) / 2. adp_energy += mu_energy # print 'adp mu', mu_energy lam_energy = np.sum(self.lam**2) / 2. adp_energy += lam_energy # print 'adp lam', lam_energy for i in range(len(atoms)): # this is the atom to be embedded adp_energy -= np.sum(self.lam[i].trace()**2) / 6. # print 'energy no adp', energy energy += adp_energy self.positions = atoms.positions.copy() self.cell = atoms.get_cell().copy() self.energy_free = energy self.energy_zero = energy 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 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 eam_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 eam_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
# Surface energy computed with DFT (PBE-GGA) surface_energy = 0.161 * (J / m**2) * 10 a = atoms.copy() b = a * [nx, 1, 1] b.center() b.positions[:, 0] += 0.5 b.set_scaled_positions(b.get_scaled_positions()) mask = ((b.positions[:, 0] > 0.) & (b.positions[:, 0] < 2 * a.cell[0, 0]) & (b.positions[:, 1] < (a.cell[1, 1] / 2.0 + final_height / 2.0)) & (b.positions[:, 1] > (a.cell[1, 1] / 2.0 - final_height / 2.0))) nl = NeighborList([1.0] * len(b), self_interaction=False, bothways=True) nl.update(b) term = Atoms() if tetra: for i in range(len(b)): if not mask[i]: continue indices, offsets = nl.get_neighbors(i) if b.numbers[i] == 8 and mask[indices].sum() == 0: mask[i] = False if b.numbers[i] == 14: # complete tetrahedra for (j, o) in zip(indices, offsets): if b.numbers[j] == 8: mask[j] = True
def lattice_alteration_nn(indiv, Optimizer): """Move function to perform random move along random axis for nearest neighbor distance to random atoms Inputs: indiv = Individual class object to be altered Optimizer = Optimizer class object with needed parameters Outputs: indiv = Altered Individual class object """ if 'MU' in Optimizer.debug: debug = True else: debug = False if Optimizer.structure == 'Defect': if Optimizer.isolate_mutation: indc, indb, vacant, swaps, stro = find_defects( indiv[0], Optimizer.solidbulk, 0) ind = indc.copy() ind.extend(indb) else: ind = indiv[0].copy() indc = indiv[0].copy() else: ind = indiv[0].copy() indc = indiv[0].copy() if len(indc) != 0: ctoff1 = [1.0 for one in ind] nl = NeighborList(ctoff1, bothways=True, self_interaction=False) nl.update(ind) try: natomsmove = random.randint(1, len(indc) / 2) except ValueError: natomsmove = 1 passn = 0 for count in range(natomsmove): try: indexmv = random.choice([i for i in range(len(indc))]) indices, offsets = nl.get_neighbors(indexmv) nns = Atoms() nns.append(ind[indexmv]) for index, d in zip(indices, offsets): index = int(index) pos = ind[index].position + numpy.dot(d, ind.get_cell()) nns.append(Atom(symbol=ind[index].symbol, position=pos)) dist = [nns.get_distance(0, i) for i in range(1, len(nns))] r = sum(dist) / len(dist) dir = random.choice([[1, 0, 0], [-1, 0, 0], [0, 1, 0], [0, -1, 0], [0, 0, 1], [0, 0, -1]]) ind[indexmv].position += [i * r for i in dir] except: passn += 1 indiv[0] = ind.copy() else: natomsmove = 0 passn = 0 Optimizer.output.write( 'Lattice Alteration NN Mutation performed on individual\n') Optimizer.output.write('Index = ' + repr(indiv.index) + '\n') natomsmove -= passn Optimizer.output.write('Number of atoms moved = ' + repr(natomsmove) + '\n') Optimizer.output.write(repr(indiv[0]) + '\n') muttype = 'LANN' + repr(natomsmove) if indiv.energy == 0: indiv.history_index = indiv.history_index + 'm' + muttype else: indiv.history_index = repr(indiv.index) + 'm' + muttype return indiv
a = atoms.copy() b = a * [nx, 1, 1] b.center() b.positions[:,0] += 0.5 b.set_scaled_positions(b.get_scaled_positions()) mask = ((b.positions[:,0] > 0.) & (b.positions[:,0] < 2*a.cell[0,0]) & (b.positions[:,1] < (a.cell[1,1]/2.0 + final_height/2.0)) & (b.positions[:,1] > (a.cell[1,1]/2.0 - final_height/2.0))) nl = NeighborList([1.0]*len(b), self_interaction=False, bothways=True) nl.update(b) term = Atoms() if tetra: for i in range(len(b)): if not mask[i]: continue indices, offsets = nl.get_neighbors(i) if b.numbers[i] == 8 and mask[indices].sum() == 0: mask[i] = False if b.numbers[i] == 14: # complete tetrahedra for (j, o) in zip(indices, offsets): if b.numbers[j] == 8: mask[j] = True
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')
class EMT: disabled = False # Set to True to disable (asap does this). def __init__(self, fakestress=False): self.energy = None self.name = 'EMT' self.version = '1.0' # fakestress is needed to fake some stress value for the testsuite # in order to test the filter functionality. self.fakestress = fakestress if self.disabled: print >> sys.stderr, """ ase.EMT has been disabled by Asap. Most likely, you intended to use Asap's EMT calculator, but accidentally imported ase's EMT calculator after Asap's. This could happen if your script contains the lines from asap3 import * from ase.calculators.emt import EMT Swap the two lines to solve the problem. (or 'from ase import *' in older versions of ASE.) Swap the two lines to solve the problem. In the UNLIKELY event that you actually wanted to use ase.calculators.emt.EMT although asap3 is loaded into memory, please reactivate it with the command ase.calculators.emt.EMT.disabled = False """ raise RuntimeError('ase.EMT has been disabled. ' + 'See message printed above.') def get_name(self): return self.name def get_version(self): return self.version def get_spin_polarized(self): return False def initialize(self, atoms): self.par = {} self.rc = 0.0 self.numbers = atoms.get_atomic_numbers() maxseq = max(par[1] for par in parameters.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) for Z in self.numbers: if Z not in self.par: p = parameters[chemical_symbols[Z]] 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} #if rc + 0.5 > self.rc: # self.rc = rc + 0.5 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'] * # exp(eta1 * (p1['s0'] - p2['s0']))) 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 + 0.25] * len(atoms), self_interaction=False) def update(self, atoms): if (self.energy is None or len(self.numbers) != len(atoms) or (self.numbers != atoms.get_atomic_numbers()).any()): self.initialize(atoms) self.calculate(atoms) elif ((self.positions != atoms.get_positions()).any() or (self.pbc != atoms.get_pbc()).any() or (self.cell != atoms.get_cell()).any()): self.calculate(atoms) def calculation_required(self, atoms, quantities): if len(quantities) == 0: return False return (self.energy is None or len(self.numbers) != len(atoms) or (self.numbers != atoms.get_atomic_numbers()).any() or (self.positions != atoms.get_positions()).any() or (self.pbc != atoms.get_pbc()).any() or (self.cell != atoms.get_cell()).any()) def get_number_of_iterations(self): return 0 def get_potential_energy(self, atoms): self.update(atoms) return self.energy def get_numeric_forces(self, atoms): self.update(atoms) p = atoms.positions p0 = p.copy() forces = np.empty_like(p) eps = 0.0001 for a in range(len(p)): for c in range(3): p[a, c] += eps self.calculate(atoms) de = self.energy p[a, c] -= 2 * eps self.calculate(atoms) de -= self.energy p[a, c] += eps forces[a, c] = -de / (2 * eps) p[:] = p0 return forces def get_forces(self, atoms): self.update(atoms) return self.forces.copy() def get_stress(self, atoms): if self.fakestress: return np.zeros((6)) else: raise NotImplementedError def calculate(self, atoms): self.positions = atoms.get_positions().copy() self.cell = atoms.get_cell().copy() self.pbc = atoms.get_pbc().copy() self.nl.update(atoms) self.energy = 0.0 self.sigma1[:] = 0.0 self.forces[:] = 0.0 natoms = len(atoms) for a1 in range(natoms): Z1 = self.numbers[a1] p1 = self.par[Z1] ksi = self.ksi[Z1] neighbors, offsets = self.nl.get_neighbors(a1) offsets = np.dot(offsets, atoms.cell) for a2, offset in zip(neighbors, offsets): d = self.positions[a2] + offset - self.positions[a1] r = sqrt(np.dot(d, d)) if r < self.rc + 0.5: Z2 = self.numbers[a2] p2 = self.par[Z2] self.interact1(a1, a2, d, r, p1, p2, ksi[Z2]) for a in range(natoms): Z = self.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'])) e = p['E0'] * ((1 + x) * y - 1) + z self.energy += p['E0'] * ((1 + x) * y - 1) + z for a1 in range(natoms): Z1 = self.numbers[a1] p1 = self.par[Z1] ksi = self.ksi[Z1] neighbors, offsets = self.nl.get_neighbors(a1) offsets = np.dot(offsets, atoms.cell) for a2, offset in zip(neighbors, offsets): d = self.positions[a2] + offset - self.positions[a1] r = sqrt(np.dot(d, d)) if r < self.rc + 0.5: Z2 = self.numbers[a2] p2 = self.par[Z2] self.interact2(a1, a2, d, r, p1, p2, ksi[Z2]) 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 def set_atoms(self,*args,**kwargs): 'empty function for compatibility with other calculators and tests' pass
def eval_energy(input): """Function to evaluate energy of an individual Inputs: input = [Optimizer class object with parameters, Individual class structure to be evaluated] Outputs: energy, bul, individ, signal energy = energy of Individual evaluated bul = bulk structure of Individual if simulation structure is Defect individ = Individual class structure evaluated signal = string of information about evaluation """ if input[0] == None: energy = 0 bul = 0 individ = 0 rank = MPI.COMM_WORLD.Get_rank() signal = 'Evaluated none individual on ' + repr(rank) + '\n' else: [Optimizer, individ] = input if Optimizer.calc_method == 'MAST': energy = individ.energy bul = individ.energy signal = 'Recieved MAST structure\n' else: if Optimizer.parallel: rank = MPI.COMM_WORLD.Get_rank() if not Optimizer.genealogy: STR = '----Individual ' + str( individ.index) + ' Optimization----\n' else: STR = '----Individual ' + str( individ.history_index) + ' Optimization----\n' indiv = individ[0] if 'EE' in Optimizer.debug: debug = True else: debug = False if debug: write_xyz(Optimizer.debugfile, indiv, 'Recieved by eval_energy') Optimizer.debugfile.flush() if Optimizer.structure == 'Defect': indi = indiv.copy() if Optimizer.alloy == True: bulk = individ.bulki else: bulk = individ.bulko nat = indi.get_number_of_atoms() csize = bulk.get_cell() totalsol = Atoms(cell=csize, pbc=True) totalsol.extend(indi) totalsol.extend(bulk) for sym, c, m, u in Optimizer.atomlist: nc = len([atm for atm in totalsol if atm.symbol == sym]) STR += 'Defect configuration contains ' + repr( nc) + ' ' + repr(sym) + ' atoms\n' elif Optimizer.structure == 'Surface': totalsol = Atoms() totalsol.extend(indiv) nat = indiv.get_number_of_atoms() totalsol.extend(individ.bulki) for sym, c, m, u in Optimizer.atomlist: nc = len([atm for atm in totalsol if atm.symbol == sym]) STR += 'Surface-Bulk configuration contains ' + repr( nc) + ' ' + repr(sym) + ' atoms\n' cell = numpy.maximum.reduce(indiv.get_cell()) totalsol.set_cell([cell[0], cell[1], 500]) totalsol.set_pbc([True, True, False]) if Optimizer.constrain_position: ts = totalsol.copy() indc, indb, vacant, swap, stro = find_defects( ts, Optimizer.solidbulk, 0) sbulk = Optimizer.solidbulk.copy() bcom = sbulk.get_center_of_mass() #totalsol.translate(-bulkcom) #indc.translate(-bulkcom) #totalsol.append(Atom(position=[0,0,0])) # for one in indc: # index = [atm.index for atm in totalsol if atm.position[0]==one.position[0] and atm.position[1]==one.position[1] and atm.position[2]==one.position[2]][0] # if totalsol.get_distance(-1,index) > Optimizer.sf: # r = random.random() # totalsol.set_distance(-1,index,Optimizer.sf*r,fix=0) # totalsol.pop() # totalsol.translate(bulkcom) com = indc.get_center_of_mass() dist = (sum((bcom[i] - com[i])**2 for i in range(3)))**0.5 if dist > Optimizer.sf: STR += 'Shifting structure to within region\n' r = random.random() * Optimizer.sf comv = numpy.linalg.norm(com) ncom = [one * r / comv for one in com] trans = [ncom[i] - com[i] for i in range(3)] indices = [] for one in indc: id = [ atm.index for atm in totalsol if atm.position[0] == one.position[0] and atm.position[1] == one.position[1] and atm.position[2] == one.position[2] ][0] totalsol[id].position += trans # Check for atoms that are too close min_len = 0.7 #pdb.set_trace() if not Optimizer.fixed_region: if Optimizer.structure == 'Defect' or Optimizer.structure == 'Surface': cutoffs = [2.0 for one in totalsol] nl = NeighborList(cutoffs, bothways=True, self_interaction=False) nl.update(totalsol) for one in totalsol[0:nat]: nbatoms = Atoms() nbatoms.append(one) indices, offsets = nl.get_neighbors(one.index) for index, d in zip(indices, offsets): index = int(index) sym = totalsol[index].symbol pos = totalsol[index].position + numpy.dot( d, totalsol.get_cell()) at = Atom(symbol=sym, position=pos) nbatoms.append(at) while True: dflag = False for i in range(1, len(nbatoms)): d = nbatoms.get_distance(0, i) if d < min_len: nbatoms.set_distance(0, i, min_len + .01, fix=0.5) STR += '--- WARNING: Atoms too close (<0.7A) - Implement Move ---\n' dflag = True if dflag == False: break for i in range(len(indices)): totalsol[indices[i]].position = nbatoms[i + 1].position totalsol[one.index].position = nbatoms[0].position nl.update(totalsol) if debug: write_xyz(Optimizer.debugfile, totalsol, 'After minlength check') Optimizer.debugfile.flush() else: for i in range(len(indiv)): for j in range(len(indiv)): if i != j: d = indiv.get_distance(i, j) if d < min_len: indiv.set_distance(i, j, min_len, fix=0.5) STR += '--- WARNING: Atoms too close (<0.7A) - Implement Move ---\n' if debug: write_xyz(Optimizer.debugfile, indiv, 'After minlength check') Optimizer.debugfile.flush() # Set calculator to use to get forces/energies if Optimizer.parallel: calc = setup_calculator(Optimizer) if Optimizer.fixed_region: pms = copy.deepcopy(calc.parameters) try: pms['mass'][ len(pms['mass']) - 1] += '\ngroup RO id >= ' + repr( nat) + '\nfix freeze RO setforce 0.0 0.0 0.0\n' except KeyError: pms['pair_coeff'][0] += '\ngroup RO id >= ' + repr( nat) + '\nfix freeze RO setforce 0.0 0.0 0.0\n' calc = LAMMPS(parameters=pms, files=calc.files, keep_tmp_files=calc.keep_tmp_files, tmp_dir=calc.tmp_dir) lmin = copy.copy(Optimizer.lammps_min) Optimizer.lammps_min = None Optimizer.static_calc = setup_calculator(Optimizer) Optimizer.lammps_min = lmin else: calc = Optimizer.calc if Optimizer.structure == 'Defect' or Optimizer.structure == 'Surface': totalsol.set_calculator(calc) totalsol.set_pbc(True) else: indiv.set_calculator(calc) indiv.set_pbc( True) #Current bug in ASE optimizer-Lammps prevents pbc=false if Optimizer.structure == 'Cluster': indiv.set_cell([500, 500, 500]) indiv.translate([250, 250, 250]) cwd = os.getcwd() # Perform Energy Minimization if not Optimizer.parallel: Optimizer.output.flush() if Optimizer.ase_min == True: try: if Optimizer.structure == 'Defect' or Optimizer.structure == 'Surface': dyn = BFGS(totalsol) else: dyn = BFGS(indiv) dyn.run(fmax=Optimizer.ase_min_fmax, steps=Optimizer.ase_min_maxsteps) except OverflowError: STR += '--- Error: Infinite Energy Calculated - Implement Random ---\n' box = Atoms() indiv = gen_pop_box(Optimizer.natoms, Optimizer.atomlist, Optimizer.size) indiv.set_calculator(calc) dyn = BFGS(indiv) dyn.run(fmax=fmax, steps=steps) except numpy.linalg.linalg.LinAlgError: STR += '--- Error: Singular Matrix - Implement Random ---\n' indiv = gen_pop_box(Optimizer.natoms, Optimizer.atomlist, Optimizer.size) indiv.set_calculator(calc) dyn = BFGS(indiv) dyn.run(fmax=fmax, steps=steps) # Get Energy of Minimized Structure if Optimizer.structure == 'Defect' or Optimizer.structure == 'Surface': en = totalsol.get_potential_energy() #force=numpy.maximum.reduce(abs(totalsol.get_forces())) if Optimizer.fitness_scheme == 'enthalpyfit': pressure = totalsol.get_isotropic_pressure( totalsol.get_stress()) cell_max = numpy.maximum.reduce(totalsol.get_positions()) cell_min = numpy.minimum.reduce(totalsol.get_positions()) cell = cell_max - cell_min volume = cell[0] * cell[1] * cell[2] else: pressure = 0 volume = 0 na = totalsol.get_number_of_atoms() ena = en / na energy = en individ[0] = totalsol[0:nat] bul = totalsol[(nat):len(totalsol)] STR += 'Number of positions = ' + repr( len(bul) + len(individ[0])) + '\n' individ[0].set_cell(csize) indiv = individ[0] else: en = indiv.get_potential_energy() if Optimizer.fitness_scheme == 'enthalpyfit': pressure = indiv.get_isotropic_pressure(indiv.get_stress()) cell_max = numpy.maximum.reduce(indiv.get_positions()) cell_min = numpy.minimum.reduce(indiv.get_positions()) cell = cell_max - cell_min volume = cell[0] * cell[1] * cell[2] else: pressure = 0 volume = 0 na = indiv.get_number_of_atoms() ena = en / na energy = ena individ[0] = indiv bul = 0 else: if Optimizer.structure == 'Defect' or Optimizer.structure == 'Surface': if Optimizer.calc_method == 'VASP': en = totalsol.get_potential_energy() calcb = Vasp(restart=True) totalsol = calcb.get_atoms() stress = calcb.read_stress() else: try: totcop = totalsol.copy() if debug: write_xyz(Optimizer.debugfile, totcop, 'Individual sent to lammps') OUT = totalsol.calc.calculate(totalsol) totalsol = OUT['atoms'] totalsol.set_pbc(True) if Optimizer.fixed_region: if debug: print 'Energy of fixed region calc = ', OUT[ 'thermo'][-1]['pe'] totalsol.set_calculator(Optimizer.static_calc) OUT = totalsol.calc.calculate(totalsol) totalsol = OUT['atoms'] totalsol.set_pbc(True) if debug: print 'Energy of static calc = ', OUT[ 'thermo'][-1]['pe'] en = OUT['thermo'][-1]['pe'] stress = numpy.array([ OUT['thermo'][-1][i] for i in ('pxx', 'pyy', 'pzz', 'pyz', 'pxz', 'pxy') ]) * (-1e-4 * GPa) #force=numpy.maximum.reduce(abs(totalsol.get_forces())) if debug: write_xyz(Optimizer.debugfile, totalsol, 'After Lammps Minimization') Optimizer.debugfile.flush() except Exception, e: os.chdir(cwd) STR += 'WARNING: Exception during energy eval:\n' + repr( e) + '\n' f = open('problem-structures.xyz', 'a') write_xyz(f, totcop, data='Starting structure hindex=' + individ.history_index) write_xyz(f, totalsol, data='Lammps Min structure') en = 10 stress = 0 f.close() if Optimizer.fitness_scheme == 'enthalpyfit': pressure = totalsol.get_isotropic_pressure(stress) cell_max = numpy.maximum.reduce(totalsol.get_positions()) cell_min = numpy.minimum.reduce(totalsol.get_positions()) cell = cell_max - cell_min volume = cell[0] * cell[1] * cell[2] else: pressure = totalsol.get_isotropic_pressure(stress) volume = 0 na = totalsol.get_number_of_atoms() ena = en / na energy = en if Optimizer.structure == 'Defect': if Optimizer.fixed_region == True or Optimizer.finddefects == False: individ[0] = totalsol[0:nat] bul = totalsol[(nat):len(totalsol)] individ[0].set_cell(csize) else: if 'FI' in Optimizer.debug: outt = find_defects( totalsol, Optimizer.solidbulk, Optimizer.sf, atomlistcheck=Optimizer.atomlist, trackvacs=Optimizer.trackvacs, trackswaps=Optimizer.trackswaps, debug=Optimizer.debugfile) else: outt = find_defects( totalsol, Optimizer.solidbulk, Optimizer.sf, atomlistcheck=Optimizer.atomlist, trackvacs=Optimizer.trackvacs, trackswaps=Optimizer.trackswaps, debug=False) individ[0] = outt[0] bul = outt[1] individ.vacancies = outt[2] individ.swaps = outt[3] STR += outt[4] indiv = individ[0] else: top, bul = find_top_layer(totalsol, Optimizer.surftopthick) indiv = top.copy() individ[0] = top.copy() else:
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_{\rm 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_{\rm tot} = \sum_i F(\bar\rho_i) + \frac{1}{2}\sum_{i\ne j} \phi(r_{ij}) + {1\over 2} \sum_{i,\alpha} (\mu_i^\alpha)^2 + {1\over 2} \sum_{i,\alpha,\beta} (\lambda_i^{\alpha\beta})^2 - {1 \over 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="""EAM/ADP potential file\nGenerated from eam.py\nblank\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.iteritems(): 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" raise NotImplementedError 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, str): f = open(fileobj) self.set_form(fileobj) else: f = fileobj lines = f.readlines() self.header = lines[:3] i = 3 # make the data one long line so as not to care how its formatted data = [] for line in lines[i:]: data.extend(line.split()) 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): if self.form == "eam": # not stored as rphi # should we ignore the first point for eam ? raise RuntimeError(".eam format not yet supported") self.phi[i, j] = spline(self.r[1:], self.rphi_data[i, j][1:], k=3) else: 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 symetrical 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 symetrical 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, "w") assert self.nr % nc == 0 assert self.nrho % nc == 0 for line in self.header: f.write(line) f.write("%d " % self.Nelements) for i in range(self.Nelements): f.write("%s " % str(self.elements[i])) f.write("\n") f.write("%d %f %d %f %f \n" % (self.nrho, self.drho, self.nr, self.dr, self.cutoff)) # 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]))) 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.0 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.0 lam_energy += np.sum(self.lam ** 2) / 2.0 for i in range(len(atoms)): # this is the atom to be embedded trace_energy -= np.sum(self.lam[i].trace() ** 2) / 6.0 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.0 # 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()
class EMT(Calculator): implemented_properties = ['energy', 'forces'] nolabel = True def __init__(self): Calculator.__init__(self) def initialize(self, atoms): self.par = {} self.rc = 0.0 self.numbers = atoms.get_atomic_numbers() maxseq = max(par[1] for par in parameters.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) for Z in self.numbers: if Z not in self.par: p = parameters[chemical_symbols[Z]] 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} #if rc + 0.5 > self.rc: # self.rc = rc + 0.5 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'] * # exp(eta1 * (p1['s0'] - p2['s0']))) 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 + 0.25] * 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 + 0.5: 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 + 0.5: Z2 = numbers[a2] p2 = self.par[Z2] self.interact2(a1, a2, d, r, p1, p2, ksi[Z2]) self.results['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
def check_min_dist(Optimizer, totalsol, type='Defect', nat=None, min_len=0.7, STR=''): if type=='Defect' or type=='Crystal' or type=='Surface': if nat==None: nat=len(totalsol) cutoffs=[2.0 for one in totalsol] nl=NeighborList(cutoffs,bothways=True,self_interaction=False) nl.update(totalsol) for one in totalsol[0:nat]: nbatoms=Atoms() nbatoms.append(one) indices, offsets=nl.get_neighbors(one.index) for index, d in zip(indices,offsets): index = int(index) sym=totalsol[index].symbol pos=totalsol[index].position + numpy.dot(d,totalsol.get_cell()) at=Atom(symbol=sym,position=pos) nbatoms.append(at) while True: dflag=False for i in range(1,len(nbatoms)): d=nbatoms.get_distance(0,i) if d < min_len: nbatoms.set_distance(0,i,min_len+.01,fix=0.5) STR+='--- WARNING: Atoms too close (<0.7A) - Implement Move ---\n' dflag=True if dflag==False: break for i in range(len(indices)): totalsol[indices[i]].position=nbatoms[i+1].position totalsol[one.index].position=nbatoms[0].position nl.update(totalsol) elif type=='Cluster': rank = MPI.COMM_WORLD.Get_rank() logger = logging.getLogger(Optimizer.loggername) R = totalsol.arrays['positions'] tol = 0.01 epsilon = 0.05 fix = 0.5 if Optimizer.forcing == 'EllipoidShape' or Optimizer.forcing == 'FreeNatom': com = totalsol.get_center_of_mass() cmax = numpy.maximum.reduce(R) cmin = numpy.minimum.reduce(R) rmax= (cmax-cmin)/2.0 if Optimizer.forcing == 'FreeNatom': rcutoff = 44.0 cutoff = [44.0,44.0,20.0] else: rcutoff = 11.0 cutoff = [12.0,12.0,12.0] rcutoff = 44.0 cutoff = [44.0,44.0,20.0] #check if atoms are isolated outside of cluster cutoffs=[3.0 for one in totalsol] nl=NeighborList(cutoffs,bothways=True,self_interaction=False) nl.update(totalsol) for i in range(len(totalsol)): indices, offsets=nl.get_neighbors(i) D = R[i]-com radius = (numpy.dot(D,D))**0.5 #numpy.linalg.norm(D) if len(indices) < 12 or radius > rcutoff : # logger.info('M:move atoms back when indice {0} or radius {1}'.format(len(indices),radius)) # R[i] = [com[j] + D[j]/radius*rcutoff for j in range(3)] theta=math.radians(random.uniform(0,360)) phi=math.radians(random.uniform(0,180)) R[i][0] = com[0] + (rmax[0]+2.5)*math.sin(theta)*math.cos(phi) #allow atoms expend by 2.5 ang R[i][1] = com[1] + (rmax[1]+2.5)*math.sin(theta)*math.sin(phi) R[i][2] = com[2] + rmax[2]*math.cos(theta) # logger.info('M:move atoms back new pos {0} {1} {2}'.format(rmax[0]*math.sin(theta)*math.cos(phi),rmax[1]*math.sin(theta)*math.sin(phi),rmax[2]*math.cos(theta))) # check if atoms are within cluster region for i in range(0,len(totalsol)): # D = R[i]-com for j in range(3): if D[j] > cutoff[j] or D[j] < -cutoff[j]: # logger.info('M:before move R {0} & com {1}'.format(R[i][j],com[j])) #if rank==0: # print "before:",j,R[i][j],com[j] R[i][j] = com[j]+numpy.sign(D[j])*cutoff[j]*random.random() # logger.info('M:after move R {0} '.format(R[i][j])) #if rank==0: # print "after:",R[i][j] # STR+='--- WARNING: Atoms too far along x-y (>44A) - Implement Move ---\n' D = R[i]-com # radius = (numpy.dot(D,D))**0.5 #numpy.linalg.norm(D) # STR+='--- WARNING: Atoms too far (>56A) - Implement Move ---\n' closelist = numpy.arange(len(totalsol)) iter = 0 while len(closelist) > 0 and iter<2: iter+=1 # checklist = numpy.copy(closelist) closelist = [] dist=scipy.spatial.distance.cdist(R,R) numpy.fill_diagonal(dist,1.0) smalldist = numpy.where(dist < min_len-tol) # for i in checklist: # for j in range(i+1,len(totalsol)): # if len(checklist) == len(totalsol): # jstart = i+1 # else: # jstart = 0 # for j in range(jstart,len(totalsol)): # if i != j and dist[i][j] < min_len: # d=totalsol.get_distance(i,j) # if d < min_len: # totalsol.set_distance(i,j,min_len,fix=0.5) # d = (D[0]*D[0]+D[1]*D[1]+D[2]*D[2])**0.5 for ind in range(len(smalldist[0])): i = smalldist[0][ind] j = smalldist[1][ind] if i < j and dist[i][j] < min_len-tol: closelist.append(i) closelist.append(j) if dist[i][j] > epsilon: x = 1.0 - min_len / dist[i][j] D = R[j]-R[i] # print "node:",rank,"x",x,R[i],R[j],D, dist[i][j] R[i] += (x * fix) * D R[j] -= (x * (1.0 - fix)) * D else: R[i] += [0.2, 0.0, 0.0] R[j] -= [0.2, 0.0, 0.0] R2P = [R[i],R[j]] dist2P=scipy.spatial.distance.cdist(R2P,R) dist[i] = dist2P[0] dist[j] = dist2P[1] for k in range(len(R)): dist[k][i] = dist[i][k] dist[k][j] = dist[j][k] # STR+='--- WARNING: Atoms too close (<0.7A) - Implement Move ---\n' closelist=list(set(closelist)) closelist.sort() if len(closelist) != 0: logger.info('M:iter {0}, closelist size {1}'.format(iter,len(closelist))) # print "rank", rank, closelist else: print 'WARNING: In Check_Min_Dist in EvalEnergy: Structure Type not recognized' return totalsol, STR
def newoverlap(wfs, spos_ac): assert wfs.ksl.block_comm.size == wfs.gd.comm.size * wfs.bd.comm.size even_part = EvenPartitioning( wfs.gd.comm, #wfs.ksl.block_comm, len(wfs.atom_partition.rank_a)) atom_partition = even_part.as_atom_partition() tci = wfs.tci gd = wfs.gd kd = wfs.kd nq = len(kd.ibzk_qc) # New neighbor list because we want it "both ways", heh. Or do we? neighbors = NeighborList(tci.cutoff_a, skin=0, sorted=True, self_interaction=True, bothways=False) atoms = Atoms('X%d' % len(tci.cutoff_a), cell=gd.cell_cv, pbc=gd.pbc_c) atoms.set_scaled_positions(spos_ac) neighbors.update(atoms) # XXX pcutoff_a = [] phicutoff_a = [] for setup in wfs.setups: if setup.pt_j: pcutoff = max([pt.get_cutoff() for pt in setup.pt_j]) else: pcutoff = 0.0 if setup.phit_j: phicutoff = max([phit.get_cutoff() for phit in setup.phit_j]) else: phicutoff = 0.0 pcutoff_a.append(pcutoff) phicutoff_a.append(phicutoff) # Calculate the projector--basis function overlaps: # # a1 ~a1 # P = < p | Phi > , # i mu i mu # # i.e. projector is on a1 and basis function is on what we will call a2. overlapcalc = TwoCenterIntegralCalculator(wfs.kd.ibzk_qc, derivative=False) P_aaqim = {} # keys: (a1, a2). Values: matrix blocks dists_and_offsets = DistsAndOffsets(neighbors, spos_ac, gd.cell_cv) #ng = 2**extra_parameters.get('log2ng', 10) #transformer = FourierTransformer(rcmax, ng) #tsoc = TwoSiteOverlapCalculator(transformer) #msoc = ManySiteOverlapCalculator(tsoc, I_a, I_a) msoc = wfs.tci.msoc phit_Ij = [setup.phit_j for setup in tci.setups_I] l_Ij = [] for phit_j in phit_Ij: l_Ij.append([phit.get_angular_momentum_number() for phit in phit_j]) pt_l_Ij = [setup.l_j for setup in tci.setups_I] pt_Ij = [setup.pt_j for setup in tci.setups_I] phit_Ijq = msoc.transform(phit_Ij) pt_Ijq = msoc.transform(pt_Ij) #self.Theta_expansions = msoc.calculate_expansions(l_Ij, phit_Ijq, # l_Ij, phit_Ijq) #self.T_expansions = msoc.calculate_kinetic_expansions(l_Ij, phit_Ijq) P_expansions = msoc.calculate_expansions(pt_l_Ij, pt_Ijq, l_Ij, phit_Ijq) P_neighbors_a = {} for a1 in atom_partition.my_indices: for a2 in range(len(wfs.setups)): R_ca_and_offset_a = dists_and_offsets.get(a1, a2) if R_ca_and_offset_a is None: # No overlap between a1 and a2 continue maxdistance = pcutoff_a[a1] + phicutoff_a[a2] expansion = P_expansions.get(a1, a2) P_qim = expansion.zeros((nq, ), dtype=wfs.dtype) disp = None for R_c, offset in R_ca_and_offset_a: r = np.linalg.norm(R_c) if r > maxdistance: continue # Below lines are meant to make use of symmetry. Will not # be relevant for P. #remainder = (a1 + a2) % 2 #if a1 < a2 and not remainder: # continue # if a1 > a2 and remainder: # continue phases = overlapcalc.phaseclass(overlapcalc.ibzk_qc, offset) disp = AtomicDisplacement(None, a1, a2, R_c, offset, phases) disp.evaluate_overlap(expansion, P_qim) if disp is not None: # there was at least one non-zero overlap assert (a1, a2) not in P_aaqim P_aaqim[(a1, a2)] = P_qim P_neighbors_a.setdefault(a1, []).append(a2) Pkeys = P_aaqim.keys() Pkeys.sort() def get_M1M2(a): M1 = wfs.setups.M_a[a] M2 = M1 + wfs.setups[a].nao return M1, M2 oldstyle_P_aqMi = None if 0: #wfs.world.size == 1: oldstyle_P_aqMi = {} for a, setup in enumerate(wfs.setups): oldstyle_P_aqMi[a] = np.zeros((nq, wfs.setups.nao, setup.ni), dtype=wfs.dtype) print([(s.ni, s.nao) for s in wfs.setups]) for a1, a2 in Pkeys: M1, M2 = get_M1M2(a2) Pconj_qmi = P_aaqim[(a1, a2)].transpose(0, 2, 1).conjugate() oldstyle_P_aqMi[a1][:, M1:M2, :] = Pconj_qmi # XXX mind distribution return P_neighbors_a, P_aaqim, oldstyle_P_aqMi
class EMT(Calculator): implemented_properties = ['energy', 'forces'] nolabel = True def __init__(self): Calculator.__init__(self) def initialize(self, atoms): self.par = {} self.rc = 0.0 self.numbers = atoms.get_atomic_numbers() maxseq = max(par[1] for par in parameters.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) for Z in self.numbers: if Z not in self.par: p = parameters[chemical_symbols[Z]] 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 } #if rc + 0.5 > self.rc: # self.rc = rc + 0.5 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'] * # exp(eta1 * (p1['s0'] - p2['s0']))) 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 + 0.25] * len(atoms), self_interaction=False) def calculate(self, atoms=None, properties=['energy'], system_changes=[ 'positions', 'numbers', 'cell', 'pbc', 'charges', 'magmoms' ]): 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 + 0.5: 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 + 0.5: Z2 = numbers[a2] p2 = self.par[Z2] self.interact2(a1, a2, d, r, p1, p2, ksi[Z2]) self.results['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 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, # length of the symbol nvalues # of values expected ): if name not in self.data: self.data[name] = {} data = self.data[name] def add_line(): line = fileobj.readline() if len(line) <= 1: # 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, 3) read_block('cutoffs', 5, 1) self.bonds = BondData(self.data['bonds']) self.angles = AnglesData(self.data['angles']) self.cutoffs = CutoffList(self.data['cutoffs']) def write_lammps(self, atoms): btypes, atypes = self.write_lammps_atoms(atoms) self.write_lammps_definitions(atoms, btypes, atypes) def write_lammps_atoms(self, atoms): """Write atoms infor for LAMMPS""" fileobj = 'lammps_atoms' if isinstance(fileobj, str): fileobj = open(fileobj, 'w') write_lammps_data(fileobj, atoms, specorder=atoms.types, speclist=(atoms.get_tags() + 1), ) # 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 btypes, blist = self.get_bonds(atoms) fileobj.write('\n' + str(len(btypes)) + ' bond types\n') fileobj.write(str(len(blist)) + ' bonds\n') fileobj.write('\nBonds\n\n') for ib, bvals in enumerate(blist): fileobj.write('%8d %6d %6d %6d\n' % (ib + 1, bvals[0] + 1, bvals[1] + 1, bvals[2] + 1)) # angles atypes, alist = self.get_angles() fileobj.write('\n' + str(len(atypes)) + ' angle types\n') fileobj.write(str(len(alist)) + ' angles\n') fileobj.write('\nAngles\n\n') for ia, avals in enumerate(alist): fileobj.write('%8d %6d %6d %6d %6d\n' % (ia + 1, avals[0] + 1, avals[1] + 1, avals[2] + 1, avals[3] + 1)) return btypes, atypes 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.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() positions = atoms.get_positions() 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() positions = atoms.get_positions() ang_list = [] ang_types = [] for i, atom in enumerate(atoms): iname = types[tags[i]] indicesi, offsetsi = self.nl.get_neighbors(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 indicesj, offsetsj = self.nl.get_neighbors(j) for k, offsetk in zip(indicesj, offsetsj): if k <= i: continue # avoid double count kname = types[tags[k]] cut = cutoffs.value(jname, kname) if cut is None: continue # don't have it dist = np.linalg.norm(atoms[k].position + np.dot(offsetk, cell) - atoms[j].position) if dist > cut: continue # too far away name, val = self.angles.name_value(iname, jname, kname) if name is None: continue # don't have it if name not in ang_types: ang_types.append(name) ang_list.append([ang_types.index(name), i, j, k]) return ang_types, ang_list def write_lammps_definitions(self, atoms, btypes, atypes): """Write force field definitions for LAMMPS.""" fileobj = 'lammps_opls' if isinstance(fileobj, str): fileobj = open(fileobj, 'w') print >> fileobj, '# OPLS potential' print >> fileobj, '# write_lammps', time.asctime( time.localtime(time.time())) # bonds 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 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') # 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(' ' + str(data[atype][2])) fileobj.write(' # ' + atype + '\n')
def find_defects(solid, bulko, rcutoff, atomlistcheck = False, trackvacs = False, trackswaps = False, debug = False, dcheck = 0.6): """Function to find interstitials, vacancies, and substitutional atoms (swaps) in a defected structure. Identifies species by comparison to perfect structure. Inputs: solid = ASE atoms class for defected structure bulko = ASE atoms class for perfect structure rcutoff = float value of distance to surrounding atoms to include atomlistcheck = False/list of atom types and concentrations according to atomlist format trackvacs = True/False whether or not to identify vacancies in defect trackswaps = True/False whether or not to identify substitutional defects debug = False/file object to write debug structures""" # Combine perfect and defect structures together b = bulko.copy() b.extend(solid) b.set_pbc(True) # Debug: Write solid and bulko to file if debug: print len(bulko) write_xyz(debug, b, 'Find Ints: Solid and Bulko') # Identify nearest neighbor atoms for each atom in perfect structure ntot = len(bulko) ctoff1 = [1.2 for one in b] nl = NeighborList(ctoff1, bothways = True, self_interaction = False) nl.update(b) slist = [] blist = [] wlist = [] # Loop over each atom in perfect structure for one in range(ntot): indices, offsets = nl.get_neighbors(one) for index, d in zip(indices, offsets): index = int(index) if index >= ntot: pos = b[index].position + numpy.dot(d, bulko.get_cell()) natm1 = Atom(position = pos) dist, dx, dy, dz = calc_dist(b[one], natm1) if dist <= dcheck: # Assume atoms closer than 0.6 Angstroms to be equivalent slist.append(index-ntot) blist.append(one) if b[one].symbol == b[index].symbol: wlist.append(index-ntot) # Identify those atoms corresponding to interstitials, vacancies, and substitutions oslist = [atm.index for atm in solid if atm.index not in slist] vlist = [atm.index for atm in bulko if atm.index not in blist] swlist = [atm.index for atm in solid if atm.index not in wlist and atm.index not in oslist] # Create Atoms objects for each identified defect ntot = len(solid) cluster = Atoms() for one in oslist: cluster.append(solid[one]) vacant = Atoms() if trackvacs == True: for one in vlist: vacant.append(Atom(symbol = bulko[one].symbol, position = bulko[one].position)) solid.append(Atom(symbol = 'X', position = bulko[one].position)) oslist.append(len(solid)-1) stro = 'Cluster Identified with length = {0}\nIdentified {1} vacancies\n'.format(len(cluster), len(vlist)) swaps = Atoms() if trackswaps == True: for one in swlist: swaps.append(solid[one]) oslist.append(one) stro = 'Cluster Identified with length = {0}\nIdentified {1} swaps\n'.format(len(cluster), len(swlist)) else: stro = 'Cluster Identified with length = {0}\n'.format(len(cluster)) # Debug: write cluster to file if debug: b = cluster.copy() write_xyz(debug, b, 'Find Ints: Cluster') debug.flush() print('Found cluster size = ', len(b)) # Identify atoms surrounding the identified defects in the defected structure box = Atoms() bulki = Atoms() if rcutoff != 0: if rcutoff > 2.0: cutoffs = [rcutoff for one in solid] else: cutoffs = [2.0 for one in solid] solid.set_pbc(True) nl = NeighborList(cutoffs, bothways = True, self_interaction = False) nl.update(solid) nbatmsd = [] repinds = [] for one in oslist: if one not in repinds: if one < ntot: nbatmsd.append((0, one)) repinds.append(one) for one in oslist: indices, offsets = nl.get_neighbors(one) for index, d in zip(indices, offsets): index = int(index) if index not in repinds and index < ntot: opos = copy.copy(solid[index].position) solid[index].position = solid[index].position + numpy.dot(d, solid.get_cell()) dist = solid.get_distance(one, index) solid[index].position = opos if dist <= rcutoff: nbatmsd.append((dist, index)) repinds.append(index) else: nbatmsd = [] repinds = [] for one in oslist: if one not in repinds: if one < ntot: nbatmsd.append((0, one)) repinds.append(one) nbatmsd = sorted(nbatmsd, key = lambda one:one[0], reverse = True) indices = [] natomsbox = 0 # Select only atoms closest to defects that satisfy concentrations specified by atomlist given in atomlistcheck if atomlistcheck: for sym, c, m, u in atomlistcheck: i = 0 nbsym = [one for one in nbatmsd if solid[one[1]].symbol == sym] if len(nbsym) > c: while i < c: a = nbsym.pop() box.append(solid[a[1]]) indices.append(a[1]) i += 1 else: for a in nbsym: box.append(solid[a[1]]) indices.append(a[1]) i += 1 if len(box)-natomsbox < c: try: while True: for n in range(len(nbatmsd)-1, -1, -1): inds, offsets = nl.get_neighbors(nbatmsd[n][1]) for one, d in zip(inds, offsets): if len(box)-natomsbox < c: if one not in indices and one < ntot and solid[one].symbol == sym: opos = copy.copy(solid[one].position) solid[one].position = solid[one].position + numpy.dot(d, solid.get_cell()) dist = solid.get_distance(nbatmsd[n][1], one) solid[one].position = opos if dist <= rcutoff*5.0: box.append(solid[one]) indices.append(one) else: raise StopIteration() for one, d in zip(inds, offsets): if len(box)-natomsbox < c: if one not in indices and one < ntot and solid[one].symbol == sym: opos = copy.copy(solid[one].position) solid[one].position = solid[one].position + numpy.dot(d, solid.get_cell()) dist = solid.get_distance(nbatmsd[n][1], one) solid[one].position = opos box.append(solid[one]) indices.append(one) else: raise StopIteration() except StopIteration: pass natomsbox = len(box) # Double check for sanity for sym, c, m, u in atomlistcheck: symsbox = [one for one in box if one.symbol == sym] if len(symsbox) != c: stro += 'WARNING!!!! : FAILURE IN FIND_DEFECTS TO MATCH PROVIDED ATOMLIST. DEBUG!!!!\n' # If atomlistcheck is False then use all the atoms in the given cutoff distance else: for a in nbatmsd: box.append(solid[a[1]]) indices.append(a[1]) # Add remaining atoms in defect to defected bulk atoms object for one in range(len(solid)): if one not in indices and one < ntot: bulki.append(solid[one]) # Check for accidental vacancy admissions #checklist = [atm for atm in box if atm.symbol == 'X'] #checklist.extend([atm for atm in bulki if atm.symbol == 'X']) # Set up new individual indiv = box.copy() bulki.set_cell(bulko.get_cell()) indiv.set_cell(bulko.get_cell()) bulki.set_pbc(True) indiv.set_pbc(True) stro += 'Atomlist check = {0}\n'.format(atomlistcheck) stro += 'Bulko = {0}\n'.format(bulko) stro += 'New individual ({0} atoms) : {1}\n'.format(len(indiv), indiv) stro += 'New bulki ({0} atoms) : {1}\n'.format(len(bulki), bulki) # Debug: write new indiv to file if debug: b = indiv.copy() write_xyz(debug, b, 'Find Ints: New Individual') # Debug: write new bulki to file b = bulki.copy() write_xyz(debug, b, 'Find Ints: New Bulki') debug.flush() print(len(bulko)) return indiv, bulki, vacant, swaps, stro
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_{\rm 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_{\rm tot} = \sum_i F(\bar\rho_i) + \frac{1}{2}\sum_{i\ne j} \phi(r_{ij}) + {1\over 2} \sum_{i,\alpha} (\mu_i^\alpha)^2 + {1\over 2} \sum_{i,\alpha,\beta} (\lambda_i^{\alpha\beta})^2 - {1 \over 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="""EAM/ADP potential file\nGenerated from eam.py\nblank\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.iteritems(): 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' raise NotImplementedError 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, str): f = open(fileobj) self.set_form(fileobj) else: f = fileobj lines = f.readlines() self.header = lines[:3] i = 3 # make the data one long line so as not to care how its formatted data = [] for line in lines[i:]: data.extend(line.split()) 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): if self.form == 'eam': # not stored as rphi # should we ignore the first point for eam ? raise RuntimeError('.eam format not yet supported') self.phi[i, j] = spline(self.r[1:], self.rphi_data[i, j][1:], k=3) else: 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 symetrical 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 symetrical 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, 'w') assert self.nr % nc == 0 assert self.nrho % nc == 0 for line in self.header: f.write(line) f.write('%d ' % self.Nelements) for i in range(self.Nelements): f.write('%s ' % str(self.elements[i])) f.write('\n') f.write('%d %f %d %f %f \n' % (self.nrho, self.drho, self.nr, self.dr, self.cutoff)) # 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]))) 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()
i, offsets = nl.get_neighbors(a) for j in i: c[j] += 1 c[a] += len(i) d += (((R[i] + np.dot(offsets, cell) - R[a])**2).sum(1)**0.5).sum() return d, c for sorted in [False, True]: for p1 in range(2): for p2 in range(2): for p3 in range(2): print(p1, p2, p3) atoms.set_pbc((p1, p2, p3)) nl = NeighborList(atoms.numbers * 0.2 + 0.5, skin=0.0, sorted=sorted) nl.update(atoms) d, c = count(nl, atoms) atoms2 = atoms.repeat((p1 + 1, p2 + 1, p3 + 1)) 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)
def newoverlap(wfs, spos_ac): assert wfs.ksl.block_comm.size == wfs.gd.comm.size * wfs.bd.comm.size even_part = EvenPartitioning(wfs.gd.comm, #wfs.ksl.block_comm, len(wfs.atom_partition.rank_a)) atom_partition = even_part.as_atom_partition() tci = wfs.tci gd = wfs.gd kd = wfs.kd nq = len(kd.ibzk_qc) # New neighbor list because we want it "both ways", heh. Or do we? neighbors = NeighborList(tci.cutoff_a, skin=0, sorted=True, self_interaction=True, bothways=False) atoms = Atoms('X%d' % len(tci.cutoff_a), cell=gd.cell_cv, pbc=gd.pbc_c) atoms.set_scaled_positions(spos_ac) neighbors.update(atoms) # XXX pcutoff_a = [] phicutoff_a = [] for setup in wfs.setups: if setup.pt_j: pcutoff = max([pt.get_cutoff() for pt in setup.pt_j]) else: pcutoff = 0.0 if setup.phit_j: phicutoff = max([phit.get_cutoff() for phit in setup.phit_j]) else: phicutoff = 0.0 pcutoff_a.append(pcutoff) phicutoff_a.append(phicutoff) # Calculate the projector--basis function overlaps: # # a1 ~a1 # P = < p | Phi > , # i mu i mu # # i.e. projector is on a1 and basis function is on what we will call a2. overlapcalc = TwoCenterIntegralCalculator(wfs.kd.ibzk_qc, derivative=False) P_aaqim = {} # keys: (a1, a2). Values: matrix blocks dists_and_offsets = DistsAndOffsets(neighbors, spos_ac, gd.cell_cv) #ng = 2**extra_parameters.get('log2ng', 10) #transformer = FourierTransformer(rcmax, ng) #tsoc = TwoSiteOverlapCalculator(transformer) #msoc = ManySiteOverlapCalculator(tsoc, I_a, I_a) msoc = wfs.tci.msoc phit_Ij = [setup.phit_j for setup in tci.setups_I] l_Ij = [] for phit_j in phit_Ij: l_Ij.append([phit.get_angular_momentum_number() for phit in phit_j]) pt_l_Ij = [setup.l_j for setup in tci.setups_I] pt_Ij = [setup.pt_j for setup in tci.setups_I] phit_Ijq = msoc.transform(phit_Ij) pt_Ijq = msoc.transform(pt_Ij) #self.Theta_expansions = msoc.calculate_expansions(l_Ij, phit_Ijq, # l_Ij, phit_Ijq) #self.T_expansions = msoc.calculate_kinetic_expansions(l_Ij, phit_Ijq) P_expansions = msoc.calculate_expansions(pt_l_Ij, pt_Ijq, l_Ij, phit_Ijq) P_neighbors_a = {} for a1 in atom_partition.my_indices: for a2 in range(len(wfs.setups)): R_ca_and_offset_a = dists_and_offsets.get(a1, a2) if R_ca_and_offset_a is None: # No overlap between a1 and a2 continue maxdistance = pcutoff_a[a1] + phicutoff_a[a2] expansion = P_expansions.get(a1, a2) P_qim = expansion.zeros((nq,), dtype=wfs.dtype) disp = None for R_c, offset in R_ca_and_offset_a: r = np.linalg.norm(R_c) if r > maxdistance: continue # Below lines are meant to make use of symmetry. Will not # be relevant for P. #remainder = (a1 + a2) % 2 #if a1 < a2 and not remainder: # continue # if a1 > a2 and remainder: # continue phases = overlapcalc.phaseclass(overlapcalc.ibzk_qc, offset) disp = AtomicDisplacement(None, a1, a2, R_c, offset, phases) disp.evaluate_overlap(expansion, P_qim) if disp is not None: # there was at least one non-zero overlap assert (a1, a2) not in P_aaqim P_aaqim[(a1, a2)] = P_qim P_neighbors_a.setdefault(a1, []).append(a2) Pkeys = P_aaqim.keys() Pkeys.sort() def get_M1M2(a): M1 = wfs.setups.M_a[a] M2 = M1 + wfs.setups[a].nao return M1, M2 oldstyle_P_aqMi = None if 0:#wfs.world.size == 1: oldstyle_P_aqMi = {} for a, setup in enumerate(wfs.setups): oldstyle_P_aqMi[a] = np.zeros((nq, wfs.setups.nao, setup.ni), dtype=wfs.dtype) print([(s.ni, s.nao) for s in wfs.setups]) for a1, a2 in Pkeys: M1, M2 = get_M1M2(a2) Pconj_qmi = P_aaqim[(a1, a2)].transpose(0, 2, 1).conjugate() oldstyle_P_aqMi[a1][:, M1:M2, :] = Pconj_qmi # XXX mind distribution return P_neighbors_a, P_aaqim, oldstyle_P_aqMi
class ANNCalculator(Calculator): def __init__(self, potentials, **kwargs): Calculator.__init__(self, **kwargs) self.ann = ANNPotentials(potentials) self.cutoff = self.ann.Rc_max self.cutoff2 = self.cutoff**2 self.neighbors = None self.implemented_properties = { 'energy': self.calculate_energy, 'forces': self.calculate_energy_and_forces, } self.results = {} def __del__(self): try: del self.ann except (AttributeError, NameError): pass def release(self): del self.ann def update(self, atoms): if (self.neighbors is None) or (len(self.neighbors.cutoffs) != len(atoms)): cutoffs = self.cutoff * np.ones(len(atoms)) self.neighbors = NeighborList(cutoffs, self_interaction=False, bothways=True) self.neighbors.update(atoms) def calculate(self, atoms=None, properties=['energy'], system_changes=all_changes): # Calculator essentially does: self.atoms = atoms Calculator.calculate(self, atoms, properties, system_changes) has_results = [p in self.results for p in properties] if (len(system_changes) > 0) or (not np.all(has_results)): self.update(self.atoms) if ('energy' in properties) and ('forces' in properties): # Forces evaluation requires energy. No need to compute # energy twice. del properties[properties.index('energy')] for p in properties: if p in self.implemented_properties: self.implemented_properties[p](self.atoms) else: raise NotImplementedError( "Property not implemented: {}".format(p)) def calculate_energy(self, atoms): energy = 0.0 atom_types = atoms.get_chemical_symbols() for i in range(len(atoms)): indices, offsets = self.neighbors.get_neighbors(i) type_i = atom_types[i] coords_i = atoms.positions[i] coords_j = np.empty((len(indices), 3), dtype=np.double) types_j = [] inb = 0 for j, offset in zip(indices, offsets): coo = (atoms.positions[j] + np.dot(offset, atoms.get_cell())) d2 = np.sum((coo - coords_i)**2) if d2 <= self.cutoff2: coords_j[inb] = coo types_j.append(atom_types[j]) inb += 1 energy += self.ann.atomic_energy(coords_i, type_i, coords_j[:inb], types_j) self.results['energy'] = energy def calculate_energy_and_forces(self, atoms): energy = 0.0 atom_types = atoms.get_chemical_symbols() forces = np.zeros((len(atoms), 3), dtype=np.double) for i in range(len(atoms)): indices, offsets = self.neighbors.get_neighbors(i) type_i = atom_types[i] index_i = i + 1 index_j = np.empty(indices.size, dtype=np.intc) coords_i = atoms.positions[i] coords_j = np.empty((indices.size, 3), dtype=np.double) types_j = [] inb = 0 for j, offset in zip(indices, offsets): coo = (atoms.positions[j] + np.dot(offset, atoms.get_cell())) d2 = np.sum((coo - coords_i)**2) if d2 <= self.cutoff2: coords_j[inb] = coo index_j[inb] = j + 1 types_j.append(atom_types[j]) inb += 1 energy += self.ann.atomic_energy_and_forces( coords_i, type_i, index_i, coords_j[:inb], types_j, index_j[:inb], forces) self.results['energy'] = energy self.results['forces'] = forces
def newclus(ind1, ind2, Optimizer): """Select a box in the cluster configuration""" if 'CX' in Optimizer.debug: debug = True else: debug = False Optimizer.output.write('Box Cluster Cx between individual ' + repr(ind1.index) + ' and individual ' + repr(ind2.index) + '\n') #Perserve starting conditions of individual solid1 = ind1[0].copy() solid2 = ind2[0].copy() cello1 = ind1[0].get_cell() cello2 = ind2[0].get_cell() cell1 = numpy.maximum.reduce(solid1.get_positions()) cell1m = numpy.minimum.reduce(solid1.get_positions()) cell2 = numpy.maximum.reduce(solid2.get_positions()) cell2m = numpy.minimum.reduce(solid2.get_positions()) cell = numpy.minimum(cell1, cell2) pbc1 = solid1.get_pbc() pbc2 = solid2.get_pbc() #Get starting concentrations and number of atoms nat1 = len(solid1) nat2 = len(solid2) # Pick a origin point for box in the cell pt1 = random.choice(solid1) pt1f = [(pt1.position[i] - cell1m[i]) / cell1[i] for i in range(3)] pt2 = [pt1f[i] * cell2[i] + cell2m[i] for i in range(3)] solid2.append(Atom(position=pt2)) pt2 = solid2[len(solid2) - 1] #Find max neighborsize of circle cut r = random.uniform(0, min(nat1, nat2) / 5.0) if debug: print 'DEBUG CX: Point one =', pt1.position print 'DEBUG CX: Point two =', pt2.position #Find atoms within sphere of neighborsize r for both individuals #Make sure that crossover is only selection of atoms not all while True: ctoff = [r for on in solid1] nl = NeighborList(ctoff, bothways=True, self_interaction=False) nl.update(solid1) indices1, offsets = nl.get_neighbors(pt1.index) if len(indices1) == 0: r = r * 1.2 elif len(indices1) < nat1 * .75: break else: r = r * 0.8 if debug: print 'Neighborsize of box = ' + repr( r) + '\nPosition in solid1 = ' + repr( pt1.position) + '\nPosition in solid2 = ' + repr(pt2.position) group1 = Atoms(cell=solid1.get_cell(), pbc=solid1.get_pbc()) group1.append(pt1) indices1a = [pt1.index] for index, d in zip(indices1, offsets): if index not in indices1a: index = int(index) pos = solid1[index].position + numpy.dot(d, solid1.get_cell()) group1.append(Atom(symbol=solid1[index].symbol, position=pos)) indices1a.append(index) indices1 = indices1a ctoff = [r for on in solid2] nl = NeighborList(ctoff, bothways=True, self_interaction=False) nl.update(solid2) indices2, offsets = nl.get_neighbors(pt2.index) group2 = Atoms(cell=solid2.get_cell(), pbc=solid2.get_pbc()) indices2a = [] for index, d in zip(indices2, offsets): if index not in indices2a: index = int(index) pos = solid2[index].position + numpy.dot(d, solid2.get_cell()) group2.append(Atom(symbol=solid2[index].symbol, position=pos)) indices2a.append(index) indices2 = indices2a if len(indices2) == 0: for one in group1: while True: sel = random.choice(solid2) if sel.symbol == one.symbol: if sel.index not in indices2: group2.append(sel) indices2.append(sel.index) break if Optimizer.forcing == 'Concentration': symlist = list(set(group1.get_chemical_symbols())) seplist = [[atm for atm in group2 if atm.symbol == sym] for sym in symlist] group2n = Atoms(cell=group2.get_cell(), pbc=group2.get_pbc()) indices2n = [] dellist = [] for one in group1: sym1 = one.symbol listpos = [i for i, s in enumerate(symlist) if s == sym1][0] if len(seplist[listpos]) > 0: pos = random.choice(range(len(seplist[listpos]))) group2n.append(seplist[listpos][pos]) indices2n.append(indices2[seplist[listpos][pos].index]) del seplist[listpos][pos] else: dellist.append(one.index) if len(dellist) != 0: dellist.sort(reverse=True) for one in dellist: del group1[one] del indices1[one] indices2n.append(pt2.index) indices2 = indices2n group2 = group2n.copy() else: dellist = [] while len(group2) < len(group1) - len(dellist): #Too many atoms in group 1 dellist.append(random.choice(group1).index) if len(dellist) != 0: dellist.sort(reverse=True) for one in dellist: del group1[one] del indices1[one] dellist = [] while len(group1) < len(group2) - len(dellist): #Too many atoms in group 2 dellist.append(random.choice(group2).index) if len(dellist) != 0: dellist.sort(reverse=True) for one in dellist: del group2[one] del indices2[one] other2 = Atoms(cell=solid2.get_cell(), pbc=solid2.get_pbc()) for one in solid2: if one.index not in indices2: other2.append(one) other1 = Atoms(cell=solid1.get_cell(), pbc=solid1.get_pbc()) for one in solid1: if one.index not in indices1: other1.append(one) #Exchange atoms in sphere and build new solids nsolid1 = other1.copy() nsolid1.extend(group2.copy()) nsolid2 = other2.copy() nsolid2.extend(group1.copy()) #DEBUG: Write crossover to file if debug: write_xyz(Optimizer.debugfile, nsolid1, 'CX(randalloybx):nsolid1') write_xyz(Optimizer.debugfile, nsolid2, 'CX(randalloybx):nsolid2') #DEBUG: Check structure of atoms exchanged for sym, c, m, u in Optimizer.atomlist: if Optimizer.structure == 'Defect': nc = len([atm for atm in nsolid1 if atm.symbol == sym]) nc += len([atm for atm in ind1.bulki if atm.symbol == sym]) oc = len([atm for atm in solid1 if atm.symbol == sym]) oc += len([atm for atm in ind1.bulki if atm.symbol == sym]) else: nc = len([atm for atm in nsolid1 if atm.symbol == sym]) oc = len([atm for atm in solid1 if atm.symbol == sym]) Optimizer.output.write('CX(clustbx):New solid1 contains ' + repr(nc) + ' ' + repr(sym) + ' atoms\n') if debug: print 'DEBUG CX: New solid1 contains ' + repr(nc) + ' ' + repr( sym) + ' atoms' if oc != nc: #pdb.set_trace() print 'CX: Issue in maintaining atom concentration\n Dropping new individual' Optimizer.output.write( 'CX: Issue in maintaining atom concentration\n Dropping new individual 1\n' ) nsolid1 = solid1 for sym, c, m, u in Optimizer.atomlist: if Optimizer.structure == 'Defect': nc = len([atm for atm in nsolid2 if atm.symbol == sym]) nc += len([atm for atm in ind2.bulki if atm.symbol == sym]) oc = len([atm for atm in solid2 if atm.symbol == sym]) oc += len([atm for atm in ind2.bulki if atm.symbol == sym]) else: nc = len([atm for atm in nsolid2 if atm.symbol == sym]) oc = len([atm for atm in solid2 if atm.symbol == sym]) Optimizer.output.write('CX(clustbx):New solid2 contains ' + repr(nc) + ' ' + repr(sym) + ' atoms\n') if debug: print 'DEBUG CX: New solid2 contains ' + repr(nc) + ' ' + repr( sym) + ' atoms' if oc != nc: #pdb.set_trace() print 'CX: Issue in maintaining atom concentration\n Dropping new individual' Optimizer.output.write( 'CX: Issue in maintaining atom concentration\n Dropping new individual 2\n' ) solid2.pop() nsolid2 = solid2 if Optimizer.forcing != 'Concentration': for i in range(len(Optimizer.atomlist)): atms1 = [ inds for inds in nsolid1 if inds.symbol == Optimizer.atomlist[i][0] ] atms2 = [ inds for inds in nsolid2 if inds.symbol == Optimizer.atomlist[i][0] ] if len(atms1) == 0: if len(atms2) == 0: nsolid1[random.randint( 0, len(indi1) - 1)].symbol == Optimizer.atomlist[i][0] nsolid2[random.randint( 0, len(indi2) - 1)].symbol == Optimizer.atomlist[i][0] else: nsolid1.append(atms2[random.randint(0, len(atms2) - 1)]) nsolid1.pop(random.randint(0, len(nsolid1) - 2)) else: if len(atms2) == 0: nsolid2.append(atms1[random.randint(0, len(atms1) - 1)]) nsolid2.pop(random.randint(0, len(nsolid2) - 2)) nsolid1.set_cell(cello1) nsolid2.set_cell(cello2) nsolid1.set_pbc(pbc1) nsolid2.set_pbc(pbc2) ind1[0] = nsolid1.copy() ind2[0] = nsolid2.copy() return ind1, ind2
bulk *= (N, N, N) bulk.set_calculator(model.calculator) e_bulk_per_atom = bulk.get_potential_energy() / len(bulk) print 'e_bulk_per_atom', e_bulk_per_atom if remove_index == 'center': half_cell = np.diag(bulk.cell) / 2. remove_index = ((bulk.positions - half_cell)**2).sum(axis=1).argmin() initial = bulk.copy() orig_pos = initial.get_positions()[remove_index, :] nl = NeighborList([a0 * sqrt(3.0) / 4 * 0.6] * len(initial), self_interaction=False, bothways=True) nl.update(initial) indices, offsets = nl.get_neighbors(remove_index) remove_index_f = None initial.arrays['ind'] = array([i for i in range(len(initial))]) offset_factor = 0.13 for i, offset in zip(indices, offsets): ri = initial.positions[remove_index] - (initial.positions[i] + dot(offset, initial.get_cell())) if remove_index_f is None: remove_index_f = i print "initial offset ", i, offset_factor, ri initial.positions[i] += offset_factor * ri offset_factor += 0.01 del initial[remove_index] initial.set_calculator(model.calculator)
def create_stucture(ratio, width, edge, key = 'top', a = 1.42): vacuum = 10 length = get_length(ratio, width, edge) length_b= int(length * .8)*2 width_b = width * 4 if edge == 'zz': orig = [np.sqrt(3)*a, 2*a] # -> AB-stacking b_l, b_w= 4, 3*width if width % 2 == 1: raise if edge == 'ac': orig = [2*a, np.sqrt(3)*a] # -> AB-stacking b_l, b_w= 2, 3*width if width % 2 == 0: raise bottom = graphene_nanoribbon2(length_b, width_b, edge_type=edge, saturated=False, C_H=1.09, C_C=a, vacuum=2.5, sheet=False, main_element='C', saturate_element='H') top = graphene_nanoribbon2(length, width, edge_type=edge, saturated=True, C_H=1.09, C_C=a, vacuum=2.5, sheet=False, main_element='C', saturate_element='H') base = graphene_nanoribbon2(b_l, b_w, edge_type=edge, saturated=True, C_H=1.09, C_C=a, vacuum=2.5, sheet=False, main_element='C', saturate_element='H') if key == 'top_bottom': top.translate([orig[0],orig[1],3.4]) atoms = bottom atoms.extend(top) atoms.cell = [bottom.cell[0,0], bottom.cell[1,1], 2 * vacuum + 3.4] atoms.center() atoms.pbc = [True, True, False] #view(atoms) return atoms if key == 'top': atoms = top L, W = top.cell[0,0], top.cell[1,1] atoms.cell = [top.cell[0,0]*1.5, 2*top.cell[0,0] + top.cell[1,1], 2 * vacuum] atoms.center() atoms.positions[:,2] = 3.4 atoms.pbc = [False, False, False] return atoms, L, W, length, None if key == 'rib+base': if edge == 'zz': base.translate([-base.cell[0,0], -base.cell[1,1]/3 ,0]) if edge == 'ac': base.translate([-base.cell[0,0], -base.cell[1,1]/3 + np.sqrt(3) / 2. * a,0]) atoms = top + base rads = np.ones(len(atoms))*.3 nl = NeighborList(rads, self_interaction=False, bothways=True) nl.update(atoms) coll = [] for i, atom in enumerate(atoms): if atom.number == 1: if 1 < len(nl.get_neighbors(i)[0]): for j in nl.get_neighbors(i)[0]: if atoms.numbers[j] == 1: coll.append(j) del atoms[coll] atoms_base = [] for i, atom in enumerate(atoms): if atom.position[0] < 0: atoms_base.append(i) rads = np.ones(len(atoms))*.7 nl = NeighborList(rads, self_interaction=False, bothways=True) nl.update(atoms) new_pos = [] for i, atom in enumerate(atoms): if len(nl.get_neighbors(i)[0]) == 2 and atom.number == 6 and i not in atoms_base: pos = np.zeros(3) for j in nl.get_neighbors(i)[0]: pos += atoms.positions[j] - atoms.positions[i] if atoms.numbers[j] != 6: raise new_pos.append(atoms.positions[i] - pos) for pos in new_pos: atoms += Atom('C', position = pos) rads = np.ones(len(atoms))*.7 nl = NeighborList(rads, self_interaction=False, bothways=True) nl.update(atoms) h_pos = [] for i, atom in enumerate(atoms): if len(nl.get_neighbors(i)[0]) == 2 and atom.number == 6: pos = np.zeros(3) for j in nl.get_neighbors(i)[0]: pos += atoms.positions[j] - atoms.positions[i] if atoms.numbers[j] != 6: raise pos = pos/np.linalg.norm(pos) h_pos.append(atoms.positions[i] - pos) for pos in h_pos: atoms += Atom('H', position = pos) L, W = top.cell[0,0], top.cell[1,1] atoms.cell = [top.cell[0,0]*1.5, 2*top.cell[0,0] + top.cell[1,1], 2 * vacuum] atoms.center() atoms.positions[:,2] = 3.4 atoms.pbc = [False, False, False] return atoms, L, W, length, atoms_base
def swap_int_local(indiv, Optimizer): """Move function to perform n atom structure swap based on swaplist for cluster in local region Inputs: indiv = Individual class object to be altered Optimizer = Optimizer class object with needed parameters Outputs: indiv = Altered Individual class object """ if 'MU' in Optimizer.debug: debug = True else: debug = False Optimizer.output.write('Local Cluster Swap Mutation performed on individual\n') Optimizer.output.write('Index = '+repr(indiv.index)+'\n') # Identify the local atoms available for swapping atmsst = indiv[0].copy() nat = len(atmsst) totalsol = atmsst.copy() if Optimizer.structure=='Defect': totalsol.extend(indiv.bulki.copy()) ctoff = [Optimizer.sf for one in totalsol] else: ctoff = [1.5 for one in totalsol] nl = NeighborList(ctoff, bothways=True, self_interaction=False) nl.update(totalsol) cluster = Atoms() indices = [] #store indices of the atoms for later for i in range(nat): indices1, offsets = nl.get_neighbors(i) for index, d in zip(indices1,offsets): index = int(index) #Make sure to prevent repeats of the same atom if index not in indices: pos = totalsol[index].position + numpy.dot(d,totalsol.get_cell()) cluster.append(Atom(symbol=totalsol[index].symbol,position=pos)) indices.append(index) # Identify Number of atoms to swap swapmax=sum([c for sym,c in indiv.swaplist]) if swapmax >1: natomsswap=random.randint(1,swapmax) elif swapmax==1: natomsswap=1 else: Optimizer.output.write('Maximum number of swaps is '+repr(swapmax)+'. Unable to perform mutation') natomsswap=0 if len(indiv.swaplist)==1: Optimizer.output.write('WARNING: Swap Mutation attempted on single atom structure system\n') natomsswap=0 Optimizer.output.write('Number of swaps = '+repr(natomsswap)+'\n') # Identify symbols that can be swapped syms=[sym for sym,c in indiv.swaplist] slist = copy.deepcopy(indiv.swaplist) if debug: print 'Starting swaplist = '+repr(indiv.swaplist) #Sanity check sanch = [[sym,0] for sym in syms] for i in range(len(sanch)): nc = len([atm for atm in cluster if atm.symbol==sanch[i][0]]) nc += [c for sym,c in slist if sym==sanch[i][0]][0] sanch[i][1]=nc # Swap Atoms for i in range(natomsswap): while True: at1 = random.choice(cluster) osym = at1.symbol nsyml=[sym for sym,c in slist if sym != osym and c > 0] if len(nsyml) > 0: break nsym = random.choice(nsyml) cluster[at1.index].symbol = nsym Optimizer.output.write('Swapped '+nsym+' atom with '+osym+'\n') for i in range(len(slist)): if slist[i][0]==nsym: slist[i][1]-=1 elif slist[i][0]==osym: slist[i][1]+=1 # Apply the new swap to the actual atoms in the structure for i in range(len(indices)): totalsol[indices[i]].symbol = cluster[i].symbol # Separate out bulk from defects indiv[0] = totalsol[0:nat] if Optimizer.structure=='Defect': indiv.bulki = totalsol[nat::] # Update new swaplist indiv.swaplist = slist #Sanity check sanchn = [[sym,0] for sym in syms] for i in range(len(sanchn)): nc = len([atm for atm in cluster if atm.symbol==sanchn[i][0]]) nc += [c for sym,c in slist if sym==sanchn[i][0]][0] sanchn[i][1]=nc if debug: for i in range(len(sanch)): print 'INTSWAP: Starting Atom structure '+repr(sanch[i][0])+' = '+repr(sanch[i][1]) print 'INTSWAP: After Mutation Atom structure '+repr(sanchn[i][0])+' = '+repr(sanchn[i][1]) Optimizer.output.write(repr(indiv[0])+'\n') muttype='SCL'+repr(natomsswap) if indiv.energy==0: indiv.history_index=indiv.history_index+'m'+muttype else: indiv.history_index=repr(indiv.index)+'m'+muttype return indiv
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 btypes, atypes, dtypes = self.write_lammps_atoms(atoms) self.write_lammps_definitions(atoms, btypes, atypes, dtypes) self.write_lammps_in() def write_lammps_in(self): # XXX change this # XXX some input file for syntax checks # XXX change this fileobj = self.prefix + '_in' if isinstance(fileobj, str): fileobj = open(fileobj, 'w') fileobj.write(""" # (written by ASE) clear variable dump_file string "dump_traj" variable data_file string "dump_data" units metal boundary p p f atom_style full """) fileobj.write('read_data ' + self.prefix + '_atoms\n') fileobj.write('include ' + self.prefix + '_opls\n') fileobj.write(""" ### run fix fix_nve all nve dump dump_all all custom 1 trj_lammps id type x y z vx vy vz fx fy fz thermo_style custom step temp press cpu pxx pyy pzz pxy pxz pyz ke pe etotal vol lx ly lz atoms thermo_modify flush yes thermo 1 run 0 print "__end_of_ase_invoked_calculation__" log /dev/stdout """) fileobj.close() def write_lammps_atoms(self, atoms): """Write atoms infor for LAMMPS""" fileobj = self.prefix + '_atoms' if isinstance(fileobj, str): fileobj = open(fileobj, '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') btypes, blist = self.get_bonds(atoms) if len(blist): fileobj.write(str(len(blist)) + ' bonds\n') fileobj.write(str(len(btypes)) + ' bond types\n') atypes, alist = self.get_angles() if len(alist): fileobj.write(str(len(alist)) + ' angles\n') fileobj.write(str(len(atypes)) + ' angle types\n') dtypes, dlist = self.get_dihedrals(alist, atypes) if len(dlist): 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() for i, r in enumerate(map(p.pos_to_lammps_str, atoms.get_positions())): q = 0 # charge will be overwritten fileobj.write('%6d %3d %3d %s %s %s %s' % ((i + 1, 1, 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)) fileobj.write('# ' + btypes[bvals[0]] + '\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)) fileobj.write('# ' + atypes[avals[0]] + '\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)) fileobj.write('# ' + dtypes[dvals[0]] + '\n') return btypes, atypes, dtypes 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() positions = atoms.get_positions() 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() positions = atoms.get_positions() 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: 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') # Coulomb fileobj.write(""" # Coulomb kspace_style pppm 1e-5 kspace_modify slab 3.0 """) # 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 check_min_dist(totalsol, type='Defect', nat=None, min_len=0.7, STR=''): if type=='Defect' or type=='Crystal' or type=='Surface': if nat==None: nat=len(totalsol) cutoffs=[2.0 for one in totalsol] nl=NeighborList(cutoffs,bothways=True,self_interaction=False) nl.update(totalsol) for one in totalsol[0:nat]: nbatoms=Atoms() nbatoms.append(one) indices, offsets=nl.get_neighbors(one.index) for index, d in zip(indices,offsets): index = int(index) sym=totalsol[index].symbol pos=totalsol[index].position + numpy.dot(d,totalsol.get_cell()) at=Atom(symbol=sym,position=pos) nbatoms.append(at) while True: dflag=False for i in range(1,len(nbatoms)): d=nbatoms.get_distance(0,i) if d < min_len: nbatoms.set_distance(0,i,min_len+.01,fix=0.5) STR+='--- WARNING: Atoms too close (<0.7A) - Implement Move ---\n' dflag=True if dflag==False: break for i in range(len(indices)): totalsol[indices[i]].position=nbatoms[i+1].position totalsol[one.index].position=nbatoms[0].position nl.update(totalsol) elif type=='Cluster': for i in range(len(totalsol)): for j in range(len(totalsol)): if i != j: d=totalsol.get_distance(i,j) if d < min_len: totalsol.set_distance(i,j,min_len,fix=0.5) STR+='--- WARNING: Atoms too close (<0.7A) - Implement Move ---\n' else: print 'WARNING: In Check_Min_Dist in EvalEnergy: Structure Type not recognized' return totalsol, STR
def eval_energy(input): """Function to evaluate energy of an individual Inputs: input = [Optimizer class object with parameters, Individual class structure to be evaluated] Outputs: energy, bul, individ, signal energy = energy of Individual evaluated bul = bulk structure of Individual if simulation structure is Defect individ = Individual class structure evaluated signal = string of information about evaluation """ if input[0]==None: energy=0 bul=0 individ=0 rank = MPI.COMM_WORLD.Get_rank() signal='Evaluated none individual on '+repr(rank)+'\n' else: [Optimizer, individ]=input if Optimizer.calc_method=='MAST': energy = individ.energy bul = individ.energy signal = 'Recieved MAST structure\n' else: if Optimizer.parallel: rank = MPI.COMM_WORLD.Get_rank() if not Optimizer.genealogy: STR='----Individual ' + str(individ.index)+ ' Optimization----\n' else: STR='----Individual ' + str(individ.history_index)+ ' Optimization----\n' indiv=individ[0] if 'EE' in Optimizer.debug: debug = True else: debug = False if debug: write_xyz(Optimizer.debugfile,indiv,'Recieved by eval_energy') Optimizer.debugfile.flush() if Optimizer.structure=='Defect': indi=indiv.copy() if Optimizer.alloy==True: bulk=individ.bulki else: bulk=individ.bulko nat=indi.get_number_of_atoms() csize=bulk.get_cell() totalsol=Atoms(cell=csize, pbc=True) totalsol.extend(indi) totalsol.extend(bulk) for sym,c,m,u in Optimizer.atomlist: nc=len([atm for atm in totalsol if atm.symbol==sym]) STR+='Defect configuration contains '+repr(nc)+' '+repr(sym)+' atoms\n' elif Optimizer.structure=='Surface': totalsol=Atoms() totalsol.extend(indiv) nat=indiv.get_number_of_atoms() totalsol.extend(individ.bulki) for sym,c,m,u in Optimizer.atomlist: nc=len([atm for atm in totalsol if atm.symbol==sym]) STR+='Surface-Bulk configuration contains '+repr(nc)+' '+repr(sym)+' atoms\n' cell=numpy.maximum.reduce(indiv.get_cell()) totalsol.set_cell([cell[0],cell[1],500]) totalsol.set_pbc([True,True,False]) if Optimizer.constrain_position: ts = totalsol.copy() indc,indb,vacant,swap,stro = find_defects(ts,Optimizer.solidbulk,0) sbulk = Optimizer.solidbulk.copy() bcom = sbulk.get_center_of_mass() #totalsol.translate(-bulkcom) #indc.translate(-bulkcom) #totalsol.append(Atom(position=[0,0,0])) # for one in indc: # index = [atm.index for atm in totalsol if atm.position[0]==one.position[0] and atm.position[1]==one.position[1] and atm.position[2]==one.position[2]][0] # if totalsol.get_distance(-1,index) > Optimizer.sf: # r = random.random() # totalsol.set_distance(-1,index,Optimizer.sf*r,fix=0) # totalsol.pop() # totalsol.translate(bulkcom) com = indc.get_center_of_mass() dist = (sum((bcom[i] - com[i])**2 for i in range(3)))**0.5 if dist > Optimizer.sf: STR+='Shifting structure to within region\n' r = random.random()*Optimizer.sf comv = numpy.linalg.norm(com) ncom = [one*r/comv for one in com] trans = [ncom[i]-com[i] for i in range(3)] indices = [] for one in indc: id = [atm.index for atm in totalsol if atm.position[0]==one.position[0] and atm.position[1]==one.position[1] and atm.position[2]==one.position[2]][0] totalsol[id].position += trans # Check for atoms that are too close min_len=0.7 #pdb.set_trace() if not Optimizer.fixed_region: if Optimizer.structure=='Defect' or Optimizer.structure=='Surface': cutoffs=[2.0 for one in totalsol] nl=NeighborList(cutoffs,bothways=True,self_interaction=False) nl.update(totalsol) for one in totalsol[0:nat]: nbatoms=Atoms() nbatoms.append(one) indices, offsets=nl.get_neighbors(one.index) for index, d in zip(indices,offsets): index = int(index) sym=totalsol[index].symbol pos=totalsol[index].position + numpy.dot(d,totalsol.get_cell()) at=Atom(symbol=sym,position=pos) nbatoms.append(at) while True: dflag=False for i in range(1,len(nbatoms)): d=nbatoms.get_distance(0,i) if d < min_len: nbatoms.set_distance(0,i,min_len+.01,fix=0.5) STR+='--- WARNING: Atoms too close (<0.7A) - Implement Move ---\n' dflag=True if dflag==False: break for i in range(len(indices)): totalsol[indices[i]].position=nbatoms[i+1].position totalsol[one.index].position=nbatoms[0].position nl.update(totalsol) if debug: write_xyz(Optimizer.debugfile,totalsol,'After minlength check') Optimizer.debugfile.flush() else: for i in range(len(indiv)): for j in range(len(indiv)): if i != j: d=indiv.get_distance(i,j) if d < min_len: indiv.set_distance(i,j,min_len,fix=0.5) STR+='--- WARNING: Atoms too close (<0.7A) - Implement Move ---\n' if debug: write_xyz(Optimizer.debugfile,indiv,'After minlength check') Optimizer.debugfile.flush() # Set calculator to use to get forces/energies if Optimizer.parallel: calc = setup_calculator(Optimizer) if Optimizer.fixed_region: pms=copy.deepcopy(calc.parameters) try: pms['mass'][len(pms['mass'])-1] += '\ngroup RO id >= '+repr(nat)+'\nfix freeze RO setforce 0.0 0.0 0.0\n' except KeyError: pms['pair_coeff'][0] += '\ngroup RO id >= '+repr(nat)+'\nfix freeze RO setforce 0.0 0.0 0.0\n' calc = LAMMPS(parameters=pms, files=calc.files, keep_tmp_files=calc.keep_tmp_files, tmp_dir=calc.tmp_dir) lmin = copy.copy(Optimizer.lammps_min) Optimizer.lammps_min = None Optimizer.static_calc = setup_calculator(Optimizer) Optimizer.lammps_min = lmin else: calc=Optimizer.calc if Optimizer.structure=='Defect' or Optimizer.structure=='Surface': totalsol.set_calculator(calc) totalsol.set_pbc(True) else: indiv.set_calculator(calc) indiv.set_pbc(True) #Current bug in ASE optimizer-Lammps prevents pbc=false if Optimizer.structure=='Cluster': indiv.set_cell([500,500,500]) indiv.translate([250,250,250]) cwd=os.getcwd() # Perform Energy Minimization if not Optimizer.parallel: Optimizer.output.flush() if Optimizer.ase_min == True: try: if Optimizer.structure=='Defect' or Optimizer.structure=='Surface': dyn=BFGS(totalsol) else: dyn=BFGS(indiv) dyn.run(fmax=Optimizer.ase_min_fmax, steps=Optimizer.ase_min_maxsteps) except OverflowError: STR+='--- Error: Infinite Energy Calculated - Implement Random ---\n' box=Atoms() indiv=gen_pop_box(Optimizer.natoms, Optimizer.atomlist, Optimizer.size) indiv.set_calculator(calc) dyn=BFGS(indiv) dyn.run(fmax=fmax, steps=steps) except numpy.linalg.linalg.LinAlgError: STR+='--- Error: Singular Matrix - Implement Random ---\n' indiv=gen_pop_box(Optimizer.natoms, Optimizer.atomlist, Optimizer.size) indiv.set_calculator(calc) dyn=BFGS(indiv) dyn.run(fmax=fmax, steps=steps) # Get Energy of Minimized Structure if Optimizer.structure=='Defect' or Optimizer.structure=='Surface': en=totalsol.get_potential_energy() #force=numpy.maximum.reduce(abs(totalsol.get_forces())) if Optimizer.fitness_scheme == 'enthalpyfit': pressure=totalsol.get_isotropic_pressure(totalsol.get_stress()) cell_max=numpy.maximum.reduce(totalsol.get_positions()) cell_min=numpy.minimum.reduce(totalsol.get_positions()) cell=cell_max-cell_min volume=cell[0]*cell[1]*cell[2] else: pressure=0 volume=0 na=totalsol.get_number_of_atoms() ena=en/na energy=en individ[0]=totalsol[0:nat] bul=totalsol[(nat):len(totalsol)] STR+='Number of positions = '+repr(len(bul)+len(individ[0]))+'\n' individ[0].set_cell(csize) indiv=individ[0] else: en=indiv.get_potential_energy() if Optimizer.fitness_scheme == 'enthalpyfit': pressure=indiv.get_isotropic_pressure(indiv.get_stress()) cell_max=numpy.maximum.reduce(indiv.get_positions()) cell_min=numpy.minimum.reduce(indiv.get_positions()) cell=cell_max-cell_min volume=cell[0]*cell[1]*cell[2] else: pressure=0 volume=0 na=indiv.get_number_of_atoms() ena=en/na energy=ena individ[0]=indiv bul=0 else: if Optimizer.structure=='Defect' or Optimizer.structure=='Surface': if Optimizer.calc_method=='VASP': en=totalsol.get_potential_energy() calcb=Vasp(restart=True) totalsol=calcb.get_atoms() stress=calcb.read_stress() else: try: totcop=totalsol.copy() if debug: write_xyz(Optimizer.debugfile,totcop,'Individual sent to lammps') OUT=totalsol.calc.calculate(totalsol) totalsol=OUT['atoms'] totalsol.set_pbc(True) if Optimizer.fixed_region: if debug: print 'Energy of fixed region calc = ', OUT['thermo'][-1]['pe'] totalsol.set_calculator(Optimizer.static_calc) OUT=totalsol.calc.calculate(totalsol) totalsol=OUT['atoms'] totalsol.set_pbc(True) if debug: print 'Energy of static calc = ', OUT['thermo'][-1]['pe'] en=OUT['thermo'][-1]['pe'] stress=numpy.array([OUT['thermo'][-1][i] for i in ('pxx','pyy','pzz','pyz','pxz','pxy')])*(-1e-4*GPa) #force=numpy.maximum.reduce(abs(totalsol.get_forces())) if debug: write_xyz(Optimizer.debugfile,totalsol,'After Lammps Minimization') Optimizer.debugfile.flush() except Exception, e: os.chdir(cwd) STR+='WARNING: Exception during energy eval:\n'+repr(e)+'\n' f=open('problem-structures.xyz','a') write_xyz(f,totcop,data='Starting structure hindex='+individ.history_index) write_xyz(f,totalsol,data='Lammps Min structure') en=10 stress=0 f.close() if Optimizer.fitness_scheme == 'enthalpyfit': pressure=totalsol.get_isotropic_pressure(stress) cell_max=numpy.maximum.reduce(totalsol.get_positions()) cell_min=numpy.minimum.reduce(totalsol.get_positions()) cell=cell_max-cell_min volume=cell[0]*cell[1]*cell[2] else: pressure=totalsol.get_isotropic_pressure(stress) volume=0 na=totalsol.get_number_of_atoms() ena=en/na energy=en if Optimizer.structure=='Defect': if Optimizer.fixed_region==True or Optimizer.finddefects==False: individ[0]=totalsol[0:nat] bul=totalsol[(nat):len(totalsol)] individ[0].set_cell(csize) else: if 'FI' in Optimizer.debug: outt=find_defects(totalsol,Optimizer.solidbulk,Optimizer.sf,atomlistcheck=Optimizer.atomlist,trackvacs=Optimizer.trackvacs,trackswaps=Optimizer.trackswaps,debug=Optimizer.debugfile) else: outt=find_defects(totalsol,Optimizer.solidbulk,Optimizer.sf,atomlistcheck=Optimizer.atomlist,trackvacs=Optimizer.trackvacs,trackswaps=Optimizer.trackswaps,debug=False) individ[0]=outt[0] bul=outt[1] individ.vacancies = outt[2] individ.swaps = outt[3] STR += outt[4] indiv=individ[0] else: top,bul=find_top_layer(totalsol,Optimizer.surftopthick) indiv=top.copy() individ[0]=top.copy() else: