Exemple #1
0
 def search_position(self):
     """
     Sometimes, the initial posiition is not the proper generator
     Needs to find the proper generator
     """
     if self.wp.index > 0:
         pos = self.position
         coords = apply_ops(pos, Group(self.wp.number, self.wp.dim)[0])
         for coord in coords:
             ans = apply_ops(coord, [self.wp.ops[0]])[0]
             diff = coord - ans
             diff -= np.floor(diff)
             if np.sum(diff**2) < 1e-4:
                 self.position = coord - np.floor(coord)
                 break
Exemple #2
0
    def subgroup_by_splitter(self, splitter, eps=0.05):
        lat1 = np.dot(splitter.R[:3, :3].T, self.lattice.matrix)
        multiples = np.linalg.det(splitter.R[:3, :3])
        split_sites = []
        for i, site in enumerate(self.atom_sites):
            pos = site.position
            for ops1, ops2 in zip(splitter.G2_orbits[i], splitter.H_orbits[i]):
                pos0 = apply_ops(pos, ops1)[0]
                pos0 -= np.floor(pos0)
                pos0 += eps * (np.random.sample(3) - 0.5)
                wp, _ = Wyckoff_position.from_symops(ops2,
                                                     group=splitter.H.number,
                                                     permutation=False)
                split_sites.append(atom_site(wp, pos0, site.specie))
        new_struc = deepcopy(self)
        new_struc.group = splitter.H
        lattice = Lattice.from_matrix(lat1, ltype=new_struc.group.lattice_type)
        new_struc.lattice = lattice.mutate(degree=0.01, frozen=True)
        new_struc.atom_sites = split_sites
        new_struc.numIons = [
            int(multiples * numIon) for numIon in self.numIons
        ]
        new_struc.source = 'Wyckoff Split'

        return new_struc
Exemple #3
0
 def update(self, pos=None):
     """
     Used to generate coords from self.position
     """
     if pos is None:
         pos = self.position
     self.coords = apply_ops(pos, self.wp)
     self.position = self.coords[0]
Exemple #4
0
    def _find_gen_wyckoff_in_subgroup(self, groups=None):
        """
        Symmetry transformation
        group -> subgroup
        At the moment we only consider 
        for multiplicity 2: P-1, P21, P2, Pm and Pc
        to add: for multiplicity 4: P21/c, P212121
        Permutation is allowed
        """
        from pyxtal.symmetry import Wyckoff_position

        pos = self.position
        wp0 = self.wp
        pos0 = apply_ops(pos, wp0)

        if len(wp0) == 2:
            if self.diag:  # P21/n -> Pn
                #print("----------P21n----------")
                wp1 = Wyckoff_position.from_group_and_index(7, 0)
                wp1.diagonalize_symops()
                axes = [[0, 1, 2], [2, 1, 0]]
                for ax in axes:
                    pos1 = apply_ops(pos[ax], wp1)
                    diff = (pos1[:, ax] - pos0)[1]
                    diff -= np.floor(diff)
                    if len(diff[diff == 0]) >= 2:
                        #return wp1, ax, pos[ax] - 0.5*diff
                        return Wyckoff_position.from_group_and_index(
                            7, 0), ax, pos[ax] - 0.5 * diff
            else:
                if groups is None:
                    groups = [4, 3, 6, 7, 2]
                if 15 < wp0.number < 71:
                    axes = [[0, 1, 2], [0, 2, 1], [1, 0, 2], [2, 1, 0]]
                elif wp0.number < 15:
                    axes = [[0, 1, 2], [2, 1, 0]]
                for group in groups:
                    wp1 = Wyckoff_position.from_group_and_index(group, 0)
                    for ax in axes:
                        pos1 = apply_ops(pos[ax], wp1)
                        diff = (pos1[:, ax] - pos0)[1]
                        diff -= np.floor(diff)
                        if len(diff[diff == 0]) >= 2:
                            return wp1, ax, pos[ax] - 0.5 * diff
Exemple #5
0
    def get_centers(self, absolute=False):
        """
        Returns the fractional coordinates for the center of mass for each molecule in
        the Wyckoff position

        Returns:
            A numpy array of fractional 3-vectors
        """
        centers = apply_ops(self.position, self.wp.ops)
        # centers1 = filtered_coords(centers0, self.PBC)
        if absolute is False:
            return centers
        else:
            return np.dot(centers, self.lattice.matrix)
