def screened_effective_charge(born, eps): """ Compute screened effective charge tensor from Born and dielectric tensors """ screened = fzeros((3,3,3,3)) for i in frange(3): for j in frange(3): for k in frange(3): for l in frange(3): if eps[k,l] == 0.0: continue screened[i,j,k,l] = born[i,j]/np.sqrt(eps[k,l]) return screened
def __getitem__(self, i): if not self.initialised: if self is self.parent.hysteretic_connect: self.calc_connect_hysteretic(self.parent) else: self.calc_connect(self.parent) distance = farray(0.0) diff = fzeros(3) cosines = fzeros(3) shift = fzeros(3, dtype=np.int32) res = [] if not get_fortran_indexing(): i = i + 1 # convert to 1-based indexing for n in frange(self.n_neighbours(i)): j = self.neighbour(self.parent, i, n, distance, diff, cosines, shift) if not get_fortran_indexing(): j = j - 1 res.append(NeighbourInfo(j, distance, diff, cosines, shift)) if get_fortran_indexing(): res = farray(res) # to give 1-based indexing return res
def get_bond_lengths(at): """Return a dictionary mapping tuples (Species1, Species2) to an farray of bond-lengths""" at.calc_connect() r_ij = farray(0.0) res = {} for i in frange(at.n): for n in frange(at.n_neighbours(i)): j = at.neighbour(i, n, distance=r_ij) print i, j, at.z[i], at.z[j], r_ij minij, maxij = min((i, j)), max((i, j)) key = (at.species[minij].stripstrings(), at.species[maxij].stripstrings()) if not key in res: res[key] = [] res[key].append(r_ij.astype(float)) print res return dict((k, farray(v)) for (k, v) in res.iteritems())
def epsilon_infty(pot, at, deltafield=0.001, zerotol=1e-5): """ Calculate dielectric tensor. Potential must be polarisable and allow an external electic field to be applied. """ dielectric_tensor = fzeros((3,3)) celldip = fzeros((3,3)) # Calculation with no applied field pot.calc(at, force=True, restart=True, applied_efield=False) celldip0 = at.dipoles.sum(axis=2)/BOHR at.add_property('ext_efield', 0., n_cols=3) # Now we apply field along each of x,y,z in turn, and # calculate overall cell dipole moment for i in frange(3): at.ext_efield[:] = 0.0 at.ext_efield[i,:] += deltafield/(BOHR/HARTREE) pot.calc(at, force=True, applied_efield=True) celldip[:,i] = at.dipoles.sum(axis=2)/BOHR dielectric_tensor[:,i] = celldip[:,i] - celldip0 dielectric_tensor = 4.0*PI*dielectric_tensor/deltafield/(at.cell_volume()/BOHR**3) + fidentity(3) at.ext_efield[:] = 0.0 dielectric_tensor[dielectric_tensor < zerotol] = 0.0 return dielectric_tensor
def calc_effective_charge_vectors(a, born_tensor): effective_charge = fzeros(3) for i in frange(a.n): p_norm = a.phonon[i]/np.sqrt(np.dot(a.phonon[i],a.phonon[i])) disp = (p_norm/np.sqrt(ElementMass[str(a.species[i])]*MASSCONVERT))/BOHR print disp effective_charge = effective_charge + dot(born_tensor[:,:,i], disp) return effective_charge
def _indices(self): """Return array of atoms indices If global ``fortran_indexing`` is True, returns FortranArray containing numbers 1..self.n. Otherwise, returns a standard numpuy array containing numbers in range 0..(self.n-1).""" if get_fortran_indexing(): return farray(list(frange(len(self)))) else: return np.array(list(range(len(self))))
def find_atoms_within_cutoff(atoms, cutoff, d=0): tmp_atoms = Atoms(atoms, fortran_indexing=False) tmp_atoms.calc_connect() over = [] rij = farray(0.0) for i in frange(len(tmp_atoms)): for n in frange(tmp_atoms.n_neighbours(i)): j = tmp_atoms.neighbour(i, n, distance=rij) # print j if rij < cutoff and i > j: if d == 0: over.append(j - 1) else: over.append(i - 1) # for i in range(len(atoms)): #python method, may be slower than fortran index? # indices, offsets = tmp_atoms.neighbours.get_neighbors(i) # for j, offset in zip(indices, offsets): # diff=tmp_atoms.get_distance(i,j) # if diff < cutoff and i > j : #i>j is important for returning only one atom id # over.append(j) return over
def apply_cp2k_sort_order(at, rev_sort_index_filename='quip_rev_sort_index'): """Reorder atoms in `at` so that they match rev_sort_index read from file. Returns sort_index and rev_sort_index arrays.""" fields = [int(x) for x in open(rev_sort_index_filename).read().split()] rev_sort_index = farray(fields, dtype=np.int32) rev_sort_index_copy = rev_sort_index.copy() sort_index = farray(frange(at.n), dtype=np.int32) insertion_sort(rev_sort_index_copy, sort_index) at.add_property('rev_sort_index', rev_sort_index) at.sort('rev_sort_index') return sort_index, rev_sort_index
def born_effective_charge(pot, at0, dx=1e-5, args_str=None): """ Calculate Born effective charges for all atoms in at0 Potential must be polarizable, i.e. compute dipole moments. """ born_tensor = fzeros((3,3,at0.n)) restart = True for i in frange(at0.n): for j in frange(3): at = at0.copy() at.pos[j,i] -= dx at.calc_connect() pot.calc(at, force=True, restart=restart, args_str=args_str) restart = True dip1 = fzeros(3) for k in frange(at.n): dip1 += at.dipoles[k] + at.charge[k]*at.pos[:,k] at = at0.copy() at.pos[j,i] += dx at.calc_connect() pot.calc(at, force=True, restart=restart, args_str=args_str) dip2 = fzeros(3) for k in frange(at.n): dip2 += at.dipoles[k] + at.charge[k]*at.pos[:,k] born_tensor[:,j,i] = (dip2 - dip1)/(dx*2.0) return born_tensor
def force_test(at, p, dx=1e-4): """ Compare analyric and numeric forces for the Potential `p` with Atoms `at` Finite difference derivates are calculated by moving each atom by `dx`. """ analytic_f = fzeros((3, at.n)) p.calc(at, force=analytic_f) num_f = fzeros((3, at.n)) ep, em = farray(0.0), farray(0.0) for i in frange(at.n): for j in (1, 2, 3): ap = at.copy() ap.pos[j, i] += dx p.calc(ap, energy=ep) print 'e+', j, i, ep ap.pos[j, i] -= 2.0 * dx p.calc(ap, energy=em) print 'e-', j, i, em num_f[j, i] = -(ep - em) / (2 * dx) return analytic_f, num_f, analytic_f - num_f
def calc(self, at, energy=None, force=None, virial=None, local_energy=None, local_virial=None, args_str=None, error=None, **kwargs): clusters = [] orig_label = self.label if not self.mm_local: # always submit the MM calc if self.test_mode: clusters.append(at) else: self.server.put(at, 0, self.label, force_restart=self.force_restart) self.label += 1 do_qm = not self.get('method').startswith('lotf') or self.get( 'lotf_do_qm') if do_qm: #print 'REGIONS', [ k for k in at.properties.keys() if re.match('hybrid_[0-9]+', k) ] n_region = len([ k for k in at.properties.keys() if re.match('hybrid_[0-9]+', k) ]) if self.get('calc_weights'): system_timer('create_hybrid_weights') # overall hybrid property is union of all the hybrids if not hasattr(at, 'hybrid'): at.add_property('hybrid', 0) at.hybrid[:] = 0 for i in frange(n_region): hybrid = getattr(at, 'hybrid_%d' % i) at.hybrid[hybrid == HYBRID_ACTIVE_MARK] = HYBRID_ACTIVE_MARK if not hasattr(at, 'hybrid_mark'): at.add_property('hybrid_mark', at.hybrid) at.hybrid_mark[:] = 0 at.hybrid_mark = at.hybrid create_hybrid_weights_args = self.cluster_args.copy() create_hybrid_weights_args['buffer_hops'] = 0 create_hybrid_weights_args[ 'transition_hops'] = 0 # ensure a fast exit # overall hybrid -> hybrid_mark, weight_region1 create_hybrid_weights( at, args_str=quippy.util.args_str(create_hybrid_weights_args)) system_timer('create_hybrid_weights') # make clusters and submit to QM clients system_timer('make_clusters') for i in frange(n_region): hybrid_name = 'hybrid_%d' % i hybrid_mark_name = 'hybrid_mark_%d' % i if self.get('calc_weights'): hybrid = getattr(at, hybrid_name) if not hasattr(at, hybrid_mark_name): at.add_property(hybrid_mark_name, HYBRID_NO_MARK) hybrid_mark = getattr(at, hybrid_mark_name) # set marks to allow previously active atoms to become buffer atoms # create_hybrid_weights will then set the buffer marks hybrid_mark[hybrid_mark == HYBRID_ACTIVE_MARK] = HYBRID_BUFFER_MARK hybrid_mark[hybrid == HYBRID_ACTIVE_MARK] = HYBRID_ACTIVE_MARK print('region %d, sum(hybrid) %d, sum(hybrid_mark) %d' % (i, sum(hybrid), sum(hybrid_mark))) create_hybrid_weights_args = self.cluster_args.copy() create_hybrid_weights_args['run_suffix'] = '_%d' % i create_hybrid_weights_args_str = quippy.util.args_str( create_hybrid_weights_args) print 'calling create_hybrid_weights with args_str %s' % create_hybrid_weights_args_str create_hybrid_weights( at, args_str=create_hybrid_weights_args_str) cluster_args_str = quippy.util.args_str(self.cluster_args) print 'calling create_cluster_simple with args_str %s' % cluster_args_str c = create_cluster_simple(at, mark_name=hybrid_mark_name, args_str=cluster_args_str) client_id = i if self.mm_local: client_id -= 1 if self.save_clusters: c.write( os.path.join( self.rundir, 'cluster.client-%03d.label-%04d.xyz' % (client_id, self.label))) if self.test_mode: clusters.append(c) else: self.server.put(c, client_id, self.label, force_restart=self.force_restart) self.label += 1 system_timer('make_clusters') # wait for results to be ready system_timer('get_results') if self.test_mode: results = [] for i, cluster in enumerate(clusters): result = cluster.copy() result.set_cutoff(self.qm_pot.cutoff()) result.calc_connect() self.qm_pot.calc(result, force=True) result.params['label'] = orig_label + i results.append(result) else: results = self.server.get_results() system_timer('get_results') system_timer('process_results') if self.mm_local: qm_results = results else: # process MM results mm_results, qm_results = results[:1], results[1:] mm_result = mm_results[0] at.add_property('mm_force', mm_result.force, overwrite=True) if do_qm: # process QM results at.add_property('qm_force', 0., n_cols=3, overwrite=True) # extract forces from each cluster for i, r in fenumerate(qm_results): #print 'qm forces (orig order?):' #print '\n'.join(['%d: pos=%s f=%s' % (j, p, f) for j, p, f in zip(r.index, r.pos, r.force)]) client_id = i if self.mm_local: client_id -= 1 if self.save_clusters: r.write( os.path.join( self.rundir, 'results.client-%03d.label-%04d.xyz' % (client_id, r.label))) mark_name = 'hybrid_mark_%d' % i mask = getattr(r, mark_name) == HYBRID_ACTIVE_MARK #print 'Cluster %d: QM forces on atoms %s' % (i, r.index[mask]) #print r.force[:, mask].T # HL if set_fortran is false we need to reduce the index here because # the atoms object is expecting python indexing. at.qm_force[:, [ind - 1 for ind in list(r.index[mask])]] = r.force[:, mask] system_timer('process_results') # now call the parent calc() to do the force mixing etc. force_mixing_args = kwargs.copy() force_mixing_args.update(self.cluster_args) force_mixing_args['calc_weights'] = False # already done this above #print 'calling ForceMixingPotential.calc() with args %r' % force_mixing_args ForceMixingPotential.calc(self, at, energy, force, virial, local_energy, local_virial, args_str, error, **force_mixing_args)
def write(self, at): # find numbers of atoms of each type, property name and value to match, and property labels to print above numbers atnums = [] prop_vals = [] prop_vals_map = {} labels = [] if (hasattr(self, 'species_list')): if (not hasattr(at, 'species')): sys.stderr.write( "VASP writer needs species property when species_list is specified" ) sys.exit(1) property = 'species' for s in self.species_list: labels.append(s) prop_vals.append(s) prop_vals_map[s] = 1 atnums.append((at.species[:].stripstrings() == s).count()) else: property = 'Z' atnums_map = {} for i_at in frange(at.n): try: atnums_map["%d" % at.Z[i_at]] += 1 except: atnums_map["%d" % at.Z[i_at]] = 1 for Z_s in sorted(atnums_map.keys(), key=lambda entry: int(entry)): prop_vals.append(int(Z_s)) prop_vals_map[int(Z_s)] = 1 labels.append(quippy.ElementName[int(Z_s)]) atnums.append(int(atnums_map[Z_s])) # check that every atom has a valid type for i_at in frange(at.n): try: prop = getattr(at, property)[i_at].stripstrings() except: prop = getattr(at, property)[i_at] if not prop in prop_vals_map: # should probably handle situation when prop isn't a string, but that should never happen sys.stderr.write( "Failed to find property %s in prop_vals_map dictionary" % prop) sys.exit(1) swapped_a1_a2 = False vol = np.dot(at.lattice[:, 1], np.cross(at.lattice[:, 2], at.lattice[:, 3])) if (vol < 0.0): t_a1 = at.lattice[:, 1].copy() at.lattice[:, 1] = at.lattice[:, 2] at.lattice[:, 2] = t_a1[:] swapped_a1_a2 = True sys.stderr.write( "WARNING: swapped a1 and a2 to get positive scalar triple product\n" ) # Comment try: self.out.write(at.params['VASP_Comment']) if (swapped_a1_a2): self.out.write( " a1 and a2 swapped relative to input to get positive volume" ) self.out.write("\n") except: try: self.out.write(at.params['comment']) except: self.out.write('') if (swapped_a1_a2): self.out.write( " a1 and a2 swapped relative to input to get positive volume" ) self.out.write("\n") # Lattice self.out.write("1.0\n") self.out.write("%.12f %.12f %.12f\n" % (at.lattice[1, 1], at.lattice[2, 1], at.lattice[3, 1])) self.out.write("%.12f %.12f %.12f\n" % (at.lattice[1, 2], at.lattice[2, 2], at.lattice[3, 2])) self.out.write("%.12f %.12f %.12f\n" % (at.lattice[1, 3], at.lattice[2, 3], at.lattice[3, 3])) # Numbers of atoms and type labels self.out.write(" ".join(labels) + "\n") self.out.write(" ".join([("%d" % Z) for Z in atnums]) + "\n") self.out.write("Selective Dynamics\n") if (hasattr(at, 'VASP_Pos_Format')): self.out.write(at.params['VASP_Pos_Format'] + "\n") if ((at.params['VASP_Pos_Format'][0] == 'd' or at.params['VASP_Pos_Format'][0] == 'D') and swapped_a1_a2): t_p1 = at.pos[1, :].copy() at.pos[1, :] = at.pos[2, :] at.pos[2, :] = t_p1[:] else: self.out.write("Cartesian\n") # Positions for i in range(len(prop_vals)): for i_at in frange(at.n): try: match = getattr( at, property)[i_at].stripstrings() == prop_vals[i] except: match = getattr(at, property)[i_at] == prop_vals[i] if (match): self.out.write( "%.12f %.12f %.12f T T T\n" % (at.pos[1, i_at], at.pos[2, i_at], at.pos[3, i_at])) # Velocities if (hasattr(at, 'velo')): self.out.write("\n") for i in range(len(prop_vals)): for i_at in frange(at.n): try: match = getattr( at, property)[i_at].stripstrings() == prop_vals[i] except: match = getattr(at, property)[i_at] == prop_vals[i] if (match): self.out.write("%f %f %f\n" % (at.velo[1, i_at], at.velo[2, i_at], at.velo[3, i_at]))
def VASP_OUTCAR_Reader(outcar, species=None): """Read a configuration from a VASP OUTCAR file.""" if (outcar == 'stdin' or outcar == '-'): p = sys.stdin else: p = open(outcar, 'r') re_comment = re.compile("\s*POSCAR:\s*(.+)") re_potcar = re.compile("\s*POTCAR:\s*\S+\s+(\S+)") re_n_atoms = re.compile("\s*ions per type =\s*((?:\d+\s*)*)") energy_i = -1 at_i = -1 lat_i = -1 elements = [] n_at = -1 at_cur = None for lr in p: l = lr.rstrip() if (n_at <= 0): # parse header type things m = re_comment.match(l) if (m is not None): VASP_Comment = m.group(1) # print "got VASP_Comment '%s'" % VASP_Comment m = re_potcar.match(l) if (m is not None): elements.append(m.group(1)) m = re_n_atoms.match(l) if (m is not None): # print "got ions per type, groups are:" # print m.groups() lat = fzeros((3, 3)) n_types = [int(f) for f in m.group(1).split()] n_at = sum(n_types) at = Atoms(n=n_at, latttice=lat) i_at = 0 for type_i in range(len(n_types)): for j in range(n_types[type_i]): i_at += 1 # print "set species of atom %d to '%s'" % (i_at, elements[type_i]) at.species[i_at] = elements[type_i] at.set_zs() else: # parse per-config lattice/pos/force if (l.find("direct lattice vectors") >= 0): at_cur = at.copy() lat_cur = fzeros((3, 3)) lat_i = 1 elif (lat_i >= 1 and lat_i <= 3): lat_cur[:, lat_i] = [ float(r) for r in l.replace("-", " -").split()[0:3] ] lat_i += 1 elif (l.find("TOTAL-FORCE (eV/Angst)") >= 0): if (not hasattr(at_cur, "force")): at_cur.add_property("force", 0.0, n_cols=3) at_i = 1 p.next() elif (at_i >= 1 and at_i <= at_cur.n): pos_force = [ float(r) for r in l.replace("-", " -").split()[0:6] ] at_cur.pos[:, at_i] = pos_force[0:3] at_cur.force[:, at_i] = pos_force[3:6] at_i += 1 elif (l.find("free energy") >= 0): at_cur.params['Energy'] = float(l.split()[4]) energy_i = 1 p.next() elif (energy_i == 1): # print "energy(sigma->0) line" # print l.split() at_cur.params['Energy_sigma_to_zero'] = float(l.split()[6]) energy_i += 1 yield at_cur if (at_cur is not None and at_i == at_cur.n): at_cur.set_lattice(lat_cur, False) for i in frange(at_cur.n): dr = at_cur.diff_min_image(i, at.pos[:, i]) at_cur.pos[:, i] = at.pos[:, i] - dr
def calc(self, at, energy=None, force=None, virial=None, local_energy=None, local_virial=None, args_str=None, error=None, **kwargs): clusters = [] orig_label = self.label if not self.mm_local: # always submit the MM calc if self.test_mode: clusters.append(at) else: self.server.put(at, 0, self.label, force_restart=self.force_restart) self.label += 1 do_qm = not self.get('method').startswith('lotf') or self.get('lotf_do_qm') if do_qm: #print 'REGIONS', [ k for k in at.properties.keys() if re.match('hybrid_[0-9]+', k) ] n_region = len([ k for k in at.properties.keys() if re.match('hybrid_[0-9]+', k) ]) if self.get('calc_weights'): system_timer('create_hybrid_weights') # overall hybrid property is union of all the hybrids if not hasattr(at, 'hybrid'): at.add_property('hybrid', 0) at.hybrid[:] = 0 for i in frange(n_region): hybrid = getattr(at, 'hybrid_%d' % i) at.hybrid[hybrid == HYBRID_ACTIVE_MARK] = HYBRID_ACTIVE_MARK if not hasattr(at, 'hybrid_mark'): at.add_property('hybrid_mark', at.hybrid) at.hybrid_mark[:] = 0 at.hybrid_mark = at.hybrid create_hybrid_weights_args = self.cluster_args.copy() create_hybrid_weights_args['buffer_hops'] = 0 create_hybrid_weights_args['transition_hops'] = 0 # ensure a fast exit # overall hybrid -> hybrid_mark, weight_region1 create_hybrid_weights(at, args_str=quippy.util.args_str(create_hybrid_weights_args)) system_timer('create_hybrid_weights') # make clusters and submit to QM clients system_timer('make_clusters') for i in frange(n_region): hybrid_name = 'hybrid_%d' % i hybrid_mark_name = 'hybrid_mark_%d' % i if self.get('calc_weights'): hybrid = getattr(at, hybrid_name) if not hasattr(at, hybrid_mark_name): at.add_property(hybrid_mark_name, HYBRID_NO_MARK) hybrid_mark = getattr(at, hybrid_mark_name) # set marks to allow previously active atoms to become buffer atoms # create_hybrid_weights will then set the buffer marks hybrid_mark[hybrid_mark == HYBRID_ACTIVE_MARK] = HYBRID_BUFFER_MARK hybrid_mark[hybrid == HYBRID_ACTIVE_MARK] = HYBRID_ACTIVE_MARK print ('region %d, sum(hybrid) %d, sum(hybrid_mark) %d' % (i, sum(hybrid), sum(hybrid_mark))) create_hybrid_weights_args = self.cluster_args.copy() create_hybrid_weights_args['run_suffix'] = '_%d' % i create_hybrid_weights_args_str = quippy.util.args_str(create_hybrid_weights_args) print 'calling create_hybrid_weights with args_str %s' % create_hybrid_weights_args_str create_hybrid_weights(at, args_str=create_hybrid_weights_args_str) cluster_args_str = quippy.util.args_str(self.cluster_args) print 'calling create_cluster_simple with args_str %s' % cluster_args_str c = create_cluster_simple(at, mark_name=hybrid_mark_name, args_str=cluster_args_str) client_id = i if self.mm_local: client_id -= 1 if self.save_clusters: c.write(os.path.join(self.rundir, 'cluster.client-%03d.label-%04d.xyz' % (client_id, self.label))) if self.test_mode: clusters.append(c) else: self.server.put(c, client_id, self.label, force_restart=self.force_restart) self.label += 1 system_timer('make_clusters') # wait for results to be ready system_timer('get_results') if self.test_mode: results = [] for i, cluster in enumerate(clusters): result = cluster.copy() result.set_cutoff(self.qm_pot.cutoff()) result.calc_connect() self.qm_pot.calc(result, force=True) result.params['label'] = orig_label + i results.append(result) else: results = self.server.get_results() system_timer('get_results') system_timer('process_results') if self.mm_local: qm_results = results else: # process MM results mm_results, qm_results = results[:1], results[1:] mm_result = mm_results[0] at.add_property('mm_force', mm_result.force, overwrite=True) if do_qm: # process QM results at.add_property('qm_force', 0., n_cols=3, overwrite=True) # extract forces from each cluster for i, r in fenumerate(qm_results): #print 'qm forces (orig order?):' #print '\n'.join(['%d: pos=%s f=%s' % (j, p, f) for j, p, f in zip(r.index, r.pos, r.force)]) client_id = i if self.mm_local: client_id -= 1 if self.save_clusters: r.write(os.path.join(self.rundir, 'results.client-%03d.label-%04d.xyz' % (client_id, r.label))) mark_name = 'hybrid_mark_%d' % i mask = getattr(r, mark_name) == HYBRID_ACTIVE_MARK #print 'Cluster %d: QM forces on atoms %s' % (i, r.index[mask]) #print r.force[:, mask].T # HL if set_fortran is false we need to reduce the index here because # the atoms object is expecting python indexing. at.qm_force[:, [ind-1 for ind in list(r.index[mask])]] = r.force[:, mask] system_timer('process_results') # now call the parent calc() to do the force mixing etc. force_mixing_args = kwargs.copy() force_mixing_args.update(self.cluster_args) force_mixing_args['calc_weights'] = False # already done this above #print 'calling ForceMixingPotential.calc() with args %r' % force_mixing_args ForceMixingPotential.calc(self, at, energy, force, virial, local_energy, local_virial, args_str, error, **force_mixing_args)
def orthorhombic_slab(at, tol=1e-5, min_nrep=1, max_nrep=5, graphics=False, rot=None, periodicity=None, vacuum=None, shift=None, verbose=True): """Try to construct an orthorhombic cell equivalent to the primitive cell `at`, using supercells up to at most `max_nrep` repeats. Symmetry must be exact within a tolerance of `tol`. If `rot` is not None, we first transform `at` by the rotation matrix `rot`. The optional argument `periodicity` can be used to fix the periodicity one or more directions. It should be a three component vector with value zero in the unconstrained directions. The vector `vacuum` can be used to add vacuum in one or more directions. `shift` is a three component vector which can be used to shift the positions in the final cell. """ def atoms_near_plane(at, n, d, tol=1e-5): """Return a list of atoms within a distance `tol` of the plane defined by np.dot(n, at.pos) == d""" pd = np.dot(n, at.pos) - d return (abs(pd) < tol).nonzero()[0] def sort_by_distance(at, ref_atom, dir, candidates): """Return a copy of `candidates` sorted by perpendicular distance from `ref_atom` in direction `dir`""" distances_candidates = zip( [at.pos[dir, i] - at.pos[dir, ref_atom] for i in candidates], candidates) distances_candidates.sort() return [p for (d, p) in distances_candidates] def orthorhombic_box(at): """Return a copy of `at` in an orthorhombic box surrounded by vacuum""" at = at.copy() at.map_into_cell() at.set_lattice( [[2.0 * (at.pos[1, :].max() - at.pos[1, :].min()), 0.0, 0.0], [0.0, 2.0 * (at.pos[2, :].max() - at.pos[2, :].min()), 0.0], [0.0, 0.0, 2.0 * (at.pos[3, :].max() - at.pos[3, :].min())]], scale_positions=False) at.map_into_cell() return at def discard_outliers(at, indices, dirs, keep_fraction=0.5): """Return a copy of `indices` with the atoms with fractional coordinates along directions in `dirs` outside +/-`keep_fraction`/2 excluded. Lattice used is close fitting, `at.lattice`/2.""" g = np.linalg.inv(at.lattice / 2) t = np.dot(g, at.pos[:, indices]) return indices[np.logical_and( t[dirs, :] >= -keep_fraction / 2.0, t[dirs, :] < keep_fraction / 2.0).all(axis=1)] def check_candidate_plane(at, ref_plane, cand_plane, dirs, verbose=False, label=''): """Check whether in-plane displacements of atoms listed in `ref_plane` match those of `cand_plane` in directions given by `dirs`""" # Which pair of planes has more atoms, reference or candidate? if len(ref_plane) < len(cand_plane): smaller = ref_plane larger = cand_plane else: smaller = cand_plane larger = ref_plane matches = {} for j in smaller: for k in larger: if at.z[k] == at.z[j] and abs(at.pos[dirs, k] - at.pos[dirs, j]).max() < tol: matches[j] = k break if verbose: print ' ', label, len(matches), '/', len(smaller), 'matches' return len(matches) == len(smaller) if rot is not None: at = transform(at, rot) xyz = fidentity(3) nrep = min_nrep - 1 max_dist = fzeros(3) if periodicity is not None: periodicity = farray(periodicity) periodicity = dict( zip((periodicity != 0).nonzero()[0], periodicity[periodicity != 0])) else: periodicity = {} if verbose: for (dir, p) in periodicity.iteritems(): print 'Periodicity in direction %d fixed at %f' % (dir, p) if graphics: import atomeye viewer = atomeye.AtomEyeViewer() while sorted(periodicity.keys()) != [1, 2, 3]: nrep += 1 if nrep > max_nrep: raise ValueError('Maximum size of supercell (%d) exceeded' % max_nrep) if verbose: print '\n\nSupercell %d' % nrep sup = supercell(at, nrep, nrep, nrep) box = orthorhombic_box(sup) box.pos[:] = box.pos - np.tile(box.pos.mean(axis=2), [box.n, 1]).T for dir in set([1, 2, 3]) - set(periodicity.keys()): if verbose: print ' Direction %d' % dir other_dirs = list(set([1, 2, 3]) - set([dir])) pos_index = zip(box.pos[dir, :], frange(box.n)) pos_index.sort() # Find a pair of planes while pos_index: ref_pos1, ref_atom1 = pos_index.pop(0) # Find atom to define second plane while pos_index: ref_pos2, ref_atom2 = pos_index.pop(0) if abs(ref_pos2 - ref_pos1) > tol: break else: continue ref_plane1 = atoms_near_plane(box, xyz[:, dir], box.pos[dir, ref_atom1], tol) ref_plane2 = atoms_near_plane(box, xyz[:, dir], box.pos[dir, ref_atom2], tol) # Only keep reference atoms in the centre of the cell ref_plane1 = discard_outliers(box, ref_plane1, other_dirs) ref_plane2 = discard_outliers(box, ref_plane2, other_dirs) if len(ref_plane1) > 2 and len(ref_plane2) > 2: # Now we've got two planes, both with more than two atoms in them break else: # Used up all atoms without finding two planes if verbose: print ' No valid reference planes found.\n' continue if verbose: print ' Reference plane #1 through atom %d ' % ref_atom1 print ' Reference plane #2 through atom %d at distance %r\n' % ( ref_atom2, ref_pos2 - ref_pos1) if graphics: highlight = fzeros(box.n) highlight[ref_plane1] = 1 highlight[ref_plane2] = 2 box.add_property('highlight', highlight, overwrite=True) viewer.show(box, 'highlight') viewer.wait() raw_input('continue') candidates = [ i for i in frange(box.n) if box.pos[dir, i] > box.pos[dir, ref_atom2] + max_dist[dir] + tol ] candidates = sort_by_distance(box, ref_atom1, dir, candidates) while candidates: cand1 = candidates.pop(0) max_dist[dir] = box.pos[dir, cand1] - box.pos[dir, ref_atom1] if verbose: print ' Trying plane through atom %d distance %r' % ( cand1, max_dist[dir]) cand_plane1 = atoms_near_plane(box, xyz[:, dir], box.pos[dir, cand1], tol) for cand2 in sort_by_distance( box, ref_atom1, dir, set(candidates) - set(cand_plane1)): if abs((box.pos[dir, cand2] - box.pos[dir, cand1]) - (box.pos[dir, ref_atom2] - box.pos[dir, ref_atom1])) < tol: if verbose: print ' Found pair to plane, passing through atom %d distance %r ' % ( cand2, box.pos[dir, cand2] - box.pos[dir, ref_atom1]) break else: if verbose: print ' Cannot find second candidate plane.\n' candidates = sort_by_distance( box, ref_atom1, dir, set(candidates) - set(cand_plane1)) continue if graphics: highlight[cand_plane1] = 3 box.highlight[:] = highlight viewer.show(box, 'highlight') viewer.wait() cand_plane2 = atoms_near_plane(box, xyz[:, dir], box.pos[dir, cand2], tol) if graphics: highlight[cand_plane2] = 4 box.highlight[:] = highlight viewer.show(box, 'highlight') viewer.wait() highlight[cand_plane1] = 0 highlight[cand_plane2] = 0 # Remove cand_plane1 from list of candidates candidates = sort_by_distance( box, ref_atom1, dir, set(candidates) - set(cand_plane1)) # Check ref_plane1 against cand_plane1 and ref_plane2 against cand_plane2 in directions # listed in other_dirs match1 = check_candidate_plane(box, ref_plane1, cand_plane1, other_dirs, verbose, 'Plane #1:') match2 = check_candidate_plane(box, ref_plane2, cand_plane2, other_dirs, verbose, 'Plane #2:') if match1 and match2: periodicity[dir] = box.pos[dir, cand1] - box.pos[dir, ref_atom1] if verbose: print '\n Periodicity in direction %d is %f\n' % ( dir, box.pos[dir, cand1] - box.pos[dir, ref_atom1]) if graphics: highlight[cand_plane1] = 3 highlight[cand_plane2] = 3 box.highlight[:] = highlight viewer.show(box, 'highlight') viewer.wait() raw_input('continue...') break if graphics: raw_input('continue...') else: # Failed to find match for direction dir continue # Finally, construct new cell by selecting atoms within first unit cell lattice = farray(np.diag([periodicity[1], periodicity[2], periodicity[3]])) g = np.linalg.inv(lattice) nrepx, nrepy, nrepz = fit_box_in_cell(periodicity[1], periodicity[2], periodicity[3], at.lattice) sup = supercell(at, nrepx, nrepy, nrepz) sup.map_into_cell() # small shift to avoid coincidental cell alignments delta = np.tile([0.01, 0.02, 0.03], [sup.n, 1]).T if shift is not None and vacuum is not None: delta = delta + np.tile(shift, [sup.n, 1]).T t = np.dot(g, sup.pos) + delta orthorhombic = sup.select(np.logical_and(t >= -0.5, t < 0.5).all(axis=1)) if vacuum: lattice = farray(np.diag(lattice.diagonal() + vacuum)) if shift is not None and vacuum is None: if verbose: print 'Shifting positions by %s' % np.dot(lattice, shift) orthorhombic.pos += np.tile(np.dot(lattice, shift), [orthorhombic.n, 1]).T orthorhombic.set_lattice(lattice, scale_positions=False) orthorhombic.map_into_cell() return orthorhombic
def write(self, at): if "dan_graph" in at.params: if type(at.params["dan_graph"]) is StringType: graph_vals = at.params["dan_graph"].split() else: graph_vals = [at.params["dan_graph"]] self.n_graphs = len(graph_vals) if self.first_config: if self.n_graphs > 0: self.out.write("n_graphs %d\n" % self.n_graphs) self.graph_min = [1.0e38] * self.n_graphs self.graph_max = [-1.0e38] * self.n_graphs self.first_config = False self.out.write("new_configuration" + "\n") self.out.write("pbc_a 1 %f %f %f\n" % (at.lattice[1, 1], at.lattice[2, 1], at.lattice[3, 1])) self.out.write("pbc_a 2 %f %f %f\n" % (at.lattice[1, 2], at.lattice[2, 2], at.lattice[3, 2])) self.out.write("pbc_a 3 %f %f %f\n" % (at.lattice[1, 3], at.lattice[2, 3], at.lattice[3, 3])) for i_at in frange(at.n): if self.atom_type is not None: atom_type = self.atom_type else: if (hasattr(at, 'z')): atom_type = 'z' else: if (hasattr(at, 'species')): atom_type = 'species' else: if (hasattr(at, 'type')): atom_type = 'type' else: raise ValueError( "Can't find z, species, or type for atom type") px = getattr(at, self.pos_field)[1, i_at] py = getattr(at, self.pos_field)[2, i_at] pz = getattr(at, self.pos_field)[3, i_at] try: self.out.write( "atom %f %f %f %s" % (px, py, pz, getattr(at, atom_type)[i_at].stripstrings())) except: self.out.write("atom %f %f %f %s" % (px, py, pz, getattr(at, atom_type)[i_at])) if self.value is not None: if (type(self.value) is StringType): self.value = [self.value] for iv in range(len(self.value)): self.out.write(" value %d %s" % (iv + 1, getattr(at, self.value[iv])[i_at])) self.out.write("\n") if self.vector is not None: if hasattr(at, self.vector): self.out.write( "vector %f %f %f %f %f %f\n" % (px, py, pz, getattr(at, self.vector)[1, i_at], getattr(at, self.vector)[2, i_at], getattr(at, self.vector)[3, i_at])) if self.n_graphs > 0: for ig in range(len(graph_vals)): val = graph_vals[ig] self.out.write("graph_value %d %f\n" % (ig, float(val))) f_val = float(val) if (f_val < self.graph_min[ig]): self.graph_min[ig] = f_val if (f_val > self.graph_max[ig]): self.graph_max[ig] = f_val if self.bond_by_cutoff: self.out.write("bond_by_cutoff\n") if self.post_config_command is not None: if (type(self.post_config_command) is StringType): self.post_config_command = [self.post_config_command] for cmd in self.post_config_command: self.out.write(cmd + "\n")