コード例 #1
0
    def test_get_supercell_matrix(self):
        sm = StructureMatcher(ltol=0.1, stol=0.3, angle_tol=2,
                              primitive_cell=False, scale=True,
                              attempt_supercell=True)

        l = Lattice.orthorhombic(1, 2, 3)

        s1 = Structure(l, ['Si', 'Si', 'Ag'],
                       [[0, 0, 0.1], [0, 0, 0.2], [.7, .4, .5]])
        s1.make_supercell([2, 1, 1])
        s2 = Structure(l, ['Si', 'Si', 'Ag'],
                       [[0, 0.1, 0], [0, 0.1, -0.95], [-.7, .5, .375]])
        result = sm.get_supercell_matrix(s1, s2)
        self.assertTrue((result == [[-2, 0, 0], [0, 1, 0], [0, 0, 1]]).all())

        s1 = Structure(l, ['Si', 'Si', 'Ag'],
                       [[0, 0, 0.1], [0, 0, 0.2], [.7, .4, .5]])
        s1.make_supercell([[1, -1, 0], [0, 0, -1], [0, 1, 0]])

        s2 = Structure(l, ['Si', 'Si', 'Ag'],
                       [[0, 0.1, 0], [0, 0.1, -0.95], [-.7, .5, .375]])
        result = sm.get_supercell_matrix(s1, s2)
        self.assertTrue((result == [[-1, -1, 0], [0, 0, -1], [0, 1, 0]]).all())

        # test when the supercell is a subset
        sm = StructureMatcher(ltol=0.1, stol=0.3, angle_tol=2,
                              primitive_cell=False, scale=True,
                              attempt_supercell=True, allow_subset=True)
        del s1[0]
        result = sm.get_supercell_matrix(s1, s2)
        self.assertTrue((result == [[-1, -1, 0], [0, 0, -1], [0, 1, 0]]).all())
コード例 #2
0
    def test_get_supercell_matrix(self):
        sm = StructureMatcher(ltol=0.1, stol=0.3, angle_tol=2,
                              primitive_cell=False, scale=True,
                              attempt_supercell=True)

        l = Lattice.orthorhombic(1, 2, 3)

        s1 = Structure(l, ['Si', 'Si', 'Ag'],
                       [[0,0,0.1],[0,0,0.2],[.7,.4,.5]])
        s1.make_supercell([2,1,1])
        s2 = Structure(l, ['Si', 'Si', 'Ag'],
                       [[0,0.1,0],[0,0.1,-0.95],[-.7,.5,.375]])
        result = sm.get_supercell_matrix(s1, s2)
        self.assertTrue((result == [[-2,0,0],[0,1,0],[0,0,1]]).all())

        s1 = Structure(l, ['Si', 'Si', 'Ag'],
                       [[0,0,0.1],[0,0,0.2],[.7,.4,.5]])
        s1.make_supercell([[1, -1, 0],[0, 0, -1],[0, 1, 0]])

        s2 = Structure(l, ['Si', 'Si', 'Ag'],
                       [[0,0.1,0],[0,0.1,-0.95],[-.7,.5,.375]])
        result = sm.get_supercell_matrix(s1, s2)
        self.assertTrue((result == [[-1,-1,0],[0,0,-1],[0,1,0]]).all())

        #test when the supercell is a subset
        sm = StructureMatcher(ltol=0.1, stol=0.3, angle_tol=2,
                              primitive_cell=False, scale=True,
                              attempt_supercell=True, allow_subset=True)
        del s1[0]
        result = sm.get_supercell_matrix(s1, s2)
        self.assertTrue((result == [[-1,-1,0],[0,0,-1],[0,1,0]]).all())