Exemple #6
0
    def subgroup_by_splitter(self, splitter, eps=0.05):
        """
        transform the crystal to subgroup symmetry from a splitter object
        """
        lat1 = np.dot(splitter.R[:3, :3].T, self.lattice.matrix)
        multiples = np.linalg.det(splitter.R[:3, :3])
        new_struc = deepcopy(self)
        new_struc.group = splitter.H
        lattice = Lattice.from_matrix(lat1, ltype=new_struc.group.lattice_type)
        lattice = lattice.mutate(degree=eps, frozen=True)

        h = splitter.H.number
        split_sites = []
        if self.molecular:
            # below only works when the cell does not change
            for i, site in enumerate(self.mol_sites):
                pos = site.position
                mol = site.molecule
                ori = site.orientation
                coord0 = mol.mol.cart_coords.dot(ori.matrix.T)
                wp1 = site.wp
                ori.reset_matrix(np.eye(3))
                for ops1, ops2 in zip(splitter.G2_orbits[i],
                                      splitter.H_orbits[i]):
                    #reset molecule
                    coord1 = np.dot(coord0, ops1[0].affine_matrix[:3, :3].T)
                    _mol = mol.copy()
                    _mol.reset_positions(coord1)

                    pos0 = apply_ops(pos, ops1)[0]
                    pos0 -= np.floor(pos0)
                    pos0 += eps * (np.random.sample(3) - 0.5)

                    wp, _ = Wyckoff_position.from_symops(ops2,
                                                         h,
                                                         permutation=False)
                    split_sites.append(mol_site(_mol, pos0, ori, wp, lattice))
            new_struc.mol_sites = split_sites
            new_struc.numMols = [
                int(multiples * numMol) for numMol in self.numMols
            ]

        else:
            for i, site in enumerate(self.atom_sites):
                pos = site.position
                for ops1, ops2 in zip(splitter.G2_orbits[i],
                                      splitter.H_orbits[i]):
                    pos0 = apply_ops(pos, ops1)[0]
                    pos0 -= np.floor(pos0)
                    pos0 += eps * (np.random.sample(3) - 0.5)
                    wp, _ = Wyckoff_position.from_symops(ops2,
                                                         h,
                                                         permutation=False)
                    split_sites.append(atom_site(wp, pos0, site.specie))

            new_struc.atom_sites = split_sites
            new_struc.numIons = [
                int(multiples * numIon) for numIon in self.numIons
            ]
        new_struc.lattice = lattice
        new_struc.source = 'Wyckoff Split'

        return new_struc
