def compute_distances(self): """ compute if the atoms in the Wyckoff position are too close to each other or not. Does not check distances between atoms in the same molecule. Uses crystal.check_distance as the base code. Returns: minimum distances """ m_length = len(self.symbols) # TODO: Use tm instead of tols lists # Get coords of WP with PBC coords, _ = self._get_coords_and_species() # Get coords of the generating molecule coords_mol = coords[:m_length] # Remove generating molecule's coords from large array coords = coords[m_length:] min_ds = [] # Check periodic images m = self._create_matrix() coords_PBC = np.vstack([coords_mol + v for v in m]) d = distance_matrix(coords_mol, coords_PBC, self.lattice.matrix, [0, 0, 0], True) if d < 0.9: return d else: min_ds.append(d) if self.wp.multiplicity > 1: # Check inter-atomic distances d = distance_matrix(coords_mol, coords, self.lattice.matrix, self.PBC, True) min_ds.append(d) return min(min_ds)
def check_mol_sites(ms1, ms2, factor=1.0, tm=Tol_matrix(prototype="molecular")): """ Checks whether or not the molecules of two mol sites overlap. Uses ellipsoid overlapping approximation to check. Takes PBC and lattice into consideration. Args: ms1: a mol_site object ms2: another mol_site object factor: the distance factor to pass to check_distances. (only for inter-atomic distance checking) tm: a Tol_matrix object (or prototype string) for distance checking Returns: False if the Wyckoff positions overlap. True otherwise """ # Get coordinates for both mol_sites c1, _ = ms1.get_coords_and_species() c2, _ = ms2.get_coords_and_species() # Calculate which distance matrix is smaller/faster m_length1 = len(ms1.numbers) m_length2 = len(ms2.numbers) wp_length1 = len(c1) wp_length2 = len(c2) size1 = m_length1 * wp_length2 size2 = m_length2 * wp_length1 # Case 1 if size1 <= size2: coords_mol = c1[:m_length1] # Calculate tol matrix for species pairs tols = np.zeros((m_length1, m_length2)) for i1, number1 in enumerate(ms1.numbers): for i2, number2 in enumerate(ms2.numbers): tols[i1][i2] = tm.get_tol(number1, number2) tols = np.repeat(tols, ms2.wp.multiplicity, axis=1) d = distance_matrix(coords_mol, c2, ms1.lattice.matrix, PBC=ms1.PBC) # Case 2 elif size1 > size2: coords_mol = c2[:m_length2] # Calculate tol matrix for species pairs tols = np.zeros((m_length2, m_length1)) for i1, number1 in enumerate(ms2.numbers): for i2, number2 in enumerate(ms1.numbers): tols[i1][i2] = tm.get_tol(number1, number2) tols = np.repeat(tols, ms1.wp.multiplicity, axis=1) d = distance_matrix(coords_mol, c1, ms1.lattice.matrix, PBC=ms1.PBC) # Check if distances are smaller than tolerances if (d < tols).any(): return False return True
def check_atom_sites(ws1, ws2, lattice, tm, same_group=True): """ Given two Wyckoff sites, checks the inter-atomic distances between them. Args: ws1: a Wyckoff_site object ws2: a different Wyckoff_site object (will always return False if two identical WS's are provided) lattice: a 3x3 cell matrix same_group: whether or not the two WS's are in the same structure. Default value True reduces the calculation cost Returns: True if all distances are greater than the allowed tolerances. False if any distance is smaller than the allowed tolerance """ # Ensure the PBC values are valid if ws1.PBC != ws2.PBC: printx("Error: PBC values do not match between Wyckoff sites") return # Get tolerance tol = tm.get_tol(ws1.specie, ws2.specie) # Symmetry shortcut method: check only some atoms if same_group is True: # We can either check one atom in WS1 against all WS2, or vice-versa # Check which option is faster if ws1.multiplicity > ws2.multiplicity: coords1 = [ws1.coords[0]] coords2 = ws2.coords else: coords1 = [ws2.coords[0]] coords2 = ws1.coords # Calculate distances dm = distance_matrix(coords1, coords2, lattice, PBC=ws1.PBC) # Check if any distances are less than the tolerance if (dm < tol).any(): return False else: return True # No symmetry method: check all atomic pairs else: dm = distance_matrix(ws1.coords, ws2.coords, lattice, PBC=ws1.PBC) # Check if any distances are less than the tolerance if (dm < tol).any(): return False else: return True
def check_distances(self): """ Checks if the atoms in the Wyckoff position are too close to each other or not. Does not check distances between atoms in the same molecule. Uses crystal.check_distance as the base code. Returns: True if the atoms are not too close together, False otherwise """ m_length = len(self.symbols) coords, _ = self._get_coords_and_species() # Get coords of the generating molecule coords_mol = coords[:m_length] # Remove generating molecule's coords from large array coords = coords[m_length:] # Check periodic images m = self._create_matrix() coords_PBC = np.vstack([coords_mol + v for v in m]) d = distance_matrix(coords_PBC, coords_mol, self.lattice.matrix, PBC=[0, 0, 0]) # only check if small distance is detected if np.min(d) < np.max(self.tols_matrix): tols = np.min(d.reshape([len(m), m_length, m_length]), axis=0) if (tols < self.tols_matrix).any(): return False if self.wp.multiplicity > 1: # Check inter-atomic distances d = distance_matrix(coords, coords_mol, self.lattice.matrix, PBC=self.PBC) if np.min(d) < np.max(self.tols_matrix): tols = np.min(d.reshape( [self.wp.multiplicity - 1, m_length, m_length]), axis=0) if (tols < self.tols_matrix).any(): return False return True
def WP_merge(pt, lattice, wp, tol, orientations=None): """ Given a list of fractional coordinates, merges them within a given tolerance, and checks if the merged coordinates satisfy a Wyckoff position. Used for merging general Wyckoff positions into special Wyckoff positions within the random_crystal (and its derivative) classes. Args: pt: the originl point (3-vector) lattice: a 3x3 matrix representing the unit cell wp: a `Wyckoff_position <pyxtal.symmetry.Wyckoff_position.html> object after merge tol: the cutoff distance for merging coordinates orientations: the valid orientations for a given molecule. Obtained from get_sg_orientations, which is called within molecular_crystal Returns: pt: 3-vector after merge wp: a `pyxtal.symmetry.Wyckoff_position` object, If no matching WP, returns False. valid_ori: the valid orientations after merge """ index = wp.index PBC = wp.PBC group = Group(wp.number, wp.dim) pt = project_point(pt, wp[0], lattice, PBC) coor = apply_ops(pt, wp) if orientations is None: valid_ori = None else: j, k = jk_from_i(index, orientations) valid_ori = orientations[j][k] # Main loop for merging multiple times while True: # Check distances of current WP. If too small, merge dm = distance_matrix([coor[0]], coor, lattice, PBC=PBC) passed_distance_check = True x = np.argwhere(dm < tol) for y in x: # Ignore distance from atom to itself if y[0] == 0 and y[1] == 0: pass else: passed_distance_check = False break # for molecular crystal, one more check if check_images([coor[0]], [6], lattice, PBC=PBC, tol=tol) is False: passed_distance_check = False if not passed_distance_check: mult1 = group[index].multiplicity # Find possible wp's to merge into possible = [] for i, wp0 in enumerate(group): mult2 = wp0.multiplicity # Check that a valid orientation exists if orientations is not None: j, k = jk_from_i(i, orientations) if orientations[j][k] == []: continue else: valid_ori = orientations[j][k] # factor = mult2 / mult1 if (mult2 < mult1) and (mult1 % mult2 == 0): possible.append(i) if possible == []: return None, False, valid_ori # Calculate minimum separation for each WP distances = [] for i in possible: wp = group[i] projected_point = project_point(pt, wp[0], lattice=lattice, PBC=PBC) d = distance(pt - projected_point, lattice, PBC=PBC) distances.append(np.min(d)) # Choose wp with shortest translation for generating point tmpindex = np.argmin(distances) index = possible[tmpindex] wp = group[index] pt = project_point(pt, wp[0], lattice=lattice, PBC=PBC) coor = apply_ops(pt, wp) # Distances were not too small; return True else: return pt, wp, valid_ori