def compare_wyckoffs(num1, num2, dim=3): """Given 2 groups, return whether the second point group has equal or greater symmetry than the first group.""" from numpy import allclose if num1 == "???": print("Error: invalid value for num1 passed to compare_wyckoffs") return if num2 == "???": return False # Get general positions for both groups if dim == 3: from pyxtal.symmetry import get_wyckoffs g1 = get_wyckoffs(num1)[0] g2 = get_wyckoffs(num2)[0] elif dim == 2: from pyxtal.symmetry import get_layer g1 = get_layer(num1)[0] g2 = get_layer(num2)[0] elif dim == 1: from pyxtal.symmetry import get_rod g1 = get_rod(num1)[0] g2 = get_rod(num2)[0] elif dim == 0: from pyxtal.symmetry import get_point g1 = get_point(num1)[0] g2 = get_point(num2)[0] # If group 2 has higher symmetry if len(g2) > len(g1): return True # Compare point group operations for i, op2 in enumerate(g2): op1 = g1[i] m1 = op1.rotation_matrix m2 = op2.rotation_matrix if not allclose(m1, m2): return False return True
def check_struct_group(crystal, group, dim=3, tol=1e-2): # Supress pymatgen/numpy complex casting warnings from pyxtal.crystal import random_crystal from pyxtal.molecular_crystal import molecular_crystal from copy import deepcopy import warnings with warnings.catch_warnings(): warnings.simplefilter("ignore") """Given a pymatgen structure, group number, and dimension, return whether or not the structure matches the group number.""" if isinstance(crystal, (random_crystal, molecular_crystal)): lattice = crystal.struct.lattice.matrix if dim != 0: old_coords = deepcopy(crystal.struct.frac_coords) old_species = deepcopy(crystal.struct.atomic_numbers) elif dim == 0: old_coords = deepcopy(crystal.cart_coords) old_species = deepcopy(crystal.species) else: lattice = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) old_coords = np.array(crystal) old_species = ["C"] * len(old_coords) from pyxtal.symmetry import distance from pyxtal.symmetry import filtered_coords from copy import deepcopy PBC = [1, 1, 1] # Obtain the generators for the group if dim == 3: from pyxtal.symmetry import get_wyckoffs generators = get_wyckoffs(group)[0] elif dim == 2: from pyxtal.symmetry import get_layer generators = get_layer(group)[0] PBC = [1, 1, 0] elif dim == 1: from pyxtal.symmetry import get_rod generators = get_rod(group)[0] PBC = [0, 0, 1] elif dim == 0: from pyxtal.symmetry import Group generators = Group(group, dim=0)[0] PBC = [0, 0, 0] # TODO: Add check for lattice symmetry # Apply SymmOps to generate new points # old_coords = filtered_coords(struct.frac_coords,PBC=PBC) new_coords = [] new_species = [] for i, point in enumerate(old_coords): for j, op in enumerate(generators): if j != 0: new_coords.append(op.operate(point)) new_species.append(old_species[i]) # new_coords = filtered_coords(new_coords,PBC=PBC) # Check that all points in new list are still in old failed = False i_list = list(range(len(new_coords))) for i, point1 in enumerate(new_coords): found = False for j, point2 in enumerate(old_coords): if new_species[i] == old_species[j]: difference = filtered_coords(point2 - point1, PBC=PBC) if distance(difference, lattice, PBC=PBC) <= tol: found = True break if found is False: failed = True break if failed is False: return True else: return False
def check_struct_group(struct, group, dim=3, tol=1e-2): """Given a pymatgen structure, group number, and dimension, return whether or not the structure matches the group number.""" from pyxtal.symmetry import distance from pyxtal.symmetry import filtered_coords from copy import deepcopy lattice = struct.lattice.matrix PBC = [1,1,1] #Obtain the generators for the group if dim == 3: from pyxtal.symmetry import get_wyckoffs generators = get_wyckoffs(group)[0] elif dim == 2: from pyxtal.symmetry import get_layer generators = get_layer(group)[0] PBC = [1,1,0] elif dim == 1: from pyxtal.symmetry import get_rod generators = get_rod(group)[0] PBC = [0,0,1] elif dim == 0: from pyxtal.symmetry import get_point generators = get_point(group)[0] PBC = [0,0,0] #TODO: Add check for lattice symmetry #Apply SymmOps to generate new points #old_coords = filtered_coords(struct.frac_coords,PBC=PBC) old_coords = deepcopy(struct.frac_coords) if dim == 0: old_coords = old_coords - 0.5 old_species = deepcopy(struct.atomic_numbers) new_coords = [] new_species = [] for i, point in enumerate(old_coords): for j, op in enumerate(generators): if j != 0: new_coords.append(op.operate(point)) new_species.append(old_species[i]) #new_coords = filtered_coords(new_coords,PBC=PBC) #Check that all points in new list are still in old failed = False i_list = list(range(len(new_coords))) for i, point1 in enumerate(new_coords): found = False for j, point2 in enumerate(old_coords): if new_species[i] == old_species[j]: difference = filtered_coords(point2 - point1, PBC=PBC) if distance(difference, lattice, PBC=PBC) <= tol: found = True break if found is False: failed = True break if failed is False: return True else: return False
def test_molecular_2D(): global outstructs global outstrings print("=== Testing generation of molecular 2D crystals. This may take some time. ===") from time import time from spglib import get_symmetry_dataset from pyxtal.symmetry import get_layer from pyxtal.crystal import cellsize from pyxtal.molecular_crystal import molecular_crystal_2D from pyxtal.database.layergroup import Layergroup from pymatgen.symmetry.analyzer import SpacegroupAnalyzer slow = [] failed = [] print(" Layergroup | sg # Expected |Generated (SPG)|Generated (PMG)|Time Elapsed") skip = []#12, 64, 65, 80] #slow to generate for num in range(1, 81): if num not in skip: sg = Layergroup(num).sgnumber multiplicity = len(get_layer(num)[0]) / cellsize(sg) #multiplicity of the general position start = time() rand_crystal = molecular_crystal_2D(num, ['H2O'], [multiplicity], 4.0) end = time() timespent = np.around((end - start), decimals=2) t = str(timespent) if len(t) == 3: t += "0" t += " s" if timespent >= 1.0: t += " ~" if timespent >= 3.0: t += "~" if timespent >= 10.0: t += "~" if timespent >= 60.0: t += "~" slow.append(num) if rand_crystal.valid: check = False ans1 = get_symmetry_dataset(rand_crystal.spg_struct, symprec=1e-1) if ans1 is None: ans1 = "???" else: ans1 = ans1['number'] sga = SpacegroupAnalyzer(rand_crystal.struct) ans2 = "???" if sga is not None: try: ans2 = sga.get_space_group_number() except: ans2 = "???" if ans2 is None: ans2 = "???" #Compare expected and detected groups if ans1 == "???" and ans2 == "???": check = True elif ans1 == "???": if int(ans2) > sg: pass elif ans2 == "???": if int(ans1) > sg: pass else: if ans1 < sg and ans2 < sg: if compare_wyckoffs(sg, ans1) or compare_wyckoffs(sg, ans2): pass else: check = True #output cif files for incorrect space groups if check is True: if check_struct_group(rand_crystal.struct, num, dim=2): pass else: t += " xxxxx" outstructs.append(rand_crystal.struct) outstrings.append(str("2D_Molecular_"+str(num)+".vasp")) print("\t"+str(num)+"\t|\t"+str(sg)+"\t|\t"+str(ans1)+"\t|\t"+str(ans2)+"\t|\t"+t) else: print("~~~~ Error: Could not generate layer group "+str(num)+" after "+t) failed.append(num) if slow != []: print("~~~~ The following layer groups took more than 60 seconds to generate:") for i in slow: print(" "+str(i)) if failed != []: print("~~~~ The following layer groups failed to generate:") for i in failed: print(" "+str(i))