Exemple #7
0
    def symmetrize(self, splitter, solution, disp):
        """
        For a given solution, search for the possbile supergroup structure

        Args: 
            splitter: splitter object to specify the relation between G and H
            disp: an overall shift from H to G, None or 3 vector
            d_tol: the tolerance in angstrom

        Returns:
            coords_G1
            coords_G2
            coords_H1
            elements
            mults
        """

        atom_sites_H = self.struc.atom_sites
        coords_G1 = []  # position in G
        coords_G2 = []  # position in G on the subgroup bais
        coords_H1 = []  # position in H
        elements = []
        mults = []
        inv_R = splitter.inv_R  # inverse coordinate transformation
        # wp1 stores the wyckoff position object of ['2c', '6h', '12i']
        for i, wp1 in enumerate(splitter.wp1_lists):

            if len(splitter.wp2_lists[i]) == 1:
                # symmetry info
                ops_H = splitter.H_orbits[i][0]  # ops for H
                ops_G2 = splitter.G2_orbits[i][0]  # ops for G2

                # refine coord1 to find the best match on coord2
                coord = atom_sites_H[solution[i][0]].position
                coord0s = apply_ops(coord, ops_H)  # possible coords in H
                dists = []
                for coord0 in coord0s:
                    coord2 = coord0.copy()
                    coord2 += disp
                    coord1 = apply_ops(coord2, ops_G2)[0]  # coord in G
                    dist = coord1 - coord2
                    dist -= np.round(dist)
                    dist = np.dot(dist, self.cell)
                    dists.append(np.linalg.norm(dist))
                min_ID = np.argmin(np.array(dists))

                coord2 = coord0s[min_ID].copy()
                coord1 = ops_G2[0].operate(coord2 + disp)
                if round(np.trace(ops_G2[0].rotation_matrix)) in [1, 2]:

                    def fun(x, pt, ref, op):
                        pt[0] = x[0]
                        y = op.operate(pt)
                        diff = y - ref
                        diff -= np.round(diff)
                        diff = np.dot(diff, self.cell)
                        return np.linalg.norm(diff)

                    # optimize the distance by changing coord1
                    res = minimize(fun,
                                   coord1[0],
                                   args=(coord1, coord2, ops_G2[0]),
                                   method='Nelder-Mead',
                                   options={'maxiter': 20})
                    coord1[0] = res.x[0]
                    coord1 = ops_G2[0].operate(coord1)

                coords_G1.append(
                    np.dot(inv_R[:3, :3], coord1).T + inv_R[:3, 3].T)
                #coords_G1.append(coord1)
                coords_G2.append(coord1)
                coords_H1.append(coord2)
                elements.append(splitter.elements[i])
                mults.append(len(ops_G2))

            else:
                # symmetry operations
                ops_H1 = splitter.H_orbits[i][0]
                ops_G22 = splitter.G2_orbits[i][1]

                coord1 = atom_sites_H[solution[i][0]].position.copy()
                coord2 = atom_sites_H[solution[i][1]].position.copy()
                # refine coord1 to find the best match on coord2
                coord11 = coord1 + disp
                coord22 = coord2 + disp
                coords11 = apply_ops(coord11, ops_H1)

                # transform coords1 by symmetry operation
                op = ops_G22[0]
                for m, coord11 in enumerate(coords11):
                    coords11[m] = op.operate(coord11)
                tmp, dist = get_best_match(coords11, coord22, self.cell)

                # recover the original position
                inv_op = get_inverse(op)
                coord1 = inv_op.operate(tmp)
                coord1 -= disp
                d = coord22 - tmp
                d -= np.round(d)

                coord22 -= d / 2  #final coord2 after disp
                # recover the displaced position
                coord11 = inv_op.operate(coord22)

                coords_G1.append(
                    np.dot(inv_R[:3, :3], coord11).T + inv_R[:3, 3].T)
                coords_G2.append(coord11)
                coords_G2.append(coord22)
                coords_H1.append(coord1)
                coords_H1.append(coord2)
                elements.extend([splitter.elements[i]] * 2)
                mults.append(len(ops_H1))
                mults.append(len(ops_G22))
        return coords_G1, coords_G2, coords_H1, elements, mults