コード例 #3
0
ファイル: ce.py プロジェクト: qchempku2017/CMO
class ClusterExpansion(object):
    """
    Holds lists of SymmetrizedClusters and ClusterSupercells. This is probably the class you're looking for
    and should be instantiating. You probably want to generate from ClusterExpansion.from_radii, which will
    auto-generate the symmetrized clusters, unless you want more control over them.
    """

    def __init__(self, structure, expansion_structure, symops, clusters, \
                 sm_type='pmg_sm', ltol=0.2, stol=0.1, angle_tol=5,\
                 supercell_size='num_sites', use_ewald=False, use_inv_r=False, eta=None, basis = '01'):
        """
            Args:
                structure:
                    disordered structure to build a cluster expansion for. Typically the primitive cell
                radii:
                    dict of {cluster_size: max_radius}. Radii should be strictly decreasing.
                    Typically something like {2:5, 3:4}
                sm_type:
                    The structure matcher type that you wish to use in structure matching. Can choose from 
                    pymatgen default (pmg_sm), anion framework (an_frame)
                ltol, stol, angle_tol, supercell_size: parameters to pass through to the StructureMatcher, 
                    when sm_type == 'pmg_sm' or 'an_frame'
                    Structures that don't match to the primitive cell under these tolerances won't be included
                    in the expansion. Easiest option for supercell_size is usually to use a species that has a
                    constant amount per formula unit.
                use_ewald:
                    whether to calculate the ewald energy of each structure and use it as a feature. Typically
                    a good idea for ionic materials.
                use_inv_r:
                    experimental feature that allows fitting to arbitrary 1/r interactions between specie-site
                    combinations.
                eta:
                    parameter to override the EwaldSummation default eta. Usually only necessary if use_inv_r=True
                basis: 
                    Basis to use in cluster expansion. Currently can be 'ortho' or '01', plan to add 'chebyshev'.
            """

        if use_inv_r and eta is None:
            warn("Be careful, you might need to change eta to get properly "
                 "converged electrostatic energies. This isn't well tested")

        self.structure = structure
        self.expansion_structure = expansion_structure
        self.symops = symops

        # test that all the found symmetry operations map back to the input structure
        # otherwise you can get weird subset/superset bugs
        fc = self.structure.frac_coords
        for op in self.symops:
            if not is_coord_subset_pbc(op.operate_multi(fc), fc, SITE_TOL):
                raise SYMMETRY_ERROR

        self.supercell_size = supercell_size
        self.use_ewald = use_ewald
        self.eta = eta
        self.use_inv_r = use_inv_r
        
        self.sm_type=sm_type
        self.stol = stol
        self.ltol = ltol
        #self.vor_tol = vor_tol
        self.basis = basis

        if self.sm_type == 'pmg_sm' or self.sm_type == 'an_frame':
            self.angle_tol = angle_tol
            self.sm = StructureMatcher(primitive_cell=False,
                                   attempt_supercell=True,
                                   allow_subset=True,
                                   scale=True,
                                   supercell_size=self.supercell_size,
                                   comparator=OrderDisorderElementComparator(),
                                   stol=self.stol,
                                   ltol=self.ltol,
                                   angle_tol=self.angle_tol)
       # elif self.sm_type == 'an_dmap':
       #     print("Warning: Delaunay matcher only applicable for close packed anion framework!")
       #     try:
       #         from delaunay_matcher import DelaunayMatcher
       #         self.sm = DelauneyMatcher()
       #     # At leaset three methods are required in Delauney Matcher: match, mapping and supercell matrix finding.
       #     except:
       #         pass
       # I abandoned delaunay because it is not stable with respect to distortion.
        else:
            raise ValueError('Structure matcher not implemented!')

        self.clusters = clusters

        # assign the cluster ids
        n_clusters = 1
        n_bit_orderings = 1
        n_sclusters = 1
        for k in sorted(self.clusters.keys()):
            for y in self.clusters[k]:
                n_sclusters, n_bit_orderings, n_clusters = y.assign_ids(n_sclusters, n_bit_orderings, n_clusters)
        self.n_sclusters = n_sclusters
        self.n_clusters = n_clusters
        self.n_bit_orderings = n_bit_orderings
        self._supercells = {}


    @classmethod
    def from_radii(cls, structure, radii,\
                   sm_type = 'pmg_sm', ltol=0.2, stol=0.1, angle_tol=5,\
                   supercell_size='volume',use_ewald=False, use_inv_r=False, eta=None, basis = '01'):
        """
        Args:
            structure:
                disordered structure to build a cluster expansion for. Typically the primitive cell
            radii:
                dict of {cluster_size: max_radius}. Radii should be strictly decreasing.
                Typically something like {2:5, 3:4}
            ltol, stol, angle_tol, supercell_size: parameters to pass through to the StructureMatcher.
                Structures that don't match to the primitive cell under these tolerances won't be included
                in the expansion. Easiest option for supercell_size is usually to use a species that has a
                constant amount per formula unit.
            use_ewald:
                whether to calculate the ewald energy of each structure and use it as a feature. Typically
                a good idea for ionic materials.
            use_inv_r:
                experimental feature that allows fitting to arbitrary 1/r interactions between specie-site
                combinations.
            eta:
                parameter to override the EwaldSummation default eta. Usually only necessary if use_inv_r=True
        """
        symops = SpacegroupAnalyzer(structure).get_symmetry_operations()
        #get the sites to expand over
        sites_to_expand = [site for site in structure if site.species.num_atoms < 0.99 \
                            or len(site.species) > 1]
        expansion_structure = Structure.from_sites(sites_to_expand)
        clusters = cls._clusters_from_radii(expansion_structure, radii, symops)
        return cls(structure=structure, expansion_structure=expansion_structure, symops=symops, \
                   sm_type = sm_type, ltol=ltol, stol=stol, angle_tol=angle_tol,\
                   clusters=clusters, supercell_size=supercell_size, use_ewald=use_ewald, \
                   use_inv_r=use_inv_r,eta=eta, basis=basis)

    @classmethod
    def _clusters_from_radii(cls, expansion_structure, radii, symops):
        """
        Generates dictionary of size: [SymmetrizedCluster] given a dictionary of maximal cluster radii and symmetry
        operations to apply (not necessarily all the symmetries of the expansion_structure)
        """
        bits = get_bits(expansion_structure)
        nbits = np.array([len(b) - 1 for b in bits])
        # nbits = np.array([len(b) for b in bits])

        new_clusters = []
        clusters = {}
        for i, site in enumerate(expansion_structure):
            new_c = Cluster([site.frac_coords], expansion_structure.lattice)
            new_sc = SymmetrizedCluster(new_c, [np.arange(nbits[i])], symops)
            if new_sc not in new_clusters:
                new_clusters.append(new_sc)
        clusters[1] = sorted(new_clusters, key = lambda x: (np.round(x.max_radius,6), -x.multiplicity))

        all_neighbors = expansion_structure.lattice.get_points_in_sphere(expansion_structure.frac_coords, [0.5, 0.5, 0.5],
                                    max(radii.values()) + sum(expansion_structure.lattice.abc)/2)

        for size, radius in sorted(radii.items()):
            new_clusters = []
            for c in clusters[size-1]:
                if c.max_radius > radius:
                    continue
                for n in all_neighbors:
                    p = n[0]
                    if is_coord_subset([p], c.sites, atol=SITE_TOL):
                        continue
                    new_c = Cluster(np.concatenate([c.sites, [p]]), expansion_structure.lattice)
                    if new_c.max_radius > radius + 1e-8:
                        continue
                    new_sc = SymmetrizedCluster(new_c, c.bits + [np.arange(nbits[n[2]])], symops)
                    if new_sc not in new_clusters:
                        new_clusters.append(new_sc)
            clusters[size] = sorted(new_clusters, key = lambda x: (np.round(x.max_radius,6), -x.multiplicity))
        return clusters

    def supercell_matrix_from_structure(self, structure):
        if self.sm_type == 'pmg_sm': 
            sc_matrix = self.sm.get_supercell_matrix(structure, self.structure)
        elif self.sm_type == 'an_frame':
            prim_an_sites = [site for site in self.structure if Is_Anion_Site(site)]


            prim_an = Structure.from_sites(prim_an_sites)

            s_an_fracs = []
            s_an_sps = []
            latt = structure.lattice
            for site in structure:
                if Is_Anion_Site(site):
                    s_an_fracs.append(site.frac_coords)
                    s_an_sps.append(site.specie)

            scaling = ((len(s_an_sps)/len(prim_an))/(structure.volume/self.structure.volume))**(1/3.0)
            s_an_latt = Lattice.from_parameters(latt.a * scaling, latt.b * scaling, latt.c * scaling, \
                                                        latt.alpha, latt.beta, latt.gamma)
            structure_an = Structure(s_an_latt,s_an_sps,s_an_fracs,to_unit_cell =False, coords_are_cartesian=False)
            #print('Structure:',structure)
            #print('Structure_an:',structure_an)
            #print('Prim_an:',prim_an)

            sc_matrix = self.sm.get_supercell_matrix(structure_an, prim_an)
        else:
            raise ValueError("Structure Matcher type not implemented!")
        if sc_matrix is None:
            raise ValueError("Supercell couldn't be found")
        if np.linalg.det(sc_matrix) < 0:
            sc_matrix *= -1
        return sc_matrix

    def supercell_from_structure(self, structure):
        sc_matrix = self.supercell_matrix_from_structure(structure)
        return self.supercell_from_matrix(sc_matrix)


    def supercell_from_matrix(self, sc_matrix):
        sc_matrix = tuple(sorted(tuple(s) for s in sc_matrix))
        # print(sc_matrix)
        if sc_matrix in self._supercells:
            cs = self._supercells[sc_matrix]
        else:
            cs = ClusterSupercell(sc_matrix, self)
            self._supercells[sc_matrix] = cs
        return cs

