"%iH" % NAT, positions = np.random.random([NAT,3])*SX, charges = (2*np.random.random([NAT])-1)*CHARGE, cell = [ SX, SY, SZ ] ) ] # Compute moments M = [ ] for i in range(8): M += [ get_moments(a[i].get_positions(), a[i].get_initial_charges(), L_MAX, r0) ] # Construct a composite atoms object # and compute the corresponding multipole # expansion b = ase.Atoms(cell=[ 2*SX, 2*SY, 2*SZ ]) Mc0_l, Mc_L = zero_moments(L_MAX) for i, ( ca, ( M0_l, M_L ) ) in enumerate(zip(a, M)): x = i % 2 y = (i/2) % 2 z = (i/4) % 2 dr = np.array([x*SX, y*SY, z*SZ]) ca.translate(dr) b += ca dr = np.array([(2*x-1)*SX/2, (2*y-1)*SY/2, (2*z-1)*SZ/2]) multipole_to_multipole(dr, L_MAX, M0_l, M_L, Mc0_l, Mc_L) # Compute the multipole moment directly
cell=[SX, SY, SZ]) ] # Compute moments M = [] for i in range(8): M += [ get_moments(a[i].get_positions(), a[i].get_initial_charges(), L_MAX, r0) ] # Construct a composite atoms object # and compute the corresponding multipole # expansion b = ase.Atoms(cell=[2 * SX, 2 * SY, 2 * SZ]) Mc0_l, Mc_L = zero_moments(L_MAX) for i, (ca, (M0_l, M_L)) in enumerate(zip(a, M)): x = i % 2 y = (i / 2) % 2 z = (i / 4) % 2 dr = np.array([x * SX, y * SY, z * SZ]) ca.translate(dr) b += ca dr = np.array([(2 * x - 1) * SX / 2, (2 * y - 1) * SY / 2, (2 * z - 1) * SZ / 2]) multipole_to_multipole(dr, L_MAX, M0_l, M_L, Mc0_l, Mc_L)
def _update(self, a, q): """ Compute multipoles, do the transformations, and compute the electrostatic potential and field on each atom in a. Parameters: ----------- a: Hotbit Atoms object, or atoms object that implements the transform and rotation interface. q: Charges """ self.timer.start("multipole_to_multipole") self.r_av = a.get_positions().copy() self.q_a = q.copy() nat = len(a) r = a.get_positions() if self.r0_v is None: r0_v = np.sum(r, axis=0) / len(a) else: r0_v = self.r0_v T0_l, T_L = get_moments(r, q, self.l_max, r0_v) self.M = [(T0_l.copy(), T_L.copy())] self.r0 = [r0_v] sym_ranges = a.get_symmetry_operation_ranges() for (s1, s2), k in zip(sym_ranges, self.k): if s2 != np.Inf and k != 1: print sym_ranges print self.k raise ValueError("For non-periodic symmetries the k-value must " "be 1.") n1, n2, n3 = n_from_ranges(sym_ranges, self.n1, self.n2) # Compute telescoped multipoles level = np.ones(3, dtype=int) for k in range(np.max(self.k) - 2): M0_l = T0_l M_L = T_L T0_l = np.zeros_like(M0_l) T_L = np.zeros_like(M_L) if k >= self.k[0] - 2: _n1 = [0, 1] else: _n1 = n1 if k >= self.k[1] - 2: _n2 = [0, 1] else: _n2 = n2 if k >= self.k[2] - 2: _n3 = [0, 1] else: _n3 = n3 r0_v = np.zeros(3, dtype=float) n = 0 # Determine center of gravity for x1 in range(*_n1): for x2 in range(*_n2): for x3 in range(*_n3): x = np.array([x1, x2, x3]) r0_v += a.transform(self.r0[k], x * level) n += 1 r0_v /= n self.r0 += [r0_v] # self.r0 += [ self.r0[0] ] # Transform multipoles for x1 in range(*_n1): for x2 in range(*_n2): for x3 in range(*_n3): # Loop over all symmetry operations and compute # telescoped multipoles # FIXME!!! Currently only supports continuous # symmetries, think about discrete/recurrent ones. x = np.array([x1, x2, x3]) # + self.dx # The origin is already okay, skip it # if np.any(np.abs(x) > self._TOL): r1 = a.transform(self.r0[k], x * level) T = a.rotation(x * level) S0_l, S_L = transform_multipole(T, self.l_max, M0_l, M_L) multipole_to_multipole(r1 - self.r0[k], self.l_max, S0_l, S_L, T0_l, T_L) self.M += [(T0_l.copy(), T_L.copy())] level *= self.n2 - self.n1 + 1 self.timer.stop("multipole_to_multipole") ### self.timer.start("multipole_to_local") # Compute the local expansion from telescoped multipoles L0_l, L_L = zero_moments(self.l_max) m1, m2, m3 = n_from_ranges(sym_ranges, self.m1, self.m2) Mi = len(self.M) - 1 for k in range(np.max(self.k) - 1): M0_l, M_L = self.M[Mi] if k >= self.k[0] - 1: _m1 = [0, 1] else: _m1 = m1 if k >= self.k[1] - 1: _m2 = [0, 1] else: _m2 = m2 if k >= self.k[2] - 1: _m3 = [0, 1] else: _m3 = m3 for x1 in range(*_m1): for x2 in range(*_m2): for x3 in range(*_m3): # Loop over all symmetry operations and compute the # local expansion from the telescoped multipoles x = np.array([x1, x2, x3]) # + self.dx # No local expansion in the inner region if np.any(x < self.n1) or np.any(x > self.n2): r1 = a.transform(self.r0[Mi], x * level) T = a.rotation(x * level) S0_l, S_L = transform_multipole(T, self.l_max, M0_l, M_L) multipole_to_local(-r1 + self.r0[Mi], self.l_max, S0_l, S_L, L0_l, L_L) level /= self.n2 - self.n1 + 1 Mi -= 1 self.L = (L0_l, L_L) self.timer.stop("multipole_to_local") ### self.phi_a = np.zeros(nat, dtype=float) self.E_av = np.zeros([nat, 3], dtype=float) ### self.timer.start("local_to_local") for i in a: loc0_l, loc_L = local_to_local(i.position - self.r0[0], self.l_max, L0_l, L_L, 1) self.phi_a[i.index] = loc0_l[0] self.E_av[i.index, :] = [-loc_L[0].real, -loc_L[0].imag, loc0_l[1]] self.timer.stop("local_to_local") ### self.timer.start("near_field") # Contribution of neighboring boxes for x1 in range(*n1): for x2 in range(*n2): for x3 in range(*n3): # self-interaction needs to be treated separately if x1 != 0 or x2 != 0 or x3 != 0: x = np.array([x1, x2, x3]) # construct a matrix with distances r1 = a.transform(self.r0[0], x) T = a.rotation(x) rT = np.dot(r - self.r0[0], np.transpose(T)) dr = r.reshape(nat, 1, 3) - (r1 + rT).reshape(1, nat, 3) abs_dr = np.sqrt(np.sum(dr * dr, axis=2)) phi = q / abs_dr E = q.reshape(1, nat, 1) * dr / (abs_dr ** 3).reshape(nat, nat, 1) self.phi_a += np.sum(phi, axis=1) self.E_av += np.sum(E, axis=1) # Self-contribution dr = r.reshape(nat, 1, 3) - r.reshape(1, nat, 3) abs_dr = np.sqrt(np.sum(dr * dr, axis=2)) # Avoid divide by zero abs_dr[diag_indices_from(abs_dr)] = 1.0 phi = q / abs_dr E = q.reshape(1, nat, 1) * dr / (abs_dr ** 3).reshape(nat, nat, 1) phi[diag_indices_from(phi)] = 0.0 E[diag_indices_from(phi)] = 0.0 self.phi_a += np.sum(phi, axis=1) self.E_av += np.sum(E, axis=1) # Dipole correction for 3D sum s1, s2, s3 = sym_ranges if s1[1] == np.Inf and s2[1] == np.Inf and s3[1] == np.Inf: Ml0, Mlm = self.M[0] dip = np.array([-2 * Mlm[0].real, 2 * Mlm[0].imag, Ml0[1]]) dip *= 4 * pi / (3 * a.get_volume()) self.phi_a -= np.dot(r - self.r0[0], dip) self.E_av += dip self.timer.stop("near_field")
def _update(self, a, q): """ Compute multipoles, do the transformations, and compute the electrostatic potential and field on each atom in a. Parameters: ----------- a: Hotbit Atoms object, or atoms object that implements the transform and rotation interface. q: Charges """ self.timer.start('multipole_to_multipole') self.r_av = a.get_positions().copy() self.q_a = q.copy() nat = len(a) r = a.get_positions() if self.r0_v is None: r0_v = np.sum(r, axis=0) / len(a) else: r0_v = self.r0_v T0_l, T_L = get_moments(r, q, self.l_max, r0_v) self.M = [(T0_l.copy(), T_L.copy())] self.r0 = [r0_v] sym_ranges = a.get_symmetry_operation_ranges() for (s1, s2), k in zip(sym_ranges, self.k): if s2 != np.Inf and k != 1: print(sym_ranges) print(self.k) raise ValueError( 'For non-periodic symmetries the k-value must ' 'be 1.') n1, n2, n3 = n_from_ranges(sym_ranges, self.n1, self.n2) # Compute telescoped multipoles level = np.ones(3, dtype=int) for k in range(np.max(self.k) - 2): M0_l = T0_l M_L = T_L T0_l = np.zeros_like(M0_l) T_L = np.zeros_like(M_L) if k >= self.k[0] - 2: _n1 = [0, 1] else: _n1 = n1 if k >= self.k[1] - 2: _n2 = [0, 1] else: _n2 = n2 if k >= self.k[2] - 2: _n3 = [0, 1] else: _n3 = n3 r0_v = np.zeros(3, dtype=float) n = 0 # Determine center of gravity for x1 in range(*_n1): for x2 in range(*_n2): for x3 in range(*_n3): x = np.array([x1, x2, x3]) r0_v += a.transform(self.r0[k], x * level) n += 1 r0_v /= n self.r0 += [r0_v] #self.r0 += [ self.r0[0] ] # Transform multipoles for x1 in range(*_n1): for x2 in range(*_n2): for x3 in range(*_n3): # Loop over all symmetry operations and compute # telescoped multipoles # FIXME!!! Currently only supports continuous # symmetries, think about discrete/recurrent ones. x = np.array([x1, x2, x3]) #+ self.dx # The origin is already okay, skip it #if np.any(np.abs(x) > self._TOL): r1 = a.transform(self.r0[k], x * level) T = a.rotation(x * level) S0_l, S_L = transform_multipole( T, self.l_max, M0_l, M_L) multipole_to_multipole(r1 - self.r0[k], self.l_max, S0_l, S_L, T0_l, T_L) self.M += [(T0_l.copy(), T_L.copy())] level *= self.n2 - self.n1 + 1 self.timer.stop('multipole_to_multipole') ### self.timer.start('multipole_to_local') # Compute the local expansion from telescoped multipoles L0_l, L_L = zero_moments(self.l_max) m1, m2, m3 = n_from_ranges(sym_ranges, self.m1, self.m2) Mi = len(self.M) - 1 for k in range(np.max(self.k) - 1): M0_l, M_L = self.M[Mi] if k >= self.k[0] - 1: _m1 = [0, 1] else: _m1 = m1 if k >= self.k[1] - 1: _m2 = [0, 1] else: _m2 = m2 if k >= self.k[2] - 1: _m3 = [0, 1] else: _m3 = m3 for x1 in range(*_m1): for x2 in range(*_m2): for x3 in range(*_m3): # Loop over all symmetry operations and compute the # local expansion from the telescoped multipoles x = np.array([x1, x2, x3]) #+ self.dx # No local expansion in the inner region if np.any(x < self.n1) or np.any(x > self.n2): r1 = a.transform(self.r0[Mi], x * level) T = a.rotation(x * level) S0_l, S_L = transform_multipole( T, self.l_max, M0_l, M_L) multipole_to_local(-r1 + self.r0[Mi], self.l_max, S0_l, S_L, L0_l, L_L) level //= self.n2 - self.n1 + 1 Mi -= 1 self.L = (L0_l, L_L) self.timer.stop('multipole_to_local') ### self.phi_a = np.zeros(nat, dtype=float) self.E_av = np.zeros([nat, 3], dtype=float) ### self.timer.start('local_to_local') for i in a: loc0_l, loc_L = local_to_local(i.position - self.r0[0], self.l_max, L0_l, L_L, 1) self.phi_a[i.index] = loc0_l[0] self.E_av[i.index, :] = [-loc_L[0].real, -loc_L[0].imag, loc0_l[1]] self.timer.stop('local_to_local') ### self.timer.start('near_field') # Contribution of neighboring boxes for x1 in range(*n1): for x2 in range(*n2): for x3 in range(*n3): # self-interaction needs to be treated separately if x1 != 0 or x2 != 0 or x3 != 0: x = np.array([x1, x2, x3]) # construct a matrix with distances r1 = a.transform(self.r0[0], x) T = a.rotation(x) rT = np.dot(r - self.r0[0], np.transpose(T)) dr = r.reshape(nat, 1, 3) - \ (r1+rT).reshape(1, nat, 3) abs_dr = np.sqrt(np.sum(dr * dr, axis=2)) phi = q / abs_dr E = q.reshape(1, nat, 1)*dr/ \ (abs_dr**3).reshape(nat, nat, 1) self.phi_a += np.sum(phi, axis=1) self.E_av += np.sum(E, axis=1) # Self-contribution dr = r.reshape(nat, 1, 3) - r.reshape(1, nat, 3) abs_dr = np.sqrt(np.sum(dr * dr, axis=2)) # Avoid divide by zero abs_dr[diag_indices_from(abs_dr)] = 1.0 phi = q / abs_dr E = q.reshape(1, nat, 1) * dr / (abs_dr**3).reshape(nat, nat, 1) phi[diag_indices_from(phi)] = 0.0 E[diag_indices_from(phi)] = 0.0 self.phi_a += np.sum(phi, axis=1) self.E_av += np.sum(E, axis=1) # Dipole correction for 3D sum s1, s2, s3 = sym_ranges if s1[1] == np.Inf and s2[1] == np.Inf and s3[1] == np.Inf: Ml0, Mlm = self.M[0] dip = np.array([-2 * Mlm[0].real, 2 * Mlm[0].imag, Ml0[1]]) dip *= 4 * pi / (3 * a.get_volume()) self.phi_a -= np.dot(r - self.r0[0], dip) self.E_av += dip self.timer.stop('near_field')