Exemple #8
0
    def symmetrize_dist(self,
                        splitter,
                        solution,
                        disp=None,
                        mask=None,
                        d_tol=1.2):
        """
        For a given solution, search for the possbile supergroup structure

        Args: 
            splitter: splitter object to specify the relation between G and H
            solution: list of sites in H, e.g., ['4a', '8b']
            disp: an overall shift from H to G, None or 3 vector
            d_tol: the tolerance in angstrom
        Returns:
            distortion
            cell translation
        """
        max_disps = []
        atom_sites_H = self.struc.atom_sites
        n_atoms = sum([site.wp.multiplicity for site in atom_sites_H])
        #print("checking solution-----------------", solution)
        # wp1 stores the wyckoff position object of ['2c', '6h', '12i']
        for i, wp1 in enumerate(splitter.wp1_lists):

            if len(splitter.wp2_lists[i]) == 1:
                # one to one splitting, e.g., 2c->2d
                # this usually involves increase of site symmetry

                # symmetry info
                ops_H = splitter.H_orbits[i][0]  # ops for H
                ops_G2 = splitter.G2_orbits[i][0]  # ops for G2

                # refine coord1 to find the best match on coord2
                coord = atom_sites_H[solution[i][0]].position
                coord0s = apply_ops(coord, ops_H)  # possible coords in H
                dists = []
                for coord0 in coord0s:
                    coord2 = coord0.copy()
                    if disp is not None:
                        coord2 += disp
                    coord1 = apply_ops(coord2, ops_G2)[0]  # coord in G
                    #print(coord1, coord2)
                    dist = coord1 - coord2
                    dist -= np.round(dist)
                    dist = np.dot(dist, self.cell)
                    dists.append(np.linalg.norm(dist))
                min_ID = np.argmin(np.array(dists))

                dist = dists[min_ID]
                coord2 = coord0s[min_ID].copy()

                #print("---------", wp1.letter, coord1, coord2, disp, dist)
                #print(splitter)
                #print(splitter.R)
                if disp is None:
                    coord1 = apply_ops(coord2, ops_G2)[0]
                    disp = (coord1 - coord2).copy()
                    # temporary fix
                    xyz1 = ops_H[0].as_xyz_string().split(',')
                    xyz2 = ops_G2[0].as_xyz_string().split(',')
                    mask = [m for m in range(3) if xyz1[m] == xyz2[m]]
                    #disp[mask] = 0
                    #if abs(disp[0] + disp[1]) < 1e-2:
                    #    disp[:2] = 0
                    #print(disp, coord1, coord2)
                elif dist < d_tol:
                    coord1 = ops_G2[0].operate(coord2 + disp)
                    if round(np.trace(ops_G2[0].rotation_matrix)) in [1, 2]:

                        def fun(x, pt, ref, op):
                            pt[0] = x[0]
                            y = op.operate(pt)
                            diff = y - ref
                            diff -= np.round(diff)
                            diff = np.dot(diff, self.cell)
                            return np.linalg.norm(diff)

                        # optimize the distance by changing coord1
                        res = minimize(fun,
                                       coord1[0],
                                       args=(coord1, coord2, ops_G2[0]),
                                       method='Nelder-Mead',
                                       options={'maxiter': 20})
                        coord1[0] = res.x[0]
                        coord1 = ops_G2[0].operate(coord1)
                else:
                    return 10000, None, None
                if mask is not None:
                    disp[mask] = 0
                diff = coord1 - (coord2 + disp)
                diff -= np.round(diff)
                max_disps.append(np.linalg.norm(np.dot(diff, self.cell)))

            else:
                # symmetry operations
                ops_H1 = splitter.H_orbits[i][0]
                ops_G22 = splitter.G2_orbits[i][1]

                coord1 = atom_sites_H[solution[i][0]].position.copy()
                coord2 = atom_sites_H[solution[i][1]].position.copy()
                # refine coord1 to find the best match on coord2
                if disp is not None:
                    coord11 = coord1 + disp
                    coord22 = coord2 + disp
                else:
                    coord11 = coord1
                    coord22 = coord2
                coords11 = apply_ops(coord11, ops_H1)

                # transform coords1 by symmetry operation
                op = ops_G22[0]
                for m, coord11 in enumerate(coords11):
                    coords11[m] = op.operate(coord11)
                tmp, dist = get_best_match(coords11, coord22, self.cell)
                if dist > np.sqrt(2) * d_tol:
                    return 10000, None, mask

                # recover the original position
                try:
                    inv_op = get_inverse(op)
                except:
                    print("Error in getting the inverse")
                    print(op)
                    print(op.as_xyz_string())
                    import sys
                    sys.exit()
                coord1 = inv_op.operate(tmp)
                if disp is not None:
                    coord1 -= disp
                d = coord22 - tmp
                d -= np.round(d)

                coord22 -= d / 2  #final coord2 after disp
                # recover the displaced position
                coord11 = inv_op.operate(coord22)

                max_disps.append(np.linalg.norm(np.dot(d / 2, self.cell)))

        return max(max_disps), disp, mask
