def test_spherical(self): a = PointGroupAnalyzer(CH4) self.assertEqual(a.sch_symbol, "Td") self.assertEqual(len(a.get_pointgroup()), 24) a = PointGroupAnalyzer(PF6) self.assertEqual(a.sch_symbol, "Oh") self.assertEqual(len(a.get_pointgroup()), 48) m = Molecule.from_file(os.path.join(test_dir_mol, "c60.xyz")) a = PointGroupAnalyzer(m) self.assertEqual(a.sch_symbol, "Ih") cube_species = ["C", "C", "C", "C", "C", "C", "C", "C"] cube_coords = [ [0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [0, 1, 1], [1, 0, 1], [1, 1, 1], ] m = Molecule(cube_species, cube_coords) a = PointGroupAnalyzer(m, 0.1) self.assertEqual(a.sch_symbol, "Oh")
def test_linear(self): coords = [[0.000000, 0.000000, 0.000000], [0.000000, 0.000000, 1.08], [0, 0.000000, -1.08]] mol = Molecule(["C", "H", "H"], coords) a = PointGroupAnalyzer(mol) self.assertEqual(a.sch_symbol, "D*h") mol = Molecule(["C", "H", "N"], coords) a = PointGroupAnalyzer(mol) self.assertEqual(a.sch_symbol, "C*v")
def symmetry_information(self): """ Returns symmetry information of the molecule as for example its point group :return info: a dictionary with symmetry informations """ mol = mg.Molecule(self.atoms[:, 3], self.atoms[:, :3]) analyzer = PointGroupAnalyzer(mol) info = {'point group': analyzer.get_pointgroup()} return info
def test_asym_top(self): coords = [ [0.000000, 0.000000, 0.000000], [0.000000, 0.000000, 1.08], [1.026719, 0.000000, -0.363000], [-0.513360, -0.889165, -0.363000], [-0.513360, 0.889165, -0.363000], ] mol = Molecule(["C", "H", "F", "Br", "Cl"], coords) a = PointGroupAnalyzer(mol) self.assertEqual(a.sch_symbol, "C1") self.assertEqual(len(a.get_pointgroup()), 1) coords = [ [0.000000, 0.000000, 1.08], [1.026719, 0.000000, -0.363000], [-0.513360, -0.889165, -0.363000], [-0.513360, 0.889165, -0.363000], ] cs_mol = Molecule(["H", "F", "Cl", "Cl"], coords) a = PointGroupAnalyzer(cs_mol) self.assertEqual(a.sch_symbol, "Cs") self.assertEqual(len(a.get_pointgroup()), 2) a = PointGroupAnalyzer(C2H2F2Br2) self.assertEqual(a.sch_symbol, "Ci") self.assertEqual(len(a.get_pointgroup()), 2)
def __init__(self, mol=None, symmetrize=True, tm=Tol_matrix(prototype="molecular")): mo = None self.smile = None self.torsionlist = None if type(mol) == str: # Parse molecules: either file or molecule name tmp = mol.split(".") self.name = tmp[0] if len(tmp) > 1: # Load the molecule from the given file if tmp[-1] in ["xyz", "gjf", "g03", "json"]: if os.path.exists(mol): mo = Molecule.from_file(mol) else: raise NameError("{:s} is not a valid path".format(mol)) elif tmp[-1] == 'smi': self.smile = tmp[0] symbols, xyz, self.torsionlist = self.rdkit_mol_init(tmp[0]) mo = Molecule(symbols, xyz) symmetrize = False else: raise NameError("{:s} is not a supported format".format(tmp[-1])) else: # print('\nLoad the molecule {:s} from collections'.format(mol)) mo = molecule_collection[mol] elif hasattr(mol, "sites"): # pymatgen molecule self.name = str(mol.formula) mo = mol if mo is None: msg = "Could not create molecules from given input: {:s}".format(mol) raise NameError(msg) self.props = mo.site_properties if len(mo) > 1: if symmetrize: pga = PointGroupAnalyzer(mo) mo = pga.symmetrize_molecule()["sym_mol"] mo = self.add_site_props(mo) self.mol = mo self.tm = tm self.get_box() self.volume = self.box.volume self.get_radius() self.get_symbols() self.get_tols_matrix() xyz = self.mol.cart_coords self.reset_positions(xyz-self.get_center(xyz))
def test_symmetrize_molecule2(self): np.random.seed(77) distortion = np.random.randn(len(C2H2F2Br2), 3) / 20 dist_mol = Molecule(C2H2F2Br2.species, C2H2F2Br2.cart_coords + distortion) PA1 = PointGroupAnalyzer(C2H2F2Br2, tolerance=0.1) self.assertTrue(PA1.get_pointgroup().sch_symbol == "Ci") PA2 = PointGroupAnalyzer(dist_mol, tolerance=0.1) self.assertTrue(PA2.get_pointgroup().sch_symbol == "C1") eq = iterative_symmetrize(dist_mol, tolerance=0.3) PA3 = PointGroupAnalyzer(eq["sym_mol"], tolerance=0.1) self.assertTrue(PA3.get_pointgroup().sch_symbol == "Ci")
def xyz_to_sbu(path: str) -> Dict[str, Fragment]: """ [summary] Parameters ---------- path : str [description] Returns ------- Fragment [description] """ xyz = XYZ.from_file(path) names = get_xyz_names(path) sbu = {} for molecule, name in zip(xyz.all_molecules, names): dummies_idx = molecule.indices_from_symbol("X") symmetric_mol = Molecule([ "H", ] * len(dummies_idx), [molecule[idx].coords for idx in dummies_idx], charge=len(dummies_idx)) symmetry = PointGroupAnalyzer(symmetric_mol, tolerance=0.1) sbu[name] = Fragment(atoms=molecule, symmetry=symmetry, name=name) return sbu
def test_spherical(self): a = PointGroupAnalyzer(CH4) self.assertEqual(a.sch_symbol, "Td") self.assertEqual(len(a.get_pointgroup()), 24) a = PointGroupAnalyzer(PF6) self.assertEqual(a.sch_symbol, "Oh") self.assertEqual(len(a.get_pointgroup()), 48) m = Molecule.from_file(os.path.join(test_dir_mol, "c60.xyz")) a = PointGroupAnalyzer(m) self.assertEqual(a.sch_symbol, "Ih")
def test_dihedral(self): a = PointGroupAnalyzer(C2H4) self.assertEqual(a.sch_symbol, "D2h") self.assertEqual(len(a.get_pointgroup()), 8) a = PointGroupAnalyzer(BF3) self.assertEqual(a.sch_symbol, "D3h") self.assertEqual(len(a.get_pointgroup()), 12) m = Molecule.from_file(os.path.join(test_dir_mol, "b12h12.xyz")) a = PointGroupAnalyzer(m) self.assertEqual(a.sch_symbol, "Ih")
def test_get_symmetry_operations(self): coordinates = np.array([[0.5, 0.0, 0.0], [0.0, 0.5, 0.0], [0.5, 1.0, 0.0], [1.0, 0.5, 0.0]]) species = ['H'] * len(coordinates) molecule = Molecule(species, coordinates) so = PointGroupAnalyzer(molecule, 0.3).get_symmetry_operations() self.assertEqual(len(so), 16) # D4h contains 16 symmetry elements for o in so: self.assertEqual(isinstance(o, SymmOp), True)
def polyhedral_largest(frac_cord, struct): cart = struct.lattice.get_cartesian_coords(frac_cord) neigs_mess = struct.get_neighbors_in_shell(cart,0,10,include_index=True) neigs = sorted(neigs_mess,key=lambda i:(i[1],i[0].specie.symbol)) k=0 OK = False while not OK: sites = neigs[0:3+k] k+=1 inds = [i[2] for i in sites] dis = [round(i[1],1) for i in sites] species = [i[0].specie.symbol for i in sites] dis_ = [dis[0]] for i in dis[1:]: equ = False for j in dis_: if abs(i-j)<=0.2: dis_.append(j) equ = True break if not equ: dis_.append(i) pts = [i[0].coords for i in sites] try: poly = ConvexHull(pts) except QhullError: d3 = False continue else: if len(sites) == len(poly.vertices): poly_out = poly inds_out = inds dis_out = dis_ species_out = species d3 = True elif len(sites) != len(poly.vertices) and d3: OK = True if d3: poly={} molecule = Molecule(species_out,poly_out.points) pga = PointGroupAnalyzer(molecule) nsymmop = len(pga.symmops) #ind_dis = zip(inds_after_sym(inds_out,struct),dis_out) #ind_dis = sorted(ind_dis, key=lambda i:(i[1],i[0])) poly['frac_coord']=frac_cord poly['poly']=poly_out poly['species'] = species_out poly['site_ind']= inds_after_sym(inds_out,struct) poly['dist'] = dis_out poly['nsymmop'] = nsymmop poly['struct'] = struct return poly else: return
def get_point_symmetry(self): """ Returns the point group of the molecule using pymatgen :return: point symmetry label """ from pymatgen.core import Molecule from pymatgen.symmetry.analyzer import PointGroupAnalyzer pymatgen_mol = Molecule(self.get_symbols(), self.get_coordinates()) symm_group = PointGroupAnalyzer(pymatgen_mol, tolerance=0.1) return symm_group.sch_symbol
def get_symmetry(mol, already_oriented=False): ''' Return a list of SymmOps for a molecule's point symmetry already_oriented: whether or not the principle axes of mol are already reoriented ''' pga = PointGroupAnalyzer(mol) #Handle linear molecules if '*' in pga.sch_symbol: if already_oriented == False: #Reorient the molecule oriented_mol, P = reoriented_molecule(mol) pga = PointGroupAnalyzer(oriented_mol) pg = pga.get_pointgroup() symm_m = [] for op in pg: symm_m.append(op) #Add 12-fold and reflections in place of ininitesimal rotation for axis in [[1, 0, 0], [0, 1, 0], [0, 0, 1]]: op = SymmOp.from_rotation_and_translation(aa2matrix(axis, pi / 6), [0, 0, 0]) if pga.is_valid_op(op): symm_m.append(op) #Any molecule with infinitesimal symmetry is linear; #Thus, it possess mirror symmetry for any axis perpendicular #To the rotational axis. pymatgen does not add this symmetry #for all linear molecules - for example, hydrogen if axis == [1, 0, 0]: symm_m.append(SymmOp.from_xyz_string('x,-y,z')) symm_m.append(SymmOp.from_xyz_string('x,y,-z')) elif axis == [0, 1, 0]: symm_m.append(SymmOp.from_xyz_string('-x,y,z')) symm_m.append(SymmOp.from_xyz_string('x,y,-z')) elif axis == [0, 0, 1]: symm_m.append(SymmOp.from_xyz_string('-x,y,z')) symm_m.append(SymmOp.from_xyz_string('x,-y,z')) #Generate a full list of SymmOps for the molecule's pointgroup symm_m = generate_full_symmops(symm_m, 1e-3) break #Reorient the SymmOps into mol's original frame if not already_oriented: new = [] for op in symm_m: new.append(P.inverse * op * P) return new elif already_oriented: return symm_m #Handle nonlinear molecules else: pg = pga.get_pointgroup() symm_m = [] for op in pg: symm_m.append(op) return symm_m
def gen_cluster(system, numIons, sg=None, dimension=0, factor=1.0): #space group if sg: pass else: sg = random.choice(range(1, 57)) symbol, sg = get_symbol_and_number(sg, dimension) numIons0 = np.array(numIons) cluster = random_cluster(sg, system, numIons0, factor) if cluster.valid: comp = str(cluster.struct.composition) comp = comp.replace(" ", "") #outpath ='./' + comp + '.xyz' #print('out file name %s'%outpath) #cluster.molecule.to('xyz,', outpath) #cluster.to_file(filename = outpath, fmt='xyz') #mol=Molecule.from_file(outpath) mol = cluster.molecule xmax = mol.cart_coords[:, 0].max() xmin = mol.cart_coords[:, 0].min() ymax = mol.cart_coords[:, 1].max() ymin = mol.cart_coords[:, 1].min() zmax = mol.cart_coords[:, 2].max() zmin = mol.cart_coords[:, 2].min() lx = xmax - xmin ly = ymax - ymin lz = zmax - zmin _a, _b, _c = sorted([lx, ly, lz]) if _b / _a > 2 or _c / _b > 2 or _c / _a > 2: print("too long") return None ans = PointGroupAnalyzer(mol).sch_symbol print('Symmetry requested: {:d}({:s}), generated: {:s}'.format( sg, symbol, ans)) #a=b=c=np.max(mol.distance_matrix)+10 a = lx + 10 b = ly + 10 c = lz + 10 st = mol.get_boxed_structure(a, b, c) #st.to('POSCAR','rand_'+comp+'.vasp') #print(st) print('valid') return st else: print('cannot generate corresponding structure, retry it!') return None
def mol_get_rot_list0(mol, tol=1.e-5, log=sys.stdout): from pymatgen.symmetry.analyzer import PointGroupAnalyzer,\ generate_full_symmops from scipy.linalg import det analyzer = PointGroupAnalyzer(mol) print(" sch_symbol = {}".format(analyzer.sch_symbol), file=log) # Pick rotations only. symmops = [ o for o in analyzer.symmops if abs(det(o.rotation_matrix) - 1) < 1.e-5 ] symmops = generate_full_symmops(symmops, tol, max_recursion_depth=50) rot_list = [o.rotation_matrix for o in symmops] return rot_list
def structure_symmetry(): structs, fnames = read_structures() multi_structs(structs, fnames) for struct, fname in zip(structs, fnames): if isinstance(struct, Structure): sa = SpacegroupAnalyzer(struct) print("{:<20} : {}".format('File Name', fname)) print("{:<20} : {:<15}".format('Structure Type', 'periodicity')) print("{:<20} : {:<15}".format('Lattice Type', sa.get_lattice_type())) print("{:<20} : {:<15d}".format('Space Group ID', sa.get_space_group_number())) print("{:<20} : {:<15}".format('International Symbol', sa.get_space_group_symbol())) print("{:<20} : {:15}".format('Hall Symbol', sa.get_hall())) sepline() if isinstance(struct, Molecule): print("{:<20} : {}".format('File Name', fname)) pga = PointGroupAnalyzer(struct) print("{:<20} : {:<15}".format('Structure Type', 'non-periodicity')) print("{:<20} : {:<15}".format('International Symbol', str(pga.get_pointgroup()))) return True
def analyze(topology: Structure, skin: float = 5e-3) -> List[Molecule]: # containers for the output fragments = [] # we iterate over non-dummies dmat = topology.distance_matrix dummies = numpy.array(topology.indices_from_symbol("X")) not_dummies = numpy.array( [i for i in range(len(topology)) if i not in dummies]) # initialize and set tags tags = numpy.zeros(len(topology), dtype=int) tags[dummies] = dummies + 1 topology.add_site_property(property_name="tags", values=tags) # TODO : site properties # get the distances between centers and connections distances = dmat[not_dummies][:, dummies] coordinations = numpy.array(topology.atomic_numbers)[not_dummies] partitions = numpy.argsort(distances, axis=1) for center_idx, best_dummies in enumerate(partitions): coordination = coordinations[center_idx] if coordination < len(best_dummies): best_dummies = best_dummies[:coordination] cutoff = distances[center_idx][best_dummies].max() + skin # now extract the corresponding fragment fragment_center = topology.sites[not_dummies[center_idx]] fragment_sites = topology.get_neighbors(fragment_center, r=cutoff) # some topologies in the RCSR have a crazy size # we ignore them with the heat of a thousand suns assert len( fragment_sites) <= 12, "Fragment size larger than limit of 12." # store as molecule to use the point group analysis fragment = Molecule.from_sites( fragment_sites) #, charge=1, spin_multiplicity=1) # this is needed because X have no mass, leading to a # symmetrization error. fragment.replace_species({"X": "He"}) pg = PointGroupAnalyzer(fragment, tolerance=0.1) # from pymatgen.io.ase import AseAtomsAdaptor # view(AseAtomsAdaptor.get_atoms(fragment)) if pg.sch_symbol == "C1": raise ("NoSymm") fragment.replace_species({"He": "X"}) fragments.append(Fragment(atoms=fragment, symmetry=pg, name="slot")) return fragments
def get_random_structure(elem, num_atom, sg, dim, thickness=0): max_try = 100 factor = 1.0 i = 0 while i < max_try: random.seed(time.time() * 1e8) if sg == 0: sg = get_random_spg(dim) symbol, sg = get_symbol_and_number(sg, dim) if dim == 3: rand_crystal = random_crystal(sg, elem, num_atom, factor) elif dim == 2: rand_crystal = random_crystal_2D(sg, elem, num_atom, thickness, factor) elif dim == 1: rand_crystal = random_crystal_1D(sg, elem, num_atom, thickness, factor) if dim == 0: rand_crystal = random_cluster(sg, elem, num_atom, factor) if rand_crystal.valid: comp = str(rand_crystal.struct.composition) comp = comp.replace(" ", "") if dim > 0: outpath = comp + '.cif' CifWriter(rand_crystal.struct, symprec=0.1).write_file(filename=outpath) ans = get_symmetry_dataset(rand_crystal.spg_struct, symprec=1e-1)['international'] print('Symmetry requested: {:d}({:s}), generated: {:s}'.format( sg, symbol, ans)) return True else: outpath = comp + '.xyz' rand_crystal.to_file(filename=outpath, fmt='xyz') ans = PointGroupAnalyzer(rand_crystal.molecule).sch_symbol print('Symmetry requested: {:d}({:s}), generated: {:s}'.format( sg, symbol, ans)) return True i += 1
def polyhedral_cut(poly): out={} out['frac_coord']=poly['frac_coord'] dist_n = {} dis = [i for i in poly['dist']] for i in dis: if i not in dist_n: dist_n[i]=0 dist_n[i]+=1 dis = list(set(dis)) dis.sort() n=0 for i in range(len(dis)): if abs(dis[i]-dis[0])<=0.5: n+=dist_n[dis[i]] pts = poly['poly'].points[0:n] try: convex = ConvexHull(pts) verts = list(convex.vertices) except QhullError: return else: card=poly['struct'].lattice.get_cartesian_coords(poly['frac_coord']) pts_add = np.array(list(pts)+[card]) conv_ = ConvexHull(pts_add) if conv_.volume > convex.volume: return else: out['poly']=convex out['species'] = poly['species'][0:n] out['dist'] = poly['dist'][0:n] out['site_ind']=poly['site_ind'][0:n] mole = Molecule(out['species'],pts) pga = PointGroupAnalyzer(mole) nsymmop = len(pga.symmops) out['nsymmop']=nsymmop return out
def gen_cluster(system,numIons,sg=None,dimension=0,factor=1.0): #space group random.seed(int(time.time()*1e5)) if sg: pass else: sg=random.choice(range(1,57)) symbol, sg = get_symbol_and_number(sg, dimension) numIons0 = np.array(numIons) cluster = random_cluster(sg, system, numIons0, factor) if cluster.valid: comp = str(cluster.struct.composition) comp = comp.replace(" ", "") #outpath ='./' + comp + '.xyz' #print('out file name %s'%outpath) #cluster.molecule.to('xyz,', outpath) #cluster.to_file(filename = outpath, fmt='xyz') #mol=Molecule.from_file(outpath) mol=cluster.molecule ans = PointGroupAnalyzer(mol).sch_symbol print('Symmetry requested: {:d}({:s}), generated: {:s}'.format(sg, symbol, ans)) a=max(mol.cart_coords[:,0])-min(mol.cart_coords[:,0]) b=max(mol.cart_coords[:,1])-min(mol.cart_coords[:,1]) c=max(mol.cart_coords[:,2])-min(mol.cart_coords[:,2]) lx,ly,lz=sorted([a,b,c]) if lz/ly>2 or ly/lx>2 or lz/lx>2: print('too long') return None st=mol.get_boxed_structure(a+vac,b+vac,c+vac) #st.to('POSCAR','rand_'+comp+'.vasp') #print(st) print('valid') return st else: print('cannot generate corresponding structure, retry it!') return None
def getSymmetry(ids, xyz): mol = PointGroupAnalyzer(mg.Molecule(ids, xyz)) return mol.sch_symbol
def test_cyclic(self): a = PointGroupAnalyzer(H2O2) self.assertEqual(a.sch_symbol, "C2") self.assertEqual(len(a.get_pointgroup()), 2) a = PointGroupAnalyzer(H2O) self.assertEqual(a.sch_symbol, "C2v") self.assertEqual(len(a.get_pointgroup()), 4) a = PointGroupAnalyzer(NH3) self.assertEqual(a.sch_symbol, "C3v") self.assertEqual(len(a.get_pointgroup()), 6) cs2 = Molecule.from_file( os.path.join(test_dir_mol, "Carbon_Disulfide.xyz")) a = PointGroupAnalyzer(cs2, eigen_tolerance=0.001) self.assertEqual(a.sch_symbol, "C2v")
def orientation_in_wyckoff_position( mol, wyckoff_position, randomize=True, exact_orientation=False, already_oriented=False, allow_inversion=True, rtol=1e-2, ): """ Tests if a molecule meets the symmetry requirements of a Wyckoff position, and returns the valid orientations. Args: mol: a Molecule object. Orientation is arbitrary wyckoff_position: a pyxtal.symmetry.Wyckoff_position object randomize: whether or not to apply a random rotation consistent with the symmetry requirements exact_orientation: whether to only check compatibility for the provided orientation of the molecule. Used within general case for checking. If True, this function only returns True or False already_oriented: whether or not to reorient the principle axes when calling get_symmetry. Setting to True can remove redundancy, but is not necessary allow_inversion: whether or not to allow chiral molecules to be inverted. Should only be True if the chemical and biological properties of the mirror image are known to be suitable for the desired application Returns: a list of operations.Orientation objects which can be applied to the molecule while allowing it to satisfy the symmetry requirements of the Wyckoff position. If no orientations are found, returns False. """ # For single atoms, there are no constraints if len(mol) == 1: return [Orientation([[1, 0, 0], [0, 1, 0], [0, 0, 1]], degrees=2)] wyckoffs = wyckoff_position.ops w_symm = wyckoff_position.symmetry_m # Obtain the Wyckoff symmetry symm_w = w_symm[0] pga = PointGroupAnalyzer(mol) # Check exact orientation if exact_orientation is True: mo = deepcopy(mol) valid = True for op in symm_w: if not pga.is_valid_op(op): valid = False if valid is True: return True elif valid is False: return False # Obtain molecular symmetry, exact_orientation==False symm_m = get_symmetry(mol, already_oriented=already_oriented) # Store OperationAnalyzer objects for each molecular SymmOp chiral = True opa_m = [] for op_m in symm_m: opa = OperationAnalyzer(op_m) opa_m.append(opa) if opa.type == "rotoinversion": chiral = False elif opa.type == "inversion": chiral = False # If molecule is chiral and allow_inversion is False, # check if WP breaks symmetry if chiral is True: if allow_inversion is False: for op in wyckoffs: if np.linalg.det(op.rotation_matrix) < 0: printx( "Warning: cannot place chiral molecule in spagegroup", priority=2, ) return False # Store OperationAnalyzer objects for each Wyckoff symmetry SymmOp opa_w = [] for op_w in symm_w: opa_w.append(OperationAnalyzer(op_w)) # Check for constraints from the Wyckoff symmetry... # If we find ANY two constraints (SymmOps with unique axes), the molecule's # point group MUST contain SymmOps which can be aligned to these particular # constraints. However, there may be multiple compatible orientations of the # molecule consistent with these constraints constraint1 = None constraint2 = None for i, op_w in enumerate(symm_w): if opa_w[i].axis is not None: constraint1 = opa_w[i] for j, op_w in enumerate(symm_w): if opa_w[j].axis is not None: dot = np.dot(opa_w[i].axis, opa_w[j].axis) if (not np.isclose(dot, 1, rtol=rtol)) and (not np.isclose( dot, -1, rtol=rtol)): constraint2 = opa_w[j] break break # Indirectly store the angle between the constraint axes if constraint1 is not None and constraint2 is not None: dot_w = np.dot(constraint1.axis, constraint2.axis) # Generate 1st consistent molecular constraints constraints_m = [] if constraint1 is not None: for i, opa1 in enumerate(opa_m): if opa1.is_conjugate(constraint1): constraints_m.append([opa1, []]) # Generate 2nd constraint in opposite direction extra = deepcopy(opa1) extra.axis = [ opa1.axis[0] * -1, opa1.axis[1] * -1, opa1.axis[2] * -1 ] constraints_m.append([extra, []]) # Remove redundancy for the first constraints list_i = list(range(len(constraints_m))) list_j = list(range(len(constraints_m))) copy = deepcopy(constraints_m) for i, c1 in enumerate(copy): if i in list_i: for j, c2 in enumerate(copy): if i > j and j in list_j and j in list_i: # Check if axes are colinear if np.isclose(np.dot(c1[0].axis, c2[0].axis), 1, rtol=rtol): list_i.remove(j) list_j.remove(j) # Check if axes are symmetrically equivalent else: cond1 = False # cond2 = False for opa in opa_m: if opa.type == "rotation": op = opa.op if np.isclose( np.dot(op.operate(c1[0].axis), c2[0].axis), 1, rtol=5 * rtol, ): cond1 = True break if cond1 is True: # or cond2 is True: list_i.remove(j) list_j.remove(j) c_m = deepcopy(constraints_m) constraints_m = [] for i in list_i: constraints_m.append(c_m[i]) # Generate 2nd consistent molecular constraints valid = list(range(len(constraints_m))) if constraint2 is not None: for i, c in enumerate(constraints_m): opa1 = c[0] for j, opa2 in enumerate(opa_m): if opa2.is_conjugate(constraint2): dot_m = np.dot(opa1.axis, opa2.axis) # Ensure that the angles are equal if abs(dot_m - dot_w) < 0.02 or abs(dot_m + dot_w) < 0.02: constraints_m[i][1].append(opa2) # Generate 2nd constraint in opposite direction extra = deepcopy(opa2) extra.axis = [ opa2.axis[0] * -1, opa2.axis[1] * -1, opa2.axis[2] * -1, ] constraints_m[i][1].append(extra) # If no consistent constraints are found, remove first constraint if constraints_m[i][1] == []: valid.remove(i) copy = deepcopy(constraints_m) constraints_m = [] for i in valid: constraints_m.append(copy[i]) # Generate orientations consistent with the possible constraints orientations = [] # Loop over molecular constraint sets for c1 in constraints_m: v1 = c1[0].axis v2 = constraint1.axis T = rotate_vector(v1, v2) # If there is only one constraint if c1[1] == []: o = Orientation(T, degrees=1, axis=constraint1.axis) orientations.append(o) else: # Loop over second molecular constraints for opa in c1[1]: phi = angle(constraint1.axis, constraint2.axis) phi2 = angle(constraint1.axis, np.dot(T, opa.axis)) if np.isclose(phi, phi2, rtol=rtol): r = np.sin(phi) c = np.linalg.norm(np.dot(T, opa.axis) - constraint2.axis) theta = np.arccos(1 - (c**2) / (2 * (r**2))) # R = aa2matrix(constraint1.axis, theta) R = Rotation.from_rotvec(theta * constraint1.axis).as_matrix() T2 = np.dot(R, T) a = angle(np.dot(T2, opa.axis), constraint2.axis) if not np.isclose(a, 0, rtol=rtol): T2 = np.dot(np.linalg.inv(R), T) o = Orientation(T2, degrees=0) orientations.append(o) # Ensure the identity orientation is checked if no constraints are found if constraints_m == []: o = Orientation(np.identity(3), degrees=2) orientations.append(o) # Remove redundancy from orientations list_i = list(range(len(orientations))) list_j = list(range(len(orientations))) for i, o1 in enumerate(orientations): if i in list_i: for j, o2 in enumerate(orientations): if i > j and j in list_j and j in list_i: # m1 = o1.get_matrix(angle=0) # m2 = o2.get_matrix(angle=0) m1 = o1.matrix m2 = o2.matrix new_op = SymmOp.from_rotation_and_translation( np.dot(m2, np.linalg.inv(m1)), [0, 0, 0]) P = SymmOp.from_rotation_and_translation( np.linalg.inv(m1), [0, 0, 0]) old_op = P * new_op * P.inverse if pga.is_valid_op(old_op): list_i.remove(j) list_j.remove(j) #copies = deepcopy(orientations) orientations_new = [] for i in list_i: orientations_new.append(orientations[i]) #Check each of the found orientations for consistency with the Wyckoff pos. #If consistent, put into an array of valid orientations allowed = [] for o in orientations_new: if randomize is True: op = o.get_op() elif randomize is False: op = o.get_op() #do not change mo = deepcopy(mol) mo.apply_operation(op) if orientation_in_wyckoff_position(mo, wyckoff_position, exact_orientation=True, randomize=False, allow_inversion=allow_inversion): allowed.append(o) if allowed == []: return False else: return allowed
def get_symmetry(mol, already_oriented=False): """ Return a molecule's point symmetry. Note: for linear molecules, infinitessimal rotations are treated as 6-fold rotations, which works for 3d and 2d point groups. Args: mol: a Molecule object already_oriented: whether or not the principle axes of mol are already reoriented. Can save time if True, but is not required. Returns: a list of SymmOp objects which leave the molecule unchanged when applied """ # For single atoms, we cannot represent the point group using a list of operations if len(mol) == 1: return [] pga = PointGroupAnalyzer(mol) # Handle linear molecules if "*" in pga.sch_symbol: if not already_oriented: # Reorient the molecule oriented_mol, P = reoriented_molecule(mol) pga = PointGroupAnalyzer(oriented_mol) pg = pga.get_pointgroup() symm_m = [] for op in pg: symm_m.append(op) # Add 12-fold and reflections in place of ininitesimal rotation for axis in [[1, 0, 0], [0, 1, 0], [0, 0, 1]]: # op = SymmOp.from_rotation_and_translation(aa2matrix(axis, np.pi/6), [0,0,0]) m1 = Rotation.from_rotvec(np.pi / 6 * axis).as_matrix() op = SymmOp.from_rotation_and_translation(m1, [0, 0, 0]) if pga.is_valid_op(op): symm_m.append(op) # Any molecule with infinitesimal symmetry is linear; # Thus, it possess mirror symmetry for any axis perpendicular # To the rotational axis. pymatgen does not add this symmetry # for all linear molecules - for example, hydrogen if axis == [1, 0, 0]: symm_m.append(SymmOp.from_xyz_string("x,-y,z")) symm_m.append(SymmOp.from_xyz_string("x,y,-z")) #r = SymmOp.from_xyz_string("-x,y,-z") elif axis == [0, 1, 0]: symm_m.append(SymmOp.from_xyz_string("-x,y,z")) symm_m.append(SymmOp.from_xyz_string("x,y,-z")) #r = SymmOp.from_xyz_string("-x,-y,z") elif axis == [0, 0, 1]: symm_m.append(SymmOp.from_xyz_string("-x,y,z")) symm_m.append(SymmOp.from_xyz_string("x,-y,z")) #r = SymmOp.from_xyz_string("x,-y,-z") # Generate a full list of SymmOps for the molecule's pointgroup symm_m = generate_full_symmops(symm_m, 1e-3) break # Reorient the SymmOps into mol's original frame if not already_oriented: new = [] for op in symm_m: new.append(P.inverse * op * P) return new elif already_oriented: return symm_m # Handle nonlinear molecules else: pg = pga.get_pointgroup() symm_m = [] for op in pg: symm_m.append(op) return symm_m
from __future__ import print_function import unittest import pickle with open('mol.pickle', 'rb') as f: mol = pickle.load(f) from pymatgen.symmetry.analyzer import PointGroupAnalyzer analyzer = PointGroupAnalyzer(mol) print(" sch_symbol = ", analyzer.sch_symbol) from pymatgen.symmetry.analyzer import generate_full_symmops symmops = generate_full_symmops(analyzer.symmops, tol=1.e-7) print(" num symmops = ", len(symmops)) class KnowValues(unittest.TestCase): def test_PointGroupAnalyzer(self): self.assertTrue(analyzer.sch_symbol == 'D4h') self.assertTrue(len(symmops) == 16) if __name__ == "__main__": print("Tests for pymatgen.") unittest.main()
def test_tricky(self): m = Molecule.from_file(os.path.join(test_dir_mol, "dh.xyz")) a = PointGroupAnalyzer(m, 0.1) self.assertEqual(a.sch_symbol, "D*h")
def parse_symmetry(pos): mol = Molecule(["C"] * len(pos), pos) pga = PointGroupAnalyzer(mol) return pga.sch_symbol
def generate_doc(self, dir_name, qcinput_files, qcoutput_files, multirun): try: fullpath = os.path.abspath(dir_name) d = jsanitize(self.additional_fields, strict=True) d["schema"] = { "code": "atomate", "version": QChemDrone.__version__ } d["dir_name"] = fullpath if multirun: d["calcs_reversed"] = self.process_qchem_multirun( dir_name, qcinput_files, qcoutput_files) else: d["calcs_reversed"] = [ self.process_qchemrun(dir_name, taskname, qcinput_files.get(taskname), output_filename) for taskname, output_filename in qcoutput_files.items() ] # reverse the calculations data order so newest calc is first d["calcs_reversed"].reverse() d_calc_init = d["calcs_reversed"][-1] d_calc_final = d["calcs_reversed"][0] d["input"] = { "initial_molecule": d_calc_init["initial_molecule"], "job_type": d_calc_init["input"]["rem"]["job_type"] } d["output"] = { "initial_molecule": d_calc_final["initial_molecule"], "job_type": d_calc_final["input"]["rem"]["job_type"] } if d["output"]["job_type"] == "opt" or d["output"][ "job_type"] == "optimization": d["output"]["optimized_molecule"] = d_calc_final[ "molecule_from_optimized_geometry"] d["output"]["final_energy"] = d_calc_final["final_energy"] if d_calc_final["opt_constraint"]: d["output"]["constraint"] = [ d_calc_final["opt_constraint"][0], float(d_calc_final["opt_constraint"][6]) ] if d["output"]["job_type"] == "freq" or d["output"][ "job_type"] == "frequency": d["output"]["frequencies"] = d_calc_final["frequencies"] d["output"]["enthalpy"] = d_calc_final["enthalpy"] d["output"]["entropy"] = d_calc_final["entropy"] if d["input"]["job_type"] == "opt" or d["input"][ "job_type"] == "optimization": d["output"]["optimized_molecule"] = d_calc_final[ "initial_molecule"] d["output"]["final_energy"] = d["calcs_reversed"][1][ "final_energy"] if "special_run_type" in d: if d["special_run_type"] == "frequency_flattener": d["num_frequencies_flattened"] = (len(qcinput_files) / 2) - 1 total_cputime = 0.0 total_walltime = 0.0 nan_found = False for calc in d["calcs_reversed"]: if calc["walltime"] != "nan": total_walltime += calc["walltime"] else: nan_found = True if calc["cputime"] != "nan": total_cputime += calc["cputime"] else: nan_found = True if nan_found: d["walltime"] = "nan" d["cputime"] = "nan" else: d["walltime"] = total_walltime d["cputime"] = total_cputime comp = d["output"]["initial_molecule"].composition d["formula_pretty"] = comp.reduced_formula d["formula_anonymous"] = comp.anonymized_formula d["chemsys"] = "-".join(sorted(set(d_calc_final["species"]))) d["pointgroup"] = PointGroupAnalyzer( d["output"]["initial_molecule"]).sch_symbol bb = BabelMolAdaptor(d["output"]["initial_molecule"]) pbmol = bb.pybel_mol smiles = pbmol.write(str("smi")).split()[0] d["smiles"] = smiles d["state"] = "successful" if d_calc_final[ "completion"] else "unsuccessful" d["last_updated"] = datetime.datetime.utcnow() return d except Exception: logger.error(traceback.format_exc()) logger.error("Error in " + os.path.abspath(dir_name) + ".\n" + traceback.format_exc()) raise
def _GetSiteEnvironments(cls, coord, cell, SiteTypes, cutoff, pbc, get_permutations=True, eigen_tol=1e-5): """Extract local environments from primitive cell Parameters ---------- coord : n x 3 list or numpy array of scaled positions. n is the number of atom. cell : 3 x 3 list or numpy array SiteTypes : n list of string. String must be S or A followed by a number. S indicates a spectator sites and A indicates a active sites. cutoff : float. cutoff distance in angstrom for collecting local environment. pbc : list of boolean. Periodic boundary condition get_permutations : boolean. Whether to find permutatated neighbor list or not. eigen_tol : tolerance for eigenanalysis of point group analysis in pymatgen. Returns ------ list of local_env : list of local_env class """ #%% Check error assert isinstance(coord, (list, np.ndarray)) assert isinstance(cell, (list, np.ndarray)) assert len(coord) == len(SiteTypes) #%% Initialize # TODO: Technically, user doesn't even have to supply site index, because # pymatgen can be used to automatically categorize sites.. coord = np.mod(coord, 1) pbc = np.array(pbc) #%% Map sites to other elements.. # TODO: Available pymatgne functions are very limited when DummySpecie is # involved. This may be perhaps fixed in the future. Until then, we # simply bypass this by mapping site to an element # Find available atomic number to map site to it availableAN = [i + 1 for i in reversed(range(0, 118))] # Organize Symbols and record mapping symbols = [] site_idxs = [] SiteSymMap = {} # mapping SymSiteMap = {} for i, SiteType in enumerate(SiteTypes): if SiteType not in SiteSymMap: symbol = Element.from_Z(availableAN.pop()) SiteSymMap[SiteType] = symbol SymSiteMap[symbol] = SiteType else: symbol = SiteSymMap[SiteType] symbols.append(symbol) if 'A' in SiteType: site_idxs.append(i) #%% Get local environments of each site # Find neighbors and permutations using pymatgen lattice = Lattice(cell) structure = Structure(lattice, symbols, coord) neighbors = structure.get_all_neighbors(cutoff, include_index=True) site_envs = [] for site_idx in site_idxs: local_env_sym = [symbols[site_idx]] local_env_xyz = [structure[site_idx].coords] local_env_dist = [0.0] local_env_sitemap = [site_idx] for n in neighbors[site_idx]: # if PBC condition is fulfilled.. c = np.around(n[0].frac_coords, 10) withinPBC = np.logical_and(0 <= c, c < 1) if np.all(withinPBC[~pbc]): local_env_xyz.append(n[0].coords) local_env_sym.append(n[0].specie) local_env_dist.append(n[1]) local_env_sitemap.append(n[2]) local_env_xyz = np.subtract(local_env_xyz, np.mean(local_env_xyz, 0)) perm = [] if get_permutations: finder = PointGroupAnalyzer(Molecule(local_env_sym, local_env_xyz), eigen_tolerance=eigen_tol) pg = finder.get_pointgroup() for i, op in enumerate(pg): newpos = op.operate_multi(local_env_xyz) perm.append( np.argmin(cdist(local_env_xyz, newpos), axis=1).tolist()) site_env = { 'pos': local_env_xyz, 'sitetypes': [SymSiteMap[s] for s in local_env_sym], 'env2config': local_env_sitemap, 'permutations': perm, 'dist': local_env_dist } site_envs.append(site_env) return site_envs
def generate_doc(self, dir_name, qcinput_files, qcoutput_files, multirun): try: fullpath = os.path.abspath(dir_name) d = jsanitize(self.additional_fields, strict=True) d["schema"] = { "code": "atomate", "version": QChemDrone.__version__ } d["dir_name"] = fullpath # If a saved "orig" input file is present, parse it incase the error handler made changes # to the initial input molecule or rem params, which we might want to filter for later if len(qcinput_files) > len(qcoutput_files): orig_input = QCInput.from_file( os.path.join(dir_name, qcinput_files.pop("orig"))) d["orig"] = {} d["orig"]["molecule"] = orig_input.molecule.as_dict() d["orig"]["molecule"]["charge"] = int( d["orig"]["molecule"]["charge"]) d["orig"]["rem"] = orig_input.rem d["orig"]["opt"] = orig_input.opt d["orig"]["pcm"] = orig_input.pcm d["orig"]["solvent"] = orig_input.solvent d["orig"]["smx"] = orig_input.smx if multirun: d["calcs_reversed"] = self.process_qchem_multirun( dir_name, qcinput_files, qcoutput_files) else: d["calcs_reversed"] = [ self.process_qchemrun(dir_name, taskname, qcinput_files.get(taskname), output_filename) for taskname, output_filename in qcoutput_files.items() ] # reverse the calculations data order so newest calc is first d["calcs_reversed"].reverse() d["structure_change"] = [] d["warnings"] = {} for entry in d["calcs_reversed"]: if ("structure_change" in entry and "structure_change" not in d["warnings"]): if entry["structure_change"] != "no_change": d["warnings"]["structure_change"] = True if "structure_change" in entry: d["structure_change"].append(entry["structure_change"]) for key in entry["warnings"]: if key not in d["warnings"]: d["warnings"][key] = True d_calc_init = d["calcs_reversed"][-1] d_calc_final = d["calcs_reversed"][0] d["input"] = { "initial_molecule": d_calc_init["initial_molecule"], "job_type": d_calc_init["input"]["rem"]["job_type"], } d["output"] = { "initial_molecule": d_calc_final["initial_molecule"], "job_type": d_calc_final["input"]["rem"]["job_type"], "mulliken": d_calc_final["Mulliken"][-1], } if "RESP" in d_calc_final: d["output"]["resp"] = d_calc_final["RESP"][-1] elif "ESP" in d_calc_final: d["output"]["esp"] = d_calc_final["ESP"][-1] if (d["output"]["job_type"] == "opt" or d["output"]["job_type"] == "optimization"): if "molecule_from_optimized_geometry" in d_calc_final: d["output"]["optimized_molecule"] = d_calc_final[ "molecule_from_optimized_geometry"] d["output"]["final_energy"] = d_calc_final["final_energy"] else: d["output"]["final_energy"] = "unstable" if d_calc_final["opt_constraint"]: d["output"]["constraint"] = [ d_calc_final["opt_constraint"][0], float(d_calc_final["opt_constraint"][6]), ] if (d["output"]["job_type"] == "freq" or d["output"]["job_type"] == "frequency"): d["output"]["frequencies"] = d_calc_final["frequencies"] d["output"]["enthalpy"] = d_calc_final["total_enthalpy"] d["output"]["entropy"] = d_calc_final["total_entropy"] if (d["input"]["job_type"] == "opt" or d["input"]["job_type"] == "optimization"): d["output"]["optimized_molecule"] = d_calc_final[ "initial_molecule"] d["output"]["final_energy"] = d["calcs_reversed"][1][ "final_energy"] opt_trajectory = [] calcs = copy.deepcopy(d["calcs_reversed"]) calcs.reverse() for calc in calcs: job_type = calc["input"]["rem"]["job_type"] if job_type == "opt" or job_type == "optimization": for ii, geom in enumerate(calc["geometries"]): site_properties = {"Mulliken": calc["Mulliken"][ii]} if "RESP" in calc: site_properties["RESP"] = calc["RESP"][ii] mol = Molecule( species=calc["species"], coords=geom, charge=calc["charge"], spin_multiplicity=calc["multiplicity"], site_properties=site_properties, ) traj_entry = {"molecule": mol} traj_entry["energy"] = calc["energy_trajectory"][ii] opt_trajectory.append(traj_entry) if opt_trajectory != []: d["opt_trajectory"] = opt_trajectory if "final_energy" not in d["output"]: if d_calc_final["final_energy"] != None: d["output"]["final_energy"] = d_calc_final["final_energy"] else: d["output"]["final_energy"] = d_calc_final["SCF"][-1][-1][ 0] if d_calc_final["completion"]: total_cputime = 0.0 total_walltime = 0.0 for calc in d["calcs_reversed"]: if calc["walltime"] is not None: total_walltime += calc["walltime"] if calc["cputime"] is not None: total_cputime += calc["cputime"] d["walltime"] = total_walltime d["cputime"] = total_cputime else: d["walltime"] = None d["cputime"] = None comp = d["output"]["initial_molecule"].composition d["formula_pretty"] = comp.reduced_formula d["formula_anonymous"] = comp.anonymized_formula d["formula_alphabetical"] = comp.alphabetical_formula d["chemsys"] = "-".join(sorted(set(d_calc_final["species"]))) if d_calc_final["point_group"] != None: d["pointgroup"] = d_calc_final["point_group"] else: try: d["pointgroup"] = PointGroupAnalyzer( d["output"]["initial_molecule"]).sch_symbol except ValueError: d["pointgroup"] = "PGA_error" bb = BabelMolAdaptor(d["output"]["initial_molecule"]) pbmol = bb.pybel_mol smiles = pbmol.write("smi").split()[0] d["smiles"] = smiles d["state"] = "successful" if d_calc_final[ "completion"] else "unsuccessful" if "special_run_type" in d: if d["special_run_type"] == "frequency_flattener": if d["state"] == "successful": orig_num_neg_freq = sum( 1 for freq in d["calcs_reversed"][-2]["frequencies"] if freq < 0) orig_energy = d_calc_init["final_energy"] final_num_neg_freq = sum( 1 for freq in d_calc_final["frequencies"] if freq < 0) final_energy = d["calcs_reversed"][1]["final_energy"] d["num_frequencies_flattened"] = (orig_num_neg_freq - final_num_neg_freq) if final_num_neg_freq > 0: # If a negative frequency remains, # and it's too large to ignore, if (final_num_neg_freq > 1 or abs( d["output"]["frequencies"][0]) >= 15.0): d["state"] = "unsuccessful" # then the flattening was unsuccessful if final_energy > orig_energy: d["warnings"]["energy_increased"] = True d["last_updated"] = datetime.datetime.utcnow() return d except Exception: logger.error(traceback.format_exc()) logger.error("Error in " + os.path.abspath(dir_name) + ".\n" + traceback.format_exc()) raise