#    def corr_from_external(self, structure, sc_matrix):
#        cs = self.supercell_from_matrix(self, sc_matrix)
#        return cs.corr_from_structure(structure)
#    This function will be integrated into supercell_from_structure.

    def corr_from_structure(self, structure):
        """
        Given a structure, determines which supercell to use,
        and gets the correlation vector
        """
        cs = self.supercell_from_structure(structure)
        return cs.corr_from_structure(structure)

    def base_energy(self, structure):
        sc = self.supercell_from_structure(structure)
        occu = sc.occu_from_structure(structure)
        be = sc._get_ewald_eci(occu)[0] #* sc.size
        return be

    def refine_structure(self, structure):
        sc_matrix = self.supercell_matrix_from_structure(structure)
        sc = self.supercell_from_matrix(sc_matrix)
        occu = sc.occu_from_structure(structure)
        return sc.structure_from_occu(occu)

#    def refine_structure_external(self, structure, sc_matrix):
#        cs = self.supercell_from_matrix(sc_matrix)
#        occu = cs.occu_from_structure(structure)
#        return cs.structure_from_occu(occu)

    def structure_energy(self, structure, ecis):
        cs = self.supercell_from_structure(structure)
        return cs.structure_energy(structure, ecis)

    @property
    def symmetrized_clusters(self):
        """
        Yields all symmetrized clusters
        """
        for k in sorted(self.clusters.keys()):
            for c in self.clusters[k]:
                # print(c)
                yield c

    def __str__(self):
        s = "ClusterBasis: {}\n".format(self.structure.composition)
        for k, v in self.clusters.iteritems():
            s += "    size: {}\n".format(k)
            for z in v:
                s += "    {}\n".format(z)
        return s

    @classmethod
    def from_dict(cls, d):
        symops = [SymmOp.from_dict(so) for so in d['symops']]
        clusters = {}
        for k, v in d['clusters_and_bits'].items():
            clusters[int(k)] = [SymmetrizedCluster(Cluster.from_dict(c[0]), c[1], symops) for c in v]
        return cls(structure=Structure.from_dict(d['structure']),
                   expansion_structure=Structure.from_dict(d['expansion_structure']),
                   clusters=clusters, symops=symops, 
                   sm_type = d['sm_type'] if 'sm_type' in d else 'pmg_sm',
                   ltol=d['ltol'], stol=d['stol'], angle_tol=d['angle_tol'],
                   #vor_tol = d['vor_tol'] if 'vor_tol' in d else 1e-3,
                   supercell_size=d['supercell_size'],
                   use_ewald=d['use_ewald'], use_inv_r=d['use_inv_r'],
                   eta=d['eta'],
                   basis=d['basis'] if 'basis' in d else '01')
    # Compatible with old datas

    def as_dict(self):
        c = {}
        for k, v in self.clusters.items():
            c[int(k)] = [(sc.base_cluster.as_dict(), [list(b) for b in sc.bits]) for sc in v]
        return {'structure': self.structure.as_dict(),
                'expansion_structure': self.expansion_structure.as_dict(),
                'symops': [so.as_dict() for so in self.symops],
                'clusters_and_bits': c,
                'sm_type': self.sm_type,
                'ltol': self.ltol,
                'stol': self.stol,
                'angle_tol': self.angle_tol,
                #'vor_tol': self.vor_tol,
                'supercell_size': self.supercell_size,
                'use_ewald': self.use_ewald,
                'use_inv_r': self.use_inv_r,
                'eta': self.eta,
                'basis':self.basis,
                '@module': self.__class__.__module__,
                '@class': self.__class__.__name__}