def test_get_points_in_sphere_pbc(self): latt = Lattice.cubic(1) pts = [] for a, b, c in itertools.product(xrange(10), xrange(10), xrange(10)): pts.append([a / 10, b / 10, c / 10]) self.assertEqual( len(get_points_in_sphere_pbc(latt, pts, [0, 0, 0], 0.1)), 7) self.assertEqual( len(get_points_in_sphere_pbc(latt, pts, [0.5, 0.5, 0.5], 0.5)), 515)
def test_get_points_in_sphere_pbc(self): latt = Lattice.cubic(1) pts = [] for a, b, c in itertools.product(xrange(10), xrange(10), xrange(10)): pts.append([a / 10, b / 10, c / 10]) self.assertEqual(len(get_points_in_sphere_pbc(latt, pts, [0, 0, 0], 0.1)), 7) self.assertEqual(len(get_points_in_sphere_pbc(latt, pts, [0.5, 0.5, 0.5], 0.5)), 515)
def find_mapping(self, other_lattice, ltol=1e-5, atol=1): """ Finds a mapping between current lattice and another lattice. There are an infinite number of choices of basis vectors for two entirely equivalent lattices. This method returns a mapping that maps other_lattice to this lattice. Args: other_lattice: Another lattice that is equivalent to this one. ltol: Tolerance for matching lengths. Defaults to 1e-5. atol: Tolerance for matching angles. Defaults to 1. Returns: (aligned_lattice, rotation_matrix, scale_matrix) if a mapping is found. aligned_lattice is a rotated version of other_lattice that has the same lattice parameters, but which is aligned in the coordinate system of this lattice so that translational points match up in 3D. rotation_matrix is the rotation that has to be applied to other_lattice to obtain aligned_lattice, i.e., aligned_matrix = rotation_matrix * other_lattice. Finally, scale_matrix is the integer matrix that expresses aligned_matrix as a linear combination of this lattice, i.e., aligned_matrix = scale_matrix * self None is returned if no matches are found. """ (lengths, angles) = other_lattice.lengths_and_angles (alpha, beta, gamma) = angles points = get_points_in_sphere_pbc(self, [[0, 0, 0]], [0, 0, 0], max(lengths) + 0.1) all_frac = [p[0] for p in points] dist = [p[1] for p in points] cart = self.get_cartesian_coords(all_frac) data = zip(cart, dist) candidates = [ filter(lambda d: abs(d[1] - l) < ltol, data) for l in lengths ] def get_angle(v1, v2): x = dot(v1[0], v2[0]) / v1[1] / v2[1] x = min(1, x) x = max(-1, x) angle = np.arccos(x) * 180. / pi return angle for m1, m2, m3 in itertools.product(*candidates): if abs(get_angle(m1, m2) - gamma) < atol and\ abs(get_angle(m2, m3) - alpha) < atol and\ abs(get_angle(m1, m3) - beta) < atol: aligned_m = np.array([m1[0], m2[0], m3[0]]) rotation_matrix = np.linalg.solve(other_lattice.matrix.T, aligned_m.T).T scale_matrix = np.linalg.solve(aligned_m.T, self._matrix.T).T return Lattice(aligned_m), rotation_matrix, scale_matrix return None
def find_mapping(self, other_lattice, ltol=1e-5, atol=1): """ Finds a mapping between current lattice and another lattice. There are an infinite number of choices of basis vectors for two entirely equivalent lattices. This method returns a mapping that maps other_lattice to this lattice. Args: other_lattice: Another lattice that is equivalent to this one. ltol: Tolerance for matching lengths. Defaults to 1e-5. atol: Tolerance for matching angles. Defaults to 1. Returns: (aligned_lattice, rotation_matrix, scale_matrix) if a mapping is found. aligned_lattice is a rotated version of other_lattice that has the same lattice parameters, but which is aligned in the coordinate system of this lattice so that translational points match up in 3D. rotation_matrix is the rotation that has to be applied to other_lattice to obtain aligned_lattice, i.e., aligned_matrix = rotation_matrix * other_lattice. Finally, scale_matrix is the integer matrix that expresses aligned_matrix as a linear combination of this lattice, i.e., aligned_matrix = scale_matrix * self None is returned if no matches are found. """ (lengths, angles) = other_lattice.lengths_and_angles (alpha, beta, gamma) = angles points = get_points_in_sphere_pbc(self, [[0, 0, 0]], [0, 0, 0], max(lengths) + 0.1) all_frac = [p[0] for p in points] dist = [p[1] for p in points] cart = self.get_cartesian_coords(all_frac) data = zip(cart, dist) candidates = [filter(lambda d: abs(d[1] - l) < ltol, data) for l in lengths] def get_angle(v1, v2): x = dot(v1[0], v2[0]) / v1[1] / v2[1] x = min(1, x) x = max(-1, x) angle = np.arccos(x) * 180. / pi angle = np.around(angle, 9) return angle for m1, m2, m3 in itertools.product(*candidates): if abs(get_angle(m1, m2) - gamma) < atol and\ abs(get_angle(m2, m3) - alpha) < atol and\ abs(get_angle(m1, m3) - beta) < atol: aligned_m = np.array([m1[0], m2[0], m3[0]]) rotation_matrix = np.linalg.solve(other_lattice.matrix.T, aligned_m.T).T scale_matrix = np.linalg.solve(aligned_m.T, self._matrix.T).T return Lattice(aligned_m), rotation_matrix, scale_matrix return None
def _calc_recip(self): """ Perform the reciprocal space summation. Calculates the quantity E_recip = 1/(2PiV) sum_{G < Gmax} exp(-(G.G/4/eta))/(G.G) S(G)S(-G) where S(G) = sum_{k=1,N} q_k exp(-i G.r_k) S(G)S(-G) = |S(G)|**2 This method is heavily vectorized to utilize numpy's C backend for speed. """ numsites = self._s.num_sites prefactor = 2 * pi / self._vol erecip = np.zeros((numsites, numsites)) forces = np.zeros((numsites, 3)) coords = self._coords rcp_latt = self._s.lattice.reciprocal_lattice recip_nn = get_points_in_sphere_pbc(rcp_latt, [[0, 0, 0]], [0, 0, 0], self._gmax) frac_to_cart = rcp_latt.get_cartesian_coords oxistates = np.array(self._oxi_states) #create array where q_2[i,j] is qi * qj qiqj = oxistates[None, :] * oxistates[:, None] for (fcoords, dist, i) in recip_nn: if dist == 0: continue gvect = frac_to_cart(fcoords) gsquare = np.linalg.norm(gvect) ** 2 expval = exp(-1.0 * gsquare / (4.0 * self._eta)) gvectdot = np.sum(gvect[None, :] * coords, 1) #calculate the structure factor sreal = np.sum(oxistates * np.cos(gvectdot)) simag = np.sum(oxistates * np.sin(gvectdot)) #create array where exparg[i,j] is gvectdot[i] - gvectdot[j] exparg = gvectdot[None, :] - gvectdot[:, None] #uses the identity sin(x)+cos(x) = 2**0.5 sin(x + pi/4) sfactor = qiqj * np.sin(exparg + pi / 4) * 2 ** 0.5 erecip += expval / gsquare * sfactor pref = 2 * expval / gsquare * oxistates factor = prefactor * pref * \ (sreal * np.sin(gvectdot) - simag * np.cos(gvectdot)) * EwaldSummation.CONV_FACT forces += factor[:, None] * gvect[None, :] return erecip * prefactor * EwaldSummation.CONV_FACT, forces
def _calc_recip(self): """ Perform the reciprocal space summation. Calculates the quantity E_recip = 1/(2PiV) sum_{G < Gmax} exp(-(G.G/4/eta))/(G.G) S(G)S(-G) where S(G) = sum_{k=1,N} q_k exp(-i G.r_k) S(G)S(-G) = |S(G)|**2 This method is heavily vectorized to utilize numpy's C backend for speed. """ numsites = self._s.num_sites prefactor = 2 * pi / self._vol erecip = np.zeros((numsites, numsites)) forces = np.zeros((numsites, 3)) coords = self._coords rcp_latt = self._s.lattice.reciprocal_lattice recip_nn = get_points_in_sphere_pbc(rcp_latt, [[0, 0, 0]], [0, 0, 0], self._gmax) frac_to_cart = rcp_latt.get_cartesian_coords oxistates = np.array(self._oxi_states) #create array where q_2[i,j] is qi * qj qiqj = oxistates[None, :] * oxistates[:, None] for (fcoords, dist, i) in recip_nn: if dist == 0: continue gvect = frac_to_cart(fcoords) gsquare = np.linalg.norm(gvect)**2 expval = exp(-1.0 * gsquare / (4.0 * self._eta)) gvectdot = np.sum(gvect[None, :] * coords, 1) #calculate the structure factor sreal = np.sum(oxistates * np.cos(gvectdot)) simag = np.sum(oxistates * np.sin(gvectdot)) #create array where exparg[i,j] is gvectdot[i] - gvectdot[j] exparg = gvectdot[None, :] - gvectdot[:, None] #uses the identity sin(x)+cos(x) = 2**0.5 sin(x + pi/4) sfactor = qiqj * np.sin(exparg + pi / 4) * 2**0.5 erecip += expval / gsquare * sfactor pref = 2 * expval / gsquare * oxistates factor = prefactor * pref * \ (sreal * np.sin(gvectdot) - simag * np.cos(gvectdot)) * EwaldSummation.CONV_FACT forces += factor[:, None] * gvect[None, :] return erecip * prefactor * EwaldSummation.CONV_FACT, forces
def _get_lattices(self, s1, s2, vol_tol): s1_lengths, s1_angles = s1.lattice.lengths_and_angles all_nn = get_points_in_sphere_pbc(s2.lattice, [[0, 0, 0]], [0, 0, 0], (1 + self.ltol) * max(s1_lengths))[:, [0, 1]] nv = [] for l in s1_lengths: nvi = all_nn[np.where((all_nn[:, 1] < (1 + self.ltol) * l) & (all_nn[:, 1] > (1 - self.ltol) * l))][:, 0] if not len(nvi): return nvi = [np.array(site) for site in nvi] nvi = np.dot(nvi, s2.lattice.matrix) nv.append(nvi) #The vectors are broadcast into a 5-D array containing #all permutations of the entries in nv[0], nv[1], nv[2] #produces the same result as three nested loops over the #same variables and calculating determinants individually bfl = (np.array(nv[0])[None, None, :, None, :] * np.array([1, 0, 0])[None, None, None, :, None] + np.array(nv[1])[None, :, None, None, :] * np.array([0, 1, 0])[None, None, None, :, None] + np.array(nv[2])[:, None, None, None, :] * np.array([0, 0, 1])[None, None, None, :, None]) #Compute volume of each array vol = np.sum( bfl[:, :, :, 0, :] * np.cross(bfl[:, :, :, 1, :], bfl[:, :, :, 2, :]), 3) #Find valid lattices valid = np.where(abs(vol) >= vol_tol) if not len(valid[0]): return #loop over valid lattices to compute the angles for each lengths = np.sum(bfl[valid]**2, axis=2)**0.5 angles = np.zeros((len(bfl[valid]), 3), float) for i in xrange(3): j = (i + 1) % 3 k = (i + 2) % 3 angles[:, i] = \ np.sum(bfl[valid][:, j, :] * bfl[valid][:, k, :], 1) \ / (lengths[:, j] * lengths[:, k]) angles = np.arccos(angles) * 180. / np.pi #Check angles are within tolerance valid_angles = np.where( np.all(np.abs(angles - s1_angles) < self.angle_tol, axis=1)) if len(valid_angles[0]) == 0: return #yield valid lattices for lat in bfl[valid][valid_angles]: nl = Lattice(lat) yield nl
def _get_lattices(self, s1, s2, vol_tol): s1_lengths, s1_angles = s1.lattice.lengths_and_angles all_nn = get_points_in_sphere_pbc( s2.lattice, [[0, 0, 0]], [0, 0, 0], (1 + self.ltol) * max(s1_lengths))[:, [0, 1]] nv = [] for l in s1_lengths: nvi = all_nn[np.where((all_nn[:, 1] < (1 + self.ltol) * l) & (all_nn[:, 1] > (1 - self.ltol) * l))][:, 0] if not len(nvi): return nvi = [np.array(site) for site in nvi] nvi = np.dot(nvi, s2.lattice.matrix) nv.append(nvi) #The vectors are broadcast into a 5-D array containing #all permutations of the entries in nv[0], nv[1], nv[2] #produces the same result as three nested loops over the #same variables and calculating determinants individually bfl = (np.array(nv[0])[None, None, :, None, :] * np.array([1, 0, 0])[None, None, None, :, None] + np.array(nv[1])[None, :, None, None, :] * np.array([0, 1, 0])[None, None, None, :, None] + np.array(nv[2])[:, None, None, None, :] * np.array([0, 0, 1])[None, None, None, :, None]) #Compute volume of each array vol = np.sum(bfl[:, :, :, 0, :] * np.cross(bfl[:, :, :, 1, :], bfl[:, :, :, 2, :]), 3) #Find valid lattices valid = np.where(abs(vol) >= vol_tol) if not len(valid[0]): return #loop over valid lattices to compute the angles for each lengths = np.sum(bfl[valid] ** 2, axis=2) ** 0.5 angles = np.zeros((len(bfl[valid]), 3), float) for i in xrange(3): j = (i + 1) % 3 k = (i + 2) % 3 angles[:, i] = \ np.sum(bfl[valid][:, j, :] * bfl[valid][:, k, :], 1) \ / (lengths[:, j] * lengths[:, k]) angles = np.arccos(angles) * 180. / np.pi #Check angles are within tolerance valid_angles = np.where(np.all(np.abs(angles - s1_angles) < self.angle_tol, axis=1)) if len(valid_angles[0]) == 0: return #yield valid lattices for lat in bfl[valid][valid_angles]: nl = Lattice(lat) yield nl
def to_unit_cell(self, tolerance=0.1): """ Returns all the sites to their position inside the unit cell. If there is a site within the tolerance already there, the site is deleted instead of moved. """ new_sites = [] for site in self._sites: if not new_sites: new_sites.append(site) frac_coords = np.array([site.frac_coords]) continue if len(get_points_in_sphere_pbc(self._lattice, frac_coords, site.coords, tolerance)): continue frac_coords = np.append(frac_coords, [site.frac_coords % 1], axis=0) new_sites.append(site.to_unit_cell) self._sites = new_sites
def to_unit_cell(self, tolerance=0.1): """ Returns all the sites to their position inside the unit cell. If there is a site within the tolerance already there, the site is deleted instead of moved. """ new_sites = [] for site in self._sites: if not new_sites: new_sites.append(site) frac_coords = np.array([site.frac_coords]) continue if len( get_points_in_sphere_pbc(self._lattice, frac_coords, site.coords, tolerance)): continue frac_coords = np.append(frac_coords, [site.frac_coords % 1], axis=0) new_sites.append(site.to_unit_cell) self._sites = new_sites
def get_sites_in_sphere(self, pt, r, include_index=False): """ Find all sites within a sphere from the point. This includes sites in other periodic images. Algorithm: 1. place sphere of radius r in crystal and determine minimum supercell (parallelpiped) which would contain a sphere of radius r. for this we need the projection of a_1 on a unit vector perpendicular to a_2 & a_3 (i.e. the unit vector in the direction b_1) to determine how many a_1"s it will take to contain the sphere. Nxmax = r * length_of_b_1 / (2 Pi) 2. keep points falling within r. Args: pt: cartesian coordinates of center of sphere. r: radius of sphere. include_index: boolean that determines whether the non-supercell site index is included in the returned data Returns: [(site, dist) ...] since most of the time, subsequent processing requires the distance. """ site_fcoords = np.mod(self.frac_coords, 1) neighbors = [] for fcoord, dist, i in get_points_in_sphere_pbc(self._lattice, site_fcoords, pt, r): nnsite = PeriodicSite(self[i].species_and_occu, fcoord, self._lattice, properties=self[i].properties) neighbors.append((nnsite, dist) if not include_index else (nnsite, dist, i)) return neighbors
def _get_lattices(self, target_s, s, supercell_size=1): """ Yields lattices for s with lengths and angles close to the lattice of target_s. If supercell_size is specified, the returned lattice will have that number of primitive cells in it Args: s, target_s: Structure objects """ t_l, t_a = target_s.lattice.lengths_and_angles r = (1 + self.ltol) * max(t_l) fpts, dists, i = get_points_in_sphere_pbc( lattice=s.lattice, frac_points=[[0, 0, 0]], center=[0, 0, 0], r=r).T #get possible vectors for a, b, and c new_v = [] for l in t_l: max_r = (1 + self.ltol) * l min_r = (1 - self.ltol) * l vi = fpts[np.where((dists < max_r) & (dists > min_r))] if len(vi) == 0: return cart_vi = np.dot(np.array([i for i in vi]), s.lattice.matrix) new_v.append(cart_vi) #The vectors are broadcast into a 5-D array containing #all permutations of the entries in new_v[0], new_v[1], new_v[2] #Produces the same result as three nested loops over the #same variables and calculating determinants individually bfl = (np.array(new_v[0])[None, None, :, None, :] * np.array([1, 0, 0])[None, None, None, :, None] + np.array(new_v[1])[None, :, None, None, :] * np.array([0, 1, 0])[None, None, None, :, None] + np.array(new_v[2])[:, None, None, None, :] * np.array([0, 0, 1])[None, None, None, :, None]) #Compute volume of each lattice vol = np.abs(np.sum(bfl[:, :, :, 0, :] * np.cross(bfl[:, :, :, 1, :], bfl[:, :, :, 2, :]), 3)) #valid lattices must not change volume min_vol = s.volume * 0.999 * supercell_size max_vol = s.volume * 1.001 * supercell_size bfl = bfl[np.where((vol > min_vol) & (vol < max_vol))] if len(bfl) == 0: return #compute angles lengths = np.sum(bfl ** 2, axis=2) ** 0.5 angles = np.zeros((len(bfl), 3), float) for i in xrange(3): j = (i + 1) % 3 k = (i + 2) % 3 angles[:, i] = \ np.sum(bfl[:, j, :] * bfl[:, k, :], 1) \ / (lengths[:, j] * lengths[:, k]) angles = np.arccos(angles) * 180. / np.pi #Check angles are within tolerance valid_angles = np.where(np.all(np.abs(angles - t_a) < self.angle_tol, axis=1)) for lat in bfl[valid_angles]: nl = Lattice(lat) yield nl
def _get_lattices(self, target_s, s, supercell_size=1): """ Yields lattices for s with lengths and angles close to the lattice of target_s. If supercell_size is specified, the returned lattice will have that number of primitive cells in it Args: s, target_s: Structure objects """ t_l, t_a = target_s.lattice.lengths_and_angles r = (1 + self.ltol) * max(t_l) fpts, dists, i = get_points_in_sphere_pbc(lattice=s.lattice, frac_points=[[0, 0, 0]], center=[0, 0, 0], r=r).T #get possible vectors for a, b, and c new_v = [] for l in t_l: max_r = (1 + self.ltol) * l min_r = (1 - self.ltol) * l vi = fpts[np.where((dists < max_r) & (dists > min_r))] if len(vi) == 0: return cart_vi = np.dot(np.array([i for i in vi]), s.lattice.matrix) new_v.append(cart_vi) #The vectors are broadcast into a 5-D array containing #all permutations of the entries in new_v[0], new_v[1], new_v[2] #Produces the same result as three nested loops over the #same variables and calculating determinants individually bfl = (np.array(new_v[0])[None, None, :, None, :] * np.array([1, 0, 0])[None, None, None, :, None] + np.array(new_v[1])[None, :, None, None, :] * np.array([0, 1, 0])[None, None, None, :, None] + np.array(new_v[2])[:, None, None, None, :] * np.array([0, 0, 1])[None, None, None, :, None]) #Compute volume of each lattice vol = np.abs( np.sum( bfl[:, :, :, 0, :] * np.cross(bfl[:, :, :, 1, :], bfl[:, :, :, 2, :]), 3)) #valid lattices must not change volume min_vol = s.volume * 0.999 * supercell_size max_vol = s.volume * 1.001 * supercell_size bfl = bfl[np.where((vol > min_vol) & (vol < max_vol))] if len(bfl) == 0: return #compute angles lengths = np.sum(bfl**2, axis=2)**0.5 angles = np.zeros((len(bfl), 3), float) for i in xrange(3): j = (i + 1) % 3 k = (i + 2) % 3 angles[:, i] = \ np.sum(bfl[:, j, :] * bfl[:, k, :], 1) \ / (lengths[:, j] * lengths[:, k]) angles = np.arccos(angles) * 180. / np.pi #Check angles are within tolerance valid_angles = np.where( np.all(np.abs(angles - t_a) < self.angle_tol, axis=1)) for lat in bfl[valid_angles]: nl = Lattice(lat) yield nl