Exemple #9
0
    def _subgroup_by_splitter(self, splitter, eps=0.05, mut_lat=True):
        """
        transform the crystal to subgroup symmetry from a splitter object

        Args:
            splitter: wyckoff splitter object
            eps (float): maximum atomic displacement in Angstrom
            mut_lat (bool): whether or not mutate the lattice
        """
        lat1 = np.dot(splitter.R[:3,:3].T, self.lattice.matrix)
        multiples = np.linalg.det(splitter.R[:3,:3])
        new_struc = self.copy()
        new_struc.group = splitter.H
        lattice = Lattice.from_matrix(lat1, ltype=new_struc.group.lattice_type)
        if mut_lat:
            lattice=lattice.mutate(degree=eps, frozen=True)

        h = splitter.H.number
        split_sites = []
        if self.molecular:
            # below only works when the cell does not change
            for i, site in enumerate(self.mol_sites):
                pos = site.position
                mol = site.molecule
                ori = site.orientation
                coord0 = mol.mol.cart_coords.dot(ori.matrix.T)
                coord0 = np.dot(coord0, splitter.R[:3,:3])

                wp1 = site.wp
                ori.reset_matrix(np.eye(3))
                id = 0
                for ops1, ops2 in zip(splitter.G2_orbits[i], splitter.H_orbits[i]):
                    #reset molecule
                    rot = wp1.generators_m[id].affine_matrix[:3,:3].T
                    coord1 = np.dot(coord0, rot)
                    _mol = mol.copy()
                    _mol.reset_positions(coord1)

                    pos0 = apply_ops(pos, ops1)[0]
                    pos0 -= np.floor(pos0)
                    dis = (np.random.sample(3) - 0.5).dot(self.lattice.matrix)
                    dis /= np.linalg.norm(dis)
                    pos0 += eps*dis*(np.random.random()-0.5)
                    wp, _ = Wyckoff_position.from_symops(ops2, h, permutation=False)
                    if h in [7, 14] and self.group.number == 31:
                        diag = True
                    else:
                        diag = self.diag
                    split_sites.append(mol_site(_mol, pos0, ori, wp, lattice, diag))
                    id += wp.multiplicity
            new_struc.mol_sites = split_sites
            new_struc.numMols = [int(multiples*numMol) for numMol in self.numMols]

        else:
            for i, site in enumerate(self.atom_sites):
                pos = site.position
                for ops1, ops2 in zip(splitter.G2_orbits[i], splitter.H_orbits[i]):
                    pos0 = apply_ops(pos, ops1)[0]
                    pos0 -= np.floor(pos0)
                    dis = (np.random.sample(3) - 0.5).dot(self.lattice.matrix)
                    dis /= np.linalg.norm(dis)
                    pos0 += np.dot(eps*dis*(np.random.random()-0.5), self.lattice.inv_matrix)
                    wp, _ = Wyckoff_position.from_symops(ops2, h, permutation=False)
                    split_sites.append(atom_site(wp, pos0, site.specie))

            new_struc.atom_sites = split_sites
            new_struc.numIons = [int(multiples*numIon) for numIon in self.numIons]
        new_struc.lattice = lattice
        new_struc.source = 'subgroup'

        return new_struc
Exemple #10
0
    #sites = ['8a']
    G, H, fac = 227, 166, 4
    numIons = int(sum([int(i[:-1]) for i in sites]) / fac)
    C = random_crystal(G, ['C'], [numIons], sites=[sites])
    spg1 = get_symmetry_dataset(C.to_ase(), symprec=1e-4)['international']

    splitter = wyckoff_split(G=G, H=H, wp1=sites)
    print(splitter)
    lat1 = np.dot(C.lattice.matrix, splitter.R[:3, :3].T)
    pos1 = None
    for i, site in enumerate(C.atom_sites):
        pos = site.position
        for ops1, ops2 in zip(splitter.G2_orbits[i], splitter.H_orbits[i]):
            pos0 = pos + 0.05 * (np.random.sample(3) - 0.5)
            #print(pos0)
            pos_tmp = apply_ops(pos0, ops1)
            pos_tmp = apply_ops(pos_tmp[0], ops2)
            if pos1 is None:
                pos1 = pos_tmp
            else:
                pos1 = np.vstack((pos1, pos_tmp))

    C1 = Atoms(["C"] * len(pos1),
               scaled_positions=pos1,
               cell=lat1,
               pbc=[1, 1, 1])
    C1.write("1.vasp", format='vasp', vasp5=True, direct=True)
    C.to_ase().write("0.vasp", format='vasp', vasp5=True, direct=True)
    pmg_s1 = AseAtomsAdaptor.get_structure(C1)
    spg2 = get_symmetry_dataset(C1, symprec=1e-4)['international']
    print(spg1, spg2, sm.StructureMatcher().fit(pmg_s1, C.to_pymatgen()))
Exemple #11
0
    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")
Exemple #12
0
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
Exemple #13
0
 def update(self, pos):
     """
     Used to generate coords from self.position
     """
     self.coords = apply_ops(pos, self.wp)
     self.position = self.coords[0]
