def _calc_recip(self, system): """ Perform the reciprocal space summation. Uses the fastest non mesh-based method described as given by equation (16) in https://doi.org/10.1016/0010-4655(96)00016-1 The term G=0 is neglected, even if the system has nonzero charge. Physically this would mean that we are adding a constant background charge to make the cell charge neutral. Args: system (:class:`ase.Atoms` | :class:`.System`): Input system. Returns: np.ndarray(): A 2D matrix containing the real space terms for each i,j pair. """ n_atoms = self.n_atoms erecip = np.zeros((n_atoms, n_atoms), dtype=np.float) coords = system.get_positions() # Get the reciprocal lattice points within the reciprocal space cutoff rcp_latt = 2*np.pi*system.get_reciprocal_cell() rcp_latt = Lattice(rcp_latt) recip_nn = rcp_latt.get_points_in_sphere([[0, 0, 0]], [0, 0, 0], self.gcut) # Ignore the terms with G=0. frac_coords = [fcoords for (fcoords, dist, i) in recip_nn if dist != 0] gs = rcp_latt.get_cartesian_coords(frac_coords) g2s = np.sum(gs ** 2, 1) expvals = np.exp(-g2s / (4 * self.a_squared)) grs = np.sum(gs[:, None] * coords[None, :], 2) factors = np.divide(expvals, g2s) charges = self.q # Create array where q_2[i,j] is qi * qj qiqj = charges[None, :] * charges[:, None] for gr, factor in zip(grs, factors): # Uses the identity sin(x)+cos(x) = 2**0.5 sin(x + pi/4) m = (gr[None, :] + math.pi / 4) - gr[:, None] np.sin(m, m) m *= factor erecip += m erecip *= 4 * math.pi / self.volume * qiqj * 2 ** 0.5 # The diagonal terms are divided by two diag = np.diag(erecip)/2 np.fill_diagonal(erecip, diag) return erecip
def _calc_real(self, system): """Used to calculate the Ewald real-space sum. Corresponds to equation (5) in https://doi.org/10.1016/0010-4655(96)00016-1 Args: system (:class:`ase.Atoms` | :class:`.System`): Input system. Returns: np.ndarray(): A 2D matrix containing the real space terms for each i,j pair. """ fcoords = system.get_scaled_positions() coords = system.get_positions() n_atoms = len(system) ereal = np.zeros((n_atoms, n_atoms), dtype=np.float) lattice = Lattice(system.get_cell()) # For each atom in the original cell, get the neighbours in the # infinite system within the real space cutoff and calculate the real # space portion of the Ewald sum. for i in range(n_atoms): # Get points that are within the real space cutoff nfcoords, rij, js = lattice.get_points_in_sphere( fcoords, coords[i], self.rcut, zip_results=False ) # Remove the rii term, because a charge does not interact with # itself (but does interact with copies of itself). mask = rij > 1e-8 js = js[mask] rij = rij[mask] nfcoords = nfcoords[mask] qi = self.q[i] qj = self.q[js] erfcval = erfc(self.a * rij) new_ereals = erfcval * qi * qj / rij # Insert new_ereals for k in range(n_atoms): ereal[k, i] = np.sum(new_ereals[js == k]) # The diagonal terms are divided by two diag = np.diag(ereal)/2 np.fill_diagonal(ereal, diag) return ereal