Example #1
0
 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")
Example #2
0
File: io.py Project: Virts/PyXtal
    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
Example #3
0
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
Example #4
0
    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
Example #5
0
    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
Example #6
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")