Exemple #14
0
    def symmetrize(self, splitter, mapping, disp):
        """
        For a given solution, search for the possbile supergroup structure

        Args:
            splitter: splitter object to specify the relation between G and H
            disp: an overall shift from H to G, None or 3 vector
            d_tol: the tolerance in angstrom

        Returns:
            coords_G1: coordinates in G
            coords_G2: coordinates in G under the subgroup setting
            coords_H1: coordinates in H
            elements: list of elements
        """
        cell = np.dot(np.linalg.inv(splitter.R[:3,:3]).T, self.struc.lattice.matrix)
        atom_sites_H = self.struc.atom_sites
        coords_G1 = [] # position in G
        coords_G2 = [] # position in G on the subgroup bais
        coords_H1 = [] # position in H
        elements = []
        rot = splitter.R[:3,:3] # inverse coordinate transformation
        tran = splitter.R[:3,3] # needs to check
        inv_rot = np.linalg.inv(rot)
        ops_G1  = splitter.G[0]
        # wp1 stores the wyckoff position object of ['2c', '6h', '12i']
        for i, wp1 in enumerate(splitter.wp1_lists):

            if len(splitter.wp2_lists[i]) == 1:
                op_G1 = splitter.G1_orbits[i][0][0]
                ops_H = splitter.H_orbits[i][0]
                base = atom_sites_H[mapping[i][0]].position.copy() 

                #choose the best coord1_H
                coord1s_H = apply_ops(base, ops_H)
                ds = []
                for coord1_H in coord1s_H:
                    coord1_G2 = coord1_H + disp
                    coord1_G1, diff = search_G1(splitter.G, rot, tran, coord1_G2, wp1, op_G1)
                    ds.append(diff)

                ds = np.array(ds)
                minID = np.argmin(ds)
                coord1_H = coord1s_H[minID]

                # symmetrize coord_G1
                tmp, _ = search_G1(splitter.G, rot, tran, coord1_H+disp, wp1, op_G1)

                coords_G1.append(tmp)
                coord1_G2, _ = search_G2(inv_rot, -tran, tmp, coord1_H+disp, self.cell)
                #print("G2", wp1.letter, coord1_G2, tmp)
                coords_G2.append(coord1_G2)
                coords_H1.append(coord1_H)

            else:

                if atom_sites_H[mapping[i][0]].wp.letter == splitter.wp2_lists[i][0].letter:
                    coord1_H = atom_sites_H[mapping[i][0]].position.copy() 
                    coord2_H = atom_sites_H[mapping[i][1]].position.copy()
                else:
                    coord2_H = atom_sites_H[mapping[i][0]].position.copy() 
                    coord1_H = atom_sites_H[mapping[i][1]].position.copy()

                coord1_G2 = coord1_H + disp
                coord2_G2 = coord2_H + disp  
                op_G11 = splitter.G1_orbits[i][0][0]
                op_G12 = splitter.G1_orbits[i][1][0]

                if splitter.group_type == 'k':
                    ops_H1 = splitter.H_orbits[i][0]
                    op_G21 = splitter.G2_orbits[i][0][0]
                    ops_G22 = splitter.G2_orbits[i][1]

                    coord11 = coord1_H + disp
                    coord22 = coord2_H + disp

                    for op_G22 in ops_G22:
                        diff = (op_G22.rotation_matrix - op_G21.rotation_matrix).flatten()
                        if np.sum(diff**2) < 1e-3:
                            trans = op_G22.translation_vector - op_G21.translation_vector
                            break
                    trans -= np.round(trans)
                    coords11 = apply_ops(coord1_G2, ops_H1)
                    coords11 += trans
                    tmp, dist = get_best_match(coords11, coord2_G2, self.cell)
                    #print("G1:", tmp)
                    d = coord2_G2 - tmp
                    d -= np.round(d)
                    coord2_G2 -= d/2
                    coord1_G2 += d/2
                    coord1_G1, _ = search_G1(splitter.G, rot, tran, tmp, wp1, op_G11)
                    #print(coord1_G2, coord2_G2, np.dot(rot, tmp).T + tran.T)
                    coords_G1.append(coord1_G1)

                else:
                    # H->G2->G1
                    coord1_G1, _ = search_G1(splitter.G, rot, tran, coord1_G2, wp1, op_G11)
                    coord2_G1, _ = search_G1(splitter.G, rot, tran, coord2_G2, wp1, op_G12)
                    
                    #find the best match
                    coords11 = apply_ops(coord1_G1, ops_G1)
                    tmp, dist = get_best_match(coords11, coord2_G1, cell)
                    tmp = sym.search_cloest_wp(splitter.G, wp1, op_G12, tmp)
 
                    # G1->G2->H
                    d = coord2_G1 - tmp
                    d -= np.round(d)
                    coord2_G1 -= d/2
                    coord1_G1 += d/2
                    coords_G1.append(tmp)

                    coord1_G2, dist1 = search_G2(inv_rot, -tran, coord1_G1, coord1_H+disp, self.cell)
                    coord2_G2, dist2 = search_G2(inv_rot, -tran, coord2_G1, coord2_H+disp, self.cell)

                coords_G2.append(coord1_G2)
                coords_G2.append(coord2_G2)
                coords_H1.append(coord1_H)
                coords_H1.append(coord2_H)

            elements.extend([splitter.elements[i]]*len(splitter.wp2_lists[i]))

        return coords_G1, coords_G2, coords_H1, elements
