def lattice_sumX(func, positions, elem_cell=np.eye(3), periodic_directions=(False, False, False), cutoff_radius=DF_CUTOFF_RADIUS): """Computes the lattice sum of interactions within the (0,0,0) copy as well as the interactions to all other copies that lie whithin a sphere defined by the "cutoff_radius". Thereby the interaction is defined by a distance-vector-dependent function "func" """ # Number of atoms within a single copy N_atoms = len(positions) # Maximum distance of two atoms within a copy # Serves as measure for extension of a single copy Rij_max = maxdist(positions) # use this below as cutoff radius: cutoff = cutoff_radius + Rij_max # # So far we have to provide the meaningfull (non-linearly dependent) cell vectors # also for directions that are not periodic in order to be able to invert the 3x3 # matrix. FIXME: Is there a better way? Also ASE sets the system in that way? # # compute the size of the box enclosing a sphere of radius |cutoff| # in "fractional" coordinates: box = minbox(elem_cell, cutoff) # round them in very conservative fashion, all box[i] >= 1, box = [ int(ceil(k)) for k in box ] # # This rounding approach appears to never restrict the summation # later to a single cell, rather to at least three at # # -1, 0 , +1 # # of the cell vector in each direction. # # reset the values for non-periodic directions to zero: for i in range(len(box)): if not periodic_directions[i]: # in this direction treat only the unit cell itself: box[i] = 0 # # Now we are ready to sum over all cells in the box # # Initialization of output data by call for the 000 cell f = func(np.array([0.,0.,0.])) # # Running over all residual copies in the box u = 0 v = 0 # u, v = 0 case. Summation over w, with w != 0 for w in xrange(-box[2], 0): t_vec = u * elem_cell[0] + v * elem_cell[1] + w * elem_cell[2] t2 = np.dot(t_vec, t_vec) if t2 > cutoff**2: continue f += func(t_vec) for w in xrange(1, box[2] + 1): t_vec = u * elem_cell[0] + v * elem_cell[1] + w * elem_cell[2] t2 = np.dot(t_vec, t_vec) if t2 > cutoff**2: continue f += func(t_vec) # # u = 0 case. Summation over v and w, with v != 0 for v in xrange(-box[1], 0): for w in xrange(-box[2], box[2] + 1): t_vec = u * elem_cell[0] + v * elem_cell[1] + w * elem_cell[2] t2 = np.dot(t_vec, t_vec) if t2 > cutoff**2: continue f += func(t_vec) # for w in xrange(-box[2], box[2] + 1): t_vec = u * elem_cell[0] - v * elem_cell[1] + w * elem_cell[2] t2 = np.dot(t_vec, t_vec) if t2 > cutoff**2: continue f += func(t_vec) # # General case. Summation over u, v, and w, with u != 0 for u in xrange(-box[0], 0): for v in xrange(-box[1], box[1] + 1): for w in xrange(-box[2], box[2] + 1): t_vec = u * elem_cell[0] + v * elem_cell[1] + w * elem_cell[2] t2 = np.dot(t_vec, t_vec) if t2 > cutoff**2: continue f += func(t_vec) # for v in xrange(-box[1], box[1] + 1): for w in xrange(-box[2], box[2] + 1): t_vec = -u * elem_cell[0] + v * elem_cell[1] + w * elem_cell[2] t2 = np.dot(t_vec, t_vec) if t2 > cutoff**2: continue f += func(t_vec) # return f
def lattice_sum(func, f0, f0_prime, positions, elem_cell=np.eye(3), periodic_directions=(False, False, False), cutoff_radius=DF_CUTOFF_RADIUS): """Computes the lattice sum of interactions within the (0,0,0) copy as well as the interactions to all other copies that lie whithin a sphere defined by the "cutoff_radius". Thereby the interaction is defined by a distance-vector-dependent function "func" >>> from math import pi >>> v, g = lattice_sum(lambda x: (1, 1), [[0., 0., 0.]], periodic_directions=3*(True,)) >>> v / (4. * pi / 3. * DF_CUTOFF_RADIUS**3) 1.0056889511012566 """ # Number of atoms within a single copy N_atoms = len(positions) # Maximum distance of two atoms within a copy # Serves as measure for extension of a single copy Rij_max = maxdist(positions) # use this below as cutoff radius: cutoff = cutoff_radius / AU_TO_ANG + Rij_max # # So far we have to provide the meaningfull (non-linearly dependent) cell vectors # also for directions that are not periodic in order to be able to invert the 3x3 # matrix. FIXME: Is there a better way? Also ASE sets the system in that way? # # compute the size of the box enclosing a sphere of radius |cutoff| # in "fractional" coordinates: box = minbox(elem_cell, cutoff) # round them in very conservative fashion, all box[i] >= 1, box = [ int(ceil(k)) for k in box ] # # This rounding approach appears to never restrict the summation # later to a single cell, rather to at least three at # # -1, 0 , +1 # # of the cell vector in each direction. # # reset the values for non-periodic directions to zero: for i in range(len(box)): if not periodic_directions[i]: # in this direction treat only the unit cell itself: box[i] = 0 # f = f0 f_prime = f0_prime # Now we are ready to sum over all cells in the box # # scan over indices u, v, w for u in xrange(-box[0], box[0] + 1): for v in xrange(-box[1], box[1] + 1): for w in xrange(-box[2], box[2] + 1): # Calculate translation vector to actual copy t_vec = u * elem_cell[0] + v * elem_cell[1] + w * elem_cell[2] # Calculate the squared distance to copy, cycle if that is too long: t2 = np.dot(t_vec, t_vec) if t2 > cutoff**2: continue # Calculate the dispersion interaction between # the considered pair of copies f1, fprime1 = func(t_vec) # # Actualize dispersion correction and contributions to forces f += f1 f_prime += fprime1 # # # return f, f_prime