def neighbour_list(quantities, a, cutoff): """ Compute a neighbour list for an atomic configuration. Parameters ---------- quantities : str Quantities to compute by the neighbor list algorithm. Each character in this string defines a quantity. They are returned in a tuple of the same order. Possible quantities are 'i' : first atom index 'j' : second atom index 'd' : absolute distance 'D' : distance vector 'S' : shift vector (number of cell boundaries crossed by the bond between atom i and j) a : ase.Atoms Atomic configuration. cutoff : float Cutoff for neighbour search. Returns ------- i, j, ... : array Tuple with arrays for each quantity specified above. """ return _matscipy.neighbour_list(quantities, a.cell, np.linalg.inv(a.cell.T), a.pbc, a.positions, cutoff)
def neighbour_list(quantities, a, cutoff): """ Compute a neighbor list for an atomic configuration. Atoms outside periodic boundaries are mapped into the box. Atoms outside nonperiodic boundaries are included in the neighbor list but complexity of neighbor list search for those can become n^2. The neighbor list is sorted by first atom index 'i', but not by second atom index 'j'. Parameters ---------- quantities : str Quantities to compute by the neighbor list algorithm. Each character in this string defines a quantity. They are returned in a tuple of the same order. Possible quantities are 'i' : first atom index 'j' : second atom index 'd' : absolute distance 'D' : distance vector 'S' : shift vector (number of cell boundaries crossed by the bond between atom i and j). With the shift vector S, the distances D between atoms can be computed from: D = a.positions[j]-a.positions[i]+S.dot(a.cell) a : ase.Atoms Atomic configuration. cutoff : float or dict Cutoff for neighbor search. It can be - A single float: This is a global cutoff for all elements. - A dictionary: This specifies cutoff values for element pairs. Specification accepts element numbers of symbols. Example: {(1, 6): 1.1, (1, 1): 1.0, ('C', 'C'): 1.85} - A list/array with a per atom value: This specifies the radius of an atomic sphere for each atoms. If spheres overlap, atoms are within each others neighborhood. Returns ------- i, j, ... : array Tuple with arrays for each quantity specified above. Indices in `i` are returned in ascending order 0..len(a), but the order of (i,j) pairs is not guaranteed. Examples -------- Examples assume Atoms object *a* and numpy imported as *np*. 1. Coordination counting: i = neighbor_list('i', a, 1.85) coord = np.bincount(i) 2. Coordination counting with different cutoffs for each pair of species i = neighbor_list('i', a, {('H', 'H'): 1.1, ('C', 'H'): 1.3, ('C', 'C'): 1.85}) coord = np.bincount(i) 3. Pair distribution function: d = neighbor_list('d', a, 10.00) h, bin_edges = np.histogram(d, bins=100) pdf = h/(4*np.pi/3*(bin_edges[1:]**3 - bin_edges[:-1]**3)) * a.get_volume()/len(a) 4. Pair potential: i, j, d, D = neighbor_list('ijdD', a, 5.0) energy = (-C/d**6).sum() pair_forces = (6*C/d**5 * (D/d).T).T forces_x = np.bincount(j, weights=pair_forces[:, 0], minlength=len(a)) - \ np.bincount(i, weights=pair_forces[:, 0], minlength=len(a)) forces_y = np.bincount(j, weights=pair_forces[:, 1], minlength=len(a)) - \ np.bincount(i, weights=pair_forces[:, 1], minlength=len(a)) forces_z = np.bincount(j, weights=pair_forces[:, 2], minlength=len(a)) - \ np.bincount(i, weights=pair_forces[:, 2], minlength=len(a)) 5. Dynamical matrix for a pair potential stored in a block sparse format: from scipy.sparse import bsr_matrix i, j, dr, abs_dr = neighbor_list('ijDd', atoms) energy = (dr.T / abs_dr).T dynmat = -(dde * (energy.reshape(-1, 3, 1) * energy.reshape(-1, 1, 3)).T).T \ -(de / abs_dr * (np.eye(3, dtype=energy.dtype) - \ (energy.reshape(-1, 3, 1) * energy.reshape(-1, 1, 3))).T).T dynmat_bsr = bsr_matrix((dynmat, j, first_i), shape=(3*len(a), 3*len(a))) dynmat_diag = np.empty((len(a), 3, 3)) for x in range(3): for y in range(3): dynmat_diag[:, x, y] = -np.bincount(i, weights=dynmat[:, x, y]) dynmat_bsr += bsr_matrix((dynmat_diag, np.arange(len(a)), np.arange(len(a) + 1)), shape=(3 * len(a), 3 * len(a))) i_n, j_n, dr_nc, abs_dr_n = neighbour_list('ijDd', atoms, dict) e_nc = (dr_nc.T/abs_dr_n).T D_ncc = -(dde_n * (e_nc.reshape(-1,3,1) * e_nc.reshape(-1,1,3)).T).T D_ncc += -(de_n/abs_dr_n * (np.eye(3, dtype=e_nc.dtype) - (e_nc.reshape(-1,3,1) * e_nc.reshape(-1,1,3))).T).T D = bsr_matrix((D_ncc, j_n, first_i), shape=(3*nat,3*nat)) Ddiag_icc = np.empty((nat,3,3)) for x in range(3): for y in range(3): Ddiag_icc[:,x,y] = -np.bincount(i_n, weights = D_ncc[:,x,y]) D += bsr_matrix((Ddiag_icc,np.arange(nat),np.arange(nat+1)), shape=(3*nat,3*nat)) return D """ if isinstance(cutoff, dict): maxel = np.max(a.numbers) _cutoff = np.zeros([maxel+1, maxel+1], dtype=float) for (el1, el2), c in cutoff.items(): try: el1 = atomic_numbers[el1] except: pass try: el2 = atomic_numbers[el2] except: pass if el1 < maxel+1 and el2 < maxel+1: _cutoff[el1, el2] = c _cutoff[el2, el1] = c else: _cutoff = cutoff return _matscipy.neighbour_list(quantities, a.cell, np.linalg.inv(a.cell.T), a.pbc, a.positions, _cutoff, a.numbers.astype(np.int32))
def neighbour_list(quantities, a, cutoff): """ Compute a neighbour list for an atomic configuration. Parameters ---------- quantities : str Quantities to compute by the neighbor list algorithm. Each character in this string defines a quantity. They are returned in a tuple of the same order. Possible quantities are 'i' : first atom index 'j' : second atom index 'd' : absolute distance 'D' : distance vector 'S' : shift vector (number of cell boundaries crossed by the bond between atom i and j). With the shift vector S, the distances d between can be computed as: D = a.positions[j]-a.positions[i]+S.dot(a.cell) a : ase.Atoms Atomic configuration. cutoff : float or dict Cutoff for neighbour search. If single float is given, a global cutoff is used for all elements. A dictionary specifies cutoff for element pairs. Specification accepts element numbers of symbols. Example: {(1, 6): 1.1, (1, 1): 1.0, ('C', 'C'): 1.85} Returns ------- i, j, ... : array Tuple with arrays for each quantity specified above. Indices in `i` are returned in ascending order 0..len(a), but the order of (i,j) pairs is not guaranteed. Examples -------- Examples assume Atoms object *a* and numpy imported as *np*. 1. Coordination counting: i = neighbour_list('i', a, 1.85) coord = np.bincount(i) 2. Coordination counting with different cutoffs for each pair of species i = neighbour_list('i', a, {('H', 'H'): 1.1, ('C', 'H'): 1.3, ('C', 'C'): 1.85}) coord = np.bincount(i) 3. Pair distribution function: d = neighbour_list('d', a, 10.00) h, bin_edges = np.histogram(d, bins=100) pdf = h/(4*np.pi/3*(bin_edges[1:]**3 - bin_edges[:-1]**3)) * a.get_volume()/len(a) 4. Pair potential: i, j, d, D = neighbour_list('ijdD', a, 5.0) energy = (-C/d**6).sum() pair_forces = (6*C/d**5 * (D/d).T).T forces_x = np.bincount(j, weights=pair_forces[:, 0], minlength=len(a)) - \ np.bincount(i, weights=pair_forces[:, 0], minlength=len(a)) forces_y = np.bincount(j, weights=pair_forces[:, 1], minlength=len(a)) - \ np.bincount(i, weights=pair_forces[:, 1], minlength=len(a)) forces_z = np.bincount(j, weights=pair_forces[:, 2], minlength=len(a)) - \ np.bincount(i, weights=pair_forces[:, 2], minlength=len(a)) """ if isinstance(cutoff, dict): maxel = np.max(a.numbers) _cutoff = np.zeros([maxel+1, maxel+1], dtype=float) for (el1, el2), c in cutoff.items(): try: el1 = atomic_numbers[el1] except: pass try: el2 = atomic_numbers[el2] except: pass if el1 < maxel+1 and el2 < maxel+1: _cutoff[el1, el2] = c _cutoff[el2, el1] = c else: _cutoff = cutoff return _matscipy.neighbour_list(quantities, a.cell, np.linalg.inv(a.cell.T), a.pbc, a.positions, _cutoff, a.numbers.astype(np.int32))
def neighbour_list(quantities, atoms=None, cutoff=None, positions=None, cell=None, pbc=None, numbers=None, cell_origin=None): """ Compute a neighbor list for an atomic configuration. Atoms outside periodic boundaries are mapped into the box. Atoms outside nonperiodic boundaries are included in the neighbor list but the complexity of neighbor list search for those can become n^2. The neighbor list is sorted by first atom index 'i', but not by second atom index 'j'. The neighbour list accepts either an ASE Atoms object or positions and cell vectors individually. Parameters ---------- quantities : str Quantities to compute by the neighbor list algorithm. Each character in this string defines a quantity. They are returned in a tuple of the same order. Possible quantities are 'i' : first atom index 'j' : second atom index 'd' : absolute distance 'D' : distance vector 'S' : shift vector (number of cell boundaries crossed by the bond between atom i and j). With the shift vector S, the distances D between atoms can be computed from: D = a.positions[j]-a.positions[i]+S.dot(a.cell) atoms : ase.Atoms Atomic configuration. (Default: None) cutoff : float or dict Cutoff for neighbor search. It can be - A single float: This is a global cutoff for all elements. - A dictionary: This specifies cutoff values for element pairs. Specification accepts element numbers of symbols. Example: {(1, 6): 1.1, (1, 1): 1.0, ('C', 'C'): 1.85} - A list/array with a per atom value: This specifies the radius of an atomic sphere for each atoms. If spheres overlap, atoms are within each others neighborhood. positions : array_like Atomic positions. (Default: None) cell : array_like Cell vectors as a 3x3 matrix. (Default: Shrink wrapped cell) pbc : array_like 3-vector containing periodic boundary conditions in all three directions. (Default: Nonperiodic box) numbers : array_like Array containing the atomic numbers. Returns ------- i, j, ... : array Tuple with arrays for each quantity specified above. Indices in `i` are returned in ascending order 0..len(a), but the order of (i,j) pairs is not guaranteed. Examples -------- Examples assume Atoms object *a* and numpy imported as *np*. 1. Coordination counting: i = neighbor_list('i', a, 1.85) coord = np.bincount(i) 2. Coordination counting with different cutoffs for each pair of species i = neighbor_list('i', a, {('H', 'H'): 1.1, ('C', 'H'): 1.3, ('C', 'C'): 1.85}) coord = np.bincount(i) 3. Pair distribution function: d = neighbor_list('d', a, 10.00) h, bin_edges = np.histogram(d, bins=100) pdf = h/(4*np.pi/3*(bin_edges[1:]**3 - bin_edges[:-1]**3)) * a.get_volume()/len(a) 4. Pair potential: i, j, d, D = neighbor_list('ijdD', a, 5.0) energy = (-C/d**6).sum() pair_forces = (6*C/d**5 * (D/d).T).T forces_x = np.bincount(j, weights=pair_forces[:, 0], minlength=len(a)) - \ np.bincount(i, weights=pair_forces[:, 0], minlength=len(a)) forces_y = np.bincount(j, weights=pair_forces[:, 1], minlength=len(a)) - \ np.bincount(i, weights=pair_forces[:, 1], minlength=len(a)) forces_z = np.bincount(j, weights=pair_forces[:, 2], minlength=len(a)) - \ np.bincount(i, weights=pair_forces[:, 2], minlength=len(a)) 5. Dynamical matrix for a pair potential stored in a block sparse format: from scipy.sparse import bsr_matrix i, j, dr, abs_dr = neighbor_list('ijDd', atoms) energy = (dr.T / abs_dr).T dynmat = -(dde * (energy.reshape(-1, 3, 1) * energy.reshape(-1, 1, 3)).T).T \ -(de / abs_dr * (np.eye(3, dtype=energy.dtype) - \ (energy.reshape(-1, 3, 1) * energy.reshape(-1, 1, 3))).T).T dynmat_bsr = bsr_matrix((dynmat, j, first_i), shape=(3*len(a), 3*len(a))) dynmat_diag = np.empty((len(a), 3, 3)) for x in range(3): for y in range(3): dynmat_diag[:, x, y] = -np.bincount(i, weights=dynmat[:, x, y]) dynmat_bsr += bsr_matrix((dynmat_diag, np.arange(len(a)), np.arange(len(a) + 1)), shape=(3 * len(a), 3 * len(a))) i_n, j_n, dr_nc, abs_dr_n = neighbour_list('ijDd', atoms, dict) e_nc = (dr_nc.T/abs_dr_n).T D_ncc = -(dde_n * (e_nc.reshape(-1,3,1) * e_nc.reshape(-1,1,3)).T).T D_ncc += -(de_n/abs_dr_n * (np.eye(3, dtype=e_nc.dtype) - (e_nc.reshape(-1,3,1) * e_nc.reshape(-1,1,3))).T).T D = bsr_matrix((D_ncc, j_n, first_i), shape=(3*nat,3*nat)) Ddiag_icc = np.empty((nat,3,3)) for x in range(3): for y in range(3): Ddiag_icc[:,x,y] = -np.bincount(i_n, weights = D_ncc[:,x,y]) D += bsr_matrix((Ddiag_icc,np.arange(nat),np.arange(nat+1)), shape=(3*nat,3*nat)) return D """ if cutoff is None: raise ValueError('Please provide a value for the cutoff radius.') if atoms is None: if positions is None: raise ValueError('You provided neither an ASE Atoms object nor ' 'a positions array.') if cell is None: # Shrink wrapped cell rmin = np.min(positions, axis=0) rmax = np.max(positions, axis=0) cell_origin = rmin cell = np.diag(rmax - rmin) if cell_origin is None: cell_origin = np.zeros(3) if pbc is None: pbc = np.zeros(3, dtype=bool) if numbers is None: numbers = np.ones(len(positions), dtype=np.int32) else: if positions is not None: raise ValueError( 'You cannot provide an ASE Atoms object and ' 'individual position atomic positions at the same ' 'time.') positions = atoms.positions if cell_origin is not None: raise ValueError('You cannot provide an ASE Atoms object and ' 'a cell origin at the same time.') cell_origin = np.zeros(3) if cell is not None: raise ValueError('You cannot provide an ASE Atoms object and ' 'cell vectors at the same time.') cell = atoms.cell if pbc is not None: raise ValueError('You cannot provide an ASE Atoms object and ' 'separate periodicity information at the same ' 'time.') pbc = atoms.pbc if numbers is not None: raise ValueError('You cannot provide an ASE Atoms object and ' 'separate atomic numbers at the same time.') numbers = atoms.numbers.astype(np.int32) if isinstance(cutoff, dict): maxel = np.max(numbers) _cutoff = np.zeros([maxel + 1, maxel + 1], dtype=float) for (el1, el2), c in cutoff.items(): try: el1 = atomic_numbers[el1] except: pass try: el2 = atomic_numbers[el2] except: pass if el1 < maxel + 1 and el2 < maxel + 1: _cutoff[el1, el2] = c _cutoff[el2, el1] = c else: _cutoff = cutoff return _matscipy.neighbour_list(quantities, cell_origin, cell, np.linalg.inv(cell.T), pbc, positions, _cutoff, numbers)
def neighbour_list(quantities, a, cutoff): """ Compute a neighbor list for an atomic configuration. Atoms outside periodic boundaries are mapped into the box. Atoms outside nonperiodic boundaries are included in the neighbor list but complexity of neighbor list search for those can become n^2. The neighbor list is sorted by first atom index 'i', but not by second atom index 'j'. Parameters ---------- quantities : str Quantities to compute by the neighbor list algorithm. Each character in this string defines a quantity. They are returned in a tuple of the same order. Possible quantities are 'i' : first atom index 'j' : second atom index 'd' : absolute distance 'D' : distance vector 'S' : shift vector (number of cell boundaries crossed by the bond between atom i and j). With the shift vector S, the distances D between atoms can be computed from: D = a.positions[j]-a.positions[i]+S.dot(a.cell) a : ase.Atoms Atomic configuration. cutoff : float or dict Cutoff for neighbor search. It can be - A single float: This is a global cutoff for all elements. - A dictionary: This specifies cutoff values for element pairs. Specification accepts element numbers of symbols. Example: {(1, 6): 1.1, (1, 1): 1.0, ('C', 'C'): 1.85} - A list/array with a per atom value: This specifies the radius of an atomic sphere for each atoms. If spheres overlap, atoms are within each others neighborhood. Returns ------- i, j, ... : array Tuple with arrays for each quantity specified above. Indices in `i` are returned in ascending order 0..len(a), but the order of (i,j) pairs is not guaranteed. Examples -------- Examples assume Atoms object *a* and numpy imported as *np*. 1. Coordination counting: i = neighbor_list('i', a, 1.85) coord = np.bincount(i) 2. Coordination counting with different cutoffs for each pair of species i = neighbor_list('i', a, {('H', 'H'): 1.1, ('C', 'H'): 1.3, ('C', 'C'): 1.85}) coord = np.bincount(i) 3. Pair distribution function: d = neighbor_list('d', a, 10.00) h, bin_edges = np.histogram(d, bins=100) pdf = h/(4*np.pi/3*(bin_edges[1:]**3 - bin_edges[:-1]**3)) * a.get_volume()/len(a) 4. Pair potential: i, j, d, D = neighbor_list('ijdD', a, 5.0) energy = (-C/d**6).sum() pair_forces = (6*C/d**5 * (D/d).T).T forces_x = np.bincount(j, weights=pair_forces[:, 0], minlength=len(a)) - \ np.bincount(i, weights=pair_forces[:, 0], minlength=len(a)) forces_y = np.bincount(j, weights=pair_forces[:, 1], minlength=len(a)) - \ np.bincount(i, weights=pair_forces[:, 1], minlength=len(a)) forces_z = np.bincount(j, weights=pair_forces[:, 2], minlength=len(a)) - \ np.bincount(i, weights=pair_forces[:, 2], minlength=len(a)) 5. Dynamical matrix for a pair potential stored in a block sparse format: from scipy.sparse import bsr_matrix i, j, dr, abs_dr = neighbor_list('ijDd', atoms) energy = (dr.T / abs_dr).T dynmat = -(dde * (energy.reshape(-1, 3, 1) * energy.reshape(-1, 1, 3)).T).T \ -(de / abs_dr * (np.eye(3, dtype=energy.dtype) - \ (energy.reshape(-1, 3, 1) * energy.reshape(-1, 1, 3))).T).T dynmat_bsr = bsr_matrix((dynmat, j, first_i), shape=(3*len(a), 3*len(a))) dynmat_diag = np.empty((len(a), 3, 3)) for x in range(3): for y in range(3): dynmat_diag[:, x, y] = -np.bincount(i, weights=dynmat[:, x, y]) dynmat_bsr += bsr_matrix((dynmat_diag, np.arange(len(a)), np.arange(len(a) + 1)), shape=(3 * len(a), 3 * len(a))) """ if isinstance(cutoff, dict): maxel = np.max(a.numbers) _cutoff = np.zeros([maxel+1, maxel+1], dtype=float) for (el1, el2), c in cutoff.items(): try: el1 = atomic_numbers[el1] except: pass try: el2 = atomic_numbers[el2] except: pass if el1 < maxel+1 and el2 < maxel+1: _cutoff[el1, el2] = c _cutoff[el2, el1] = c else: _cutoff = cutoff return _matscipy.neighbour_list(quantities, a.cell, np.linalg.inv(a.cell.T), a.pbc, a.positions, _cutoff, a.numbers.astype(np.int32))
def neigh_list_based_target_function(x, r=1.0, constraints=None, Dij=None): """Target function. Penalize dense packing for coordinates ||xi-xj||<ri+rj. Parameters ---------- x: (N,dim) ndarray particle coordinates r: float or (N,) ndarray, optional (default=1.0) steric radii of particles constraints: callable, returns (float, (N,dim) ndarray) constraint function value and gradien Dij: (N, N) ndarray, optional (default=None) pairwise minimum allowed distance matrix, overrides r Returns ------- (float, (N,dim) ndarray): target function value and gradient f: float, target (or penalty) function, value evaluates to sum_i sum_{j!=i} max(0,(r_i+r_j)^2-||xi-xj||^2)^2 without double-counting pairs, where r_i and r_j are the steric radii of coordinate points i and j df: (N,dim) ndarray of float, the gradient, evaluates to 4*si sj ((r_i'+r_j)^2-||xi-xj||^2)^2)*(xik'-xjk')*(kdi'j-kdi'i) for entry (i',k'), where i subscribes the coordinate point and k the spatial dimension. kd is Kronecker delta, si is sum over i """ logger = logging.getLogger(__name__) # # function: # # f(x) = sum_i sum_{j!=i} max(0,(r_i+r_j)^2-||xi-xj||^2)^2 # # gradient: # # dfdxi'k'=4*si sj ((r_i'+r_j)^2-||xi-xj||^2)^2)*(xik'-xjk')*(kdi'j-kdi'i) # # for all pairs i'j within ri'+rj cutoff # where k is the spatial direction x, y or z. assert x.ndim == 2, "2d array expected for x" n = x.shape[0] if not Dij: if not isinstance(r, np.ndarray) or r.shape != (n, ): r = r * np.ones(n) assert r.shape == (n, ) # compute minimum allowed pairwise distances Dij (NxN matrix) # r(Nx1) kron ones(1xN) = Ri(NxN) Ri = np.kron(r, np.ones((n, 1))) Rj = Ri.T Dij = Ri + Rj # TODO: allow for periodic boundaries, for now use shrink wrapped box box = np.array([x.min(axis=0), x.max(axis=0)]) cell_origin = box[0, :] cell = np.diag(box[1, :] - box[0, :]) # get all pairs within their allowed minimum distance # parameters are # _matscipy.neighbour_list(quantities, cell_origin, cell, # np.linalg.inv(cell.T), pbc, positions, # cutoff, numbers) # If thhe parameter 'cutoff' is a per-atom value, then it must be a # diamater, not a radius (as wrongly stated within the function's # docstring) i, j, dxijnorm, dxijvec = _matscipy.neighbour_list( 'ijdD', cell_origin, cell, np.linalg.inv(cell.T), [0, 0, 0], x, 2.0 * Ri, np.ones(len(x), dtype=np.int32)) # i, j are coordinate point indices, dxijnorm is pairwise distance, # dxvijvec is distance vector # nl contains redundnancies, i.e. ij AND ji # pairs = list(zip(i,j)) logger.debug( "Number of pairs within minimum allowed distance: {:d}".format(len(i))) # get minimum allowed pairwise distance for all pairs within this distance dij = Dij[i, j] # (r_i'+r_j)^2 dsq = np.square(dij) # ||xi-xj||^2 dxnormsq = np.square(dxijnorm) # (r_i+r_j)^2-||xi-xj||^2 sqdiff = dsq - dxnormsq # ((r_i+r_j)^2-||xi-xj||^2)^2 penaltysq = np.square(sqdiff) # correct for double counting due to redundancies f = 0.5 * np.sum(penaltysq) # 4*si sj ((r_i'+r_j)^2-||xi-xj||^2)^2)*(xik'-xjk')*(kdi'j-kdi'i) # (N x 1) column vector (a1 ... aN) * (N x dim) matrix ((d11,)) # Other than the formula above implicates, _matscipy.neighbour_list # returns the distance vector dxijvec = xj-xi (pointing from i to j), # thus positive sign in gradient below: gradij = 4 * np.atleast_2d( sqdiff).T * dxijvec # let's hope for proper broadcasting grad = np.zeros(x.shape) grad[i] += gradij # Since neighbour list always includes ij and ji, we only have to treat i, # not j. The following line is obsolete (otherwise we would introduce # double-counting again): # grad[j] -= gradij if constraints: logger.debug("Unconstrained penalty: {:10.5e}.".format(f)) logger.debug( DeferredMessage("Unconstrained gradient norm: {:10.5e}.", np.linalg.norm, grad)) g, g_grad = constraints(x) f += g grad += g_grad grad_1d = grad.reshape(np.product(grad.shape)) logger.debug("Total penalty: {:10.5e}.".format(f)) logger.debug( DeferredMessage("Gradient norm: {:10.5e}.", np.linalg.norm, grad_1d)) return f, grad_1d
def neighbour_list(quantities, a, cutoff, *args): """ Compute a neighbour list for an atomic configuration. Parameters ---------- quantities : str Quantities to compute by the neighbor list algorithm. Each character in this string defines a quantity. They are returned in a tuple of the same order. Possible quantities are 'i' : first atom index 'j' : second atom index 'd' : absolute distance 'D' : distance vector 'S' : shift vector (number of cell boundaries crossed by the bond between atom i and j). With the shift vector S, the distances d between can be computed as: D = a.positions[j]-a.positions[i]+S.dot(a.cell) a : ase.Atoms Atomic configuration. cutoff : float or array_like Cutoff for neighbour search. If square array is given, then different cutoffs will be used for individual bonds. Square array contains pair-wise cutoffs for a given species, given by the *numbers* parameter. numbers : array_like, optional Atomic numbers or similar identifiers for elements. Used for cutoff lookup. Returns ------- i, j, ... : array Tuple with arrays for each quantity specified above. Indices in `i` are returned in ascending order 0..len(a), but the order of (i,j) pairs is not guaranteed. Examples -------- Examples assume Atoms object *a* and numpy imported as *np*. 1. Coordination counting: i = neighbour_list('i', a, 1.85) coord = np.bincount(i) 2. Coordination counting with different cutoffs for each pair of species (Assumes that species are Carbon=6 and Hydrogen=1) i = neighbour_list('i', a, [[1.1, 1.3], [1.3, 1.85]], (a.numbers-1)//5) coord = np.bincount(i) 3. Pair distribution function: d = neighbour_list('d', a, 10.00) h, bin_edges = np.histogram(d, bins=100) pdf = h/(4*np.pi/3*(bin_edges[1:]**3 - bin_edges[:-1]**3)) * a.get_volume()/len(a) 4. Pair potential: i, j, d, D = neighbour_list('ijdD', a, 5.0) energy = (-C/d**6).sum() pair_forces = (6*C/d**5 * (D/d).T).T forces_x = np.bincount(j, weights=pair_forces[:, 0], minlength=len(a)) - \ np.bincount(i, weights=pair_forces[:, 0], minlength=len(a)) forces_y = np.bincount(j, weights=pair_forces[:, 1], minlength=len(a)) - \ np.bincount(i, weights=pair_forces[:, 1], minlength=len(a)) forces_z = np.bincount(j, weights=pair_forces[:, 2], minlength=len(a)) - \ np.bincount(i, weights=pair_forces[:, 2], minlength=len(a)) """ return _matscipy.neighbour_list(quantities, a.cell, np.linalg.inv(a.cell.T), a.pbc, a.positions, cutoff, *args)