Exemple #15
0
    def symmetrize_dist(self, splitter, mapping, disp=None, mask=None, d_tol=1.2):
        """
        For a given solution, search for the possbile supergroup structure

        Args:
            splitter: splitter object to specify the relation between G and H
            mapping: list of sites in H, e.g., ['4a', '8b']
            disp: an overall shift from H to G, None or 3 vector
            mask: if need to freeze the direction
            d_tol: the tolerance in angstrom

        Returns:
            distortion
            cell translation
        """
        cell = np.dot(np.linalg.inv(splitter.R[:3,:3]).T, self.struc.lattice.matrix)
        max_disps = []
        atom_sites_H = self.struc.atom_sites
        rot = splitter.R[:3,:3] # inverse coordinate transformation
        tran = splitter.R[:3,3] # needs to check
        inv_rot = np.linalg.inv(rot)
        cell = np.dot(np.linalg.inv(splitter.R[:3,:3]).T, self.struc.lattice.matrix)
        ops_G1  = splitter.G[0]

        # if there involves wp transformation between frozen points, disp has to be zero
        for wp2 in splitter.wp2_lists:
            frozen = True
            for wp in wp2:
                if np.linalg.matrix_rank(wp[0].rotation_matrix) > 0:
                    frozen = False

            if frozen:
                disp = np.zeros(3)
                mask = [0, 1, 2]
                break

        if mask is not None and disp is not None:
            disp[mask] = 0

        for i, wp1 in enumerate(splitter.wp1_lists):
            if len(splitter.wp2_lists[i]) == 1:
                op_G1 = splitter.G1_orbits[i][0][0]
                ops_H = splitter.H_orbits[i][0]
                base = atom_sites_H[mapping[i][0]].position.copy() 

                #choose the best coord1_H
                coord1s_H = apply_ops(base, ops_H)
                ds = []
                for coord1_H in coord1s_H:
                    if disp is not None:
                        coord1_G2 = coord1_H + disp
                    else:
                        coord1_G2 = coord1_H 
                    coord1_G1, diff = search_G1(splitter.G, rot, tran, coord1_G2, wp1, op_G1)
                    ds.append(diff)

                ds = np.array(ds)
                minID = np.argmin(ds)
                coord1_H = coord1s_H[minID]

                if disp is not None:
                    coord1_G2 = coord1_H + disp
                else:
                    coord1_G2 = coord1_H 
                
                tmp, _ = search_G1(splitter.G, rot, tran, coord1_G2, wp1, op_G1)

                # initial guess on disp
                if disp is None:
                    coord1_G2, dist1 = search_G2(inv_rot, -tran, tmp, coord1_H, None)
                    diff = coord1_G2 - coord1_H
                    diff -= np.round(diff)
                    disp = diff.copy()
                    mask = []
                    for m in range(3):
                        if abs(diff[m])<1e-4:
                            mask.append(m)
                    dist = 0
                else:
                    coord1_G2, dist = search_G2(inv_rot, -tran, tmp, coord1_H+disp, self.cell)

                if dist < d_tol:
                    #if dist >0: print(dist, coord1_G2, coord1_H+disp)
                    max_disps.append(dist)
                else:
                    #import sys; sys.exit()
                    #print("--------", wp1.letter, tmp, coord1_G2, coord1_H+disp, dist)
                    return 10000, None, None

            elif len(splitter.wp2_lists[i]) == 2:
                # assume zero shift, needs to check
                if disp is None:
                    disp = np.zeros(3)
                    mask = [0, 1, 2]

                # H->G2->G1
                if atom_sites_H[mapping[i][0]].wp.letter == splitter.wp2_lists[i][0].letter:
                    coord1_H = atom_sites_H[mapping[i][0]].position.copy()
                    coord2_H = atom_sites_H[mapping[i][1]].position.copy()
                else:
                    coord2_H = atom_sites_H[mapping[i][0]].position.copy()
                    coord1_H = atom_sites_H[mapping[i][1]].position.copy()

                #print("\n\n\nH", coord1_H, coord2_H)

                coord1_G2 = coord1_H + disp
                coord2_G2 = coord2_H + disp

                if splitter.group_type == 'k':
                    # For t-type splitting, restore the translation symmetry:
                    # e.g. (0.5, 0.5, 0.5), (0.5, 0, 0), .etc
                    # then find the best_match between coord1 and coord2, 

                    ops_H1 = splitter.H_orbits[i][0]
                    op_G21 = splitter.G2_orbits[i][0][0]
                    ops_G22 = splitter.G2_orbits[i][1]
                    for op_G22 in ops_G22:
                        diff = (op_G22.rotation_matrix - op_G21.rotation_matrix).flatten()
                        if np.sum(diff**2) < 1e-3:
                            trans = op_G22.translation_vector - op_G21.translation_vector
                            break
                    trans -= np.round(trans)
                    coords11 = apply_ops(coord1_G2, ops_H1)
                    coords11 += trans
                    tmp, dist = get_best_match(coords11, coord2_G2, self.cell)
                    if dist > np.sqrt(2)*d_tol:
                        #print("disp:", disp)
                        #print("G21:", coords11)
                        #print("G22:", coord2_G2)
                        #print("start: ", coord1_H, coord2_H)
                        #res = tmp - coord2_G2
                        #res -= np.round(res)
                        #print("kkkk", wp1.letter, dist, tmp, coord2_G2, "diff", res)
                        return 10000, None, mask
                    else:
                        d = coord2_G2 - tmp
                        d -= np.round(d)
                        max_disps.append(np.linalg.norm(np.dot(d/2, self.cell)))

                else:
                    #print("disp", disp)
                    #print("G2", coord1_G2, coord2_G2)
                    op_G11 = splitter.G1_orbits[i][0][0]
                    op_G12 = splitter.G1_orbits[i][1][0]
                    coord1_G1, _ = search_G1(splitter.G, rot, tran, coord1_G2, wp1, op_G11)
                    coord2_G1, _ = search_G1(splitter.G, rot, tran, coord2_G2, wp1, op_G12)

                    #print("G1", coord1_G1, coord2_G1, op_G12.as_xyz_string())
                    #print(splitter.G.number, splitter.wp1_lists[i].index, coord2_G1, op_G12.as_xyz_string())
                    coord2_G1 = sym.search_cloest_wp(splitter.G, wp1, op_G12, coord2_G1)
                    #print("G1(symm1)", coord1_G1, coord2_G1)
                    #import sys; sys.exit()
                    #find the best match
                    coords11 = apply_ops(coord1_G1, ops_G1)
                    tmp, dist = get_best_match(coords11, coord2_G1, cell)
                    tmp = sym.search_cloest_wp(splitter.G, wp1, op_G12, tmp)

                    # G1->G2->H
                    d = coord2_G1 - tmp
                    d -= np.round(d)
                    #print("dist", np.linalg.norm(d), "d", d, tmp, coord2_G1)

                    coord2_G1 -= d/2
                    coord1_G1 += d/2
                    #print("G1 (symm2)", coord1_G1, coord2_G1)
                    
                    coord1_G2, dist1 = search_G2(inv_rot, -tran, coord1_G1, coord1_H+disp, self.cell)
                    coord2_G2, dist2 = search_G2(inv_rot, -tran, coord2_G1, coord2_H+disp, self.cell)

                    #print(wp1.letter, dist1, dist2)
                    if max([dist1, dist2]) > np.sqrt(2)*d_tol:
                        #print("1:", coord1_G2, coord1_H, dist1)
                        #print("2:", coord2_G2, coord2_H, dist2)
                        #print("T:", tmp)
                        return 10000, None, mask
                    else:
                        max_disps.append(max([dist1, dist2]))
            else:
                raise RuntimeError("donot support merging 3 sites to 1 site")
        #print(max_disps)
        return max(max_disps), disp, mask