def test_merge(self): pt, wp, _ = WP_merge([0.05, 0.7, 0.24], l1.get_matrix(), wp1, 0.5) symbol = str(wp.multiplicity) + wp.letter self.assertTrue(symbol == "4a") pt, wp, _ = WP_merge([0.15, 0.7, 0.24], l1.get_matrix(), wp1, 0.5) symbol = str(wp.multiplicity) + wp.letter self.assertTrue(symbol == "8b")
def match(self): """ Check the two molecular graphs are isomorphic """ match, mapping = compare_mol_connectivity(self.ref_mol, self.molecule) if not match: print(self.ref_mol.to("xyz")) print(self.molecule.to("xyz")) import pickle with open('wrong.pkl', "wb") as f: pickle.dump([self.ref_mol, self.molecule], f) return False else: # resort the atomic number for molecule 1 order = [mapping[i] for i in range(len(self.ref_mol))] numbers = np.array(self.molecule.atomic_numbers) numbers = numbers[order].tolist() coords = self.molecule.cart_coords[order] position = np.mean(coords, axis=0).dot(self.lattice.inv_matrix) position -= np.floor(position) # check if molecule is on the special wyckoff position if len(self.pmg_struc)/len(self.molecule) < len(self.wyc): if self.diag: #Transform it to the conventional representation position = np.dot(self.perm, position).T position, wp, _ = WP_merge(position, self.lattice.matrix, self.wyc, 2.0) #print("After Mergey:---------------") #print(position) #print(wp) self.wyc = wp self.position = position self.molecule = Molecule(numbers, coords-np.mean(coords, axis=0)) #self.align() return True
def read_cif(filename): """ read the cif, mainly for pyxtal cif output Be cautious in using it to read other cif files Args: filename: path of the structure file Return: pyxtal structure """ species = [] coords = [] with open(filename, 'r') as f: lines = f.readlines() for i, line in enumerate(lines): if line.startswith('_symmetry_Int_Tables_number'): sg = int(line.split()[-1]) elif line.startswith('_cell_length_a'): a = float(lines[i].split()[-1]) b = float(lines[i+1].split()[-1]) c = float(lines[i+2].split()[-1]) alpha = float(lines[i+3].split()[-1]) beta = float(lines[i+4].split()[-1]) gamma = float(lines[i+5].split()[-1]) elif line.startswith('_symmetry_cell_setting'): lat_type = line.split()[-1] elif line.startswith('_symmetry_space_group_name_H-M '): symbol = line.split()[-1] if eval(symbol) in ["Pn", "P21/n", "C2/n"]: diag = True else: diag = False elif line.find('_atom_site') >= 0: s = i while True: s += 1 if lines[s].find('_atom_site') >= 0: pass elif len(lines[s].split()) <= 3: break else: tmp = lines[s].split() pos = [float(tmp[-4]), float(tmp[-3]), float(tmp[-2])] species.append(tmp[0]) coords.append(pos) break wp0 = Group(sg)[0] lattice = Lattice.from_para(a, b, c, alpha, beta, gamma, lat_type) sites = [] for specie, coord in zip(species, coords): pt, wp, _ = WP_merge(coord, lattice.matrix, wp0, tol=0.1) sites.append(atom_site(wp, pt, specie, diag)) return lattice, sites
def _generate_mol_wyckoffs(self, id, numMol, pyxtal_mol, valid_ori, mol_wyks): """ generates a set of wyckoff positions to accomodate a given number of molecules Args: numMol: Number of ions to accomodate pyxtal_mol: Type of species being placed on wyckoff site mol_wyks: current wyckoff sites Returns: if sucess, wyckoff_sites_tmp: list of wyckoff sites for valid sites otherwise, None """ numMol_added = 0 mol_sites_tmp = [] # Now we start to add the specie to the wyckoff position sites_list = deepcopy(self.sites[id]) # the list of Wyckoff site if sites_list is not None: self.wyckoff_attempts = max(len(sites_list) * 2, 10) else: # the minimum numattempts is to put all atoms to the general WPs min_wyckoffs = int(numMol / len(self.group.wyckoffs_organized[0][0])) self.wyckoff_attempts = max(2 * min_wyckoffs, 10) for cycle in range(self.wyckoff_attempts): # Choose a random WP for given multiplicity: 2a, 2b, 2c if sites_list is not None: site = sites_list[0] else: # Selecting the merging site = None # NOTE: The molecular version return wyckoff indices, not ops diff = numMol - numMol_added wp = choose_wyckoff_molecular(self.group, diff, site, valid_ori, self.select_high, self.dim) if wp is not False: # Generate a list of coords from the wyckoff position mult = wp.multiplicity # remember the original multiplicity pt = self.lattice.generate_point() # merge coordinates if the atoms are close mtol = pyxtal_mol.radius * 0.5 pt, wp, oris = WP_merge(pt, self.lattice.matrix, wp, mtol, valid_ori) if wp is not False: if site is not None and mult != wp.multiplicity: continue if self.dim == 2 and self.thickness is not None and self.thickness < 0.1: pt[-1] = 0.5 ms0 = self._generate_orientation(pyxtal_mol, pt, oris, wp) if ms0 is not None: # Check current WP against existing WP's passed_wp_check = True for ms1 in mol_sites_tmp + mol_wyks: if not check_mol_sites( ms0, ms1, tm=self.tol_matrix): passed_wp_check = False if passed_wp_check: if sites_list is not None: sites_list.pop(0) mol_sites_tmp.append(ms0) numMol_added += len(ms0.wp) # We have enough molecules of the current type if numMol_added == numMol: return mol_sites_tmp return None
def _generate_ion_wyckoffs(self, numIon, specie, cell_matrix, wyks): """ generates a set of wyckoff positions to accomodate a given number of ions Args: numIon: Number of ions to accomodate specie: Type of species being placed on wyckoff site cell_matrix: Matrix of lattice vectors wyks: current wyckoff sites Returns: Sucess: wyckoff_sites_tmp: list of wyckoff sites for valid sites Failue: None """ numIon_added = 0 tol = self.tol_matrix.get_tol(specie, specie) tol_matrix = self.tol_matrix wyckoff_sites_tmp = [] # Now we start to add the specie to the wyckoff position sites_list = deepcopy(self.sites[specie]) # the list of Wyckoff site if sites_list is not None: wyckoff_attempts = max(len(sites_list) * 2, 10) else: # the minimum numattempts is to put all atoms to the general WPs min_wyckoffs = int(numIon / len(self.group.wyckoffs_organized[0][0])) wyckoff_attempts = max(2 * min_wyckoffs, 10) cycle = 0 while cycle < wyckoff_attempts: # Choose a random WP for given multiplicity: 2a, 2b if sites_list is not None: site = sites_list[0] else: # Selecting the merging site = None wp = choose_wyckoff(self.group, numIon - numIon_added, site, self.dim) if wp is not False: # Generate a list of coords from ops mult = wp.multiplicity # remember the original multiplicity pt = self.lattice.generate_point() # Merge coordinates if the atoms are close pt, wp, _ = WP_merge(pt, cell_matrix, wp, tol) # For pure planar structure if self.dim == 2 and self.thickness is not None and self.thickness < 0.1: pt[-1] = 0.5 # If site the pre-assigned, do not accept merge if wp is not False: if site is not None and mult != wp.multiplicity: cycle += 1 continue # Use a Wyckoff_site object for the current site new_site = atom_site(wp, pt, specie) # Check current WP against existing WP's passed_wp_check = True for ws in wyckoff_sites_tmp + wyks: if not new_site.check_with_ws2(ws, cell_matrix, tol_matrix): passed_wp_check = False if passed_wp_check: if sites_list is not None: sites_list.pop(0) wyckoff_sites_tmp.append(new_site) numIon_added += new_site.multiplicity # Check if enough atoms have been added if numIon_added == numIon: return wyckoff_sites_tmp cycle += 1 self.numattempts += 1 return None
def resort(self, molecules): from pyxtal.operations import apply_ops, find_ids # filter out the molecular generators lat = self.pmg_struc.lattice.matrix inv_lat = self.pmg_struc.lattice.inv_matrix new_lat = self.lattice.matrix positions = np.zeros([len(molecules),3]) for i in range(len(molecules)): positions[i, :] = np.dot(molecules[i].cart_coords.mean(axis=0), inv_lat) ids = [] #id for the generator mults = [] #the corresponding multiplicities visited_ids = [] for id, pos in enumerate(positions): if id not in visited_ids: ids.append(id) #print("pos", pos); print(self.wyc) centers = apply_ops(pos, self.wyc) tmp_ids = find_ids(centers, positions) visited_ids.extend(tmp_ids) mults.append(len(tmp_ids)) #print("check", id, tmp_ids) # add position and molecule # print("ids", ids, mults) self.numMols = [0] * len(self.ref_mols) self.positions = [] self.p_mols = [] self.wps = [] for i in range(len(ids)): mol1 = molecules[ids[i]] matched = False for j, mol2 in enumerate(self.ref_mols): match, mapping = compare_mol_connectivity(mol2.mol, mol1) if match: self.numMols[j] += mults[i] # rearrange the order order = [mapping[at] for at in range(len(mol1))] xyz = mol1.cart_coords[order] frac = np.dot(xyz, inv_lat) xyz = np.dot(frac, new_lat) # create p_mol p_mol = mol2.copy() center = p_mol.get_center(xyz) #print(xyz-center) p_mol.reset_positions(xyz-center) position = np.dot(center, np.linalg.inv(new_lat)) position -= np.floor(position) #print(position) #print(lat) #print(p_mol.mol.cart_coords[:10] + np.dot(position, new_lat)) # print(len(self.pmg_struc), len(self.molecule), len(self.wyc)) # check if molecule is on the special wyckoff position if mults[i] < len(self.wyc): #Transform it to the conventional representation if self.diag: position = np.dot(self.perm, position).T #print("molecule is on the special wyckoff position") position, wp, _ = WP_merge(position, new_lat, self.wyc, 0.1) self.wps.append(wp) #print("After Merge:---"); print(position); print(wp) else: self.wps.append(self.wyc) self.positions.append(position) self.p_mols.append(p_mol) matched = True break if not matched: print(mol1.to('xyz')) print(mol2.mol.to('xyz')) raise RuntimeError("molecule cannot be matched")