Exemple #1
0
    def test_ordered_primitive_to_disordered_supercell(self):
        sm_atoms = StructureMatcher(ltol=0.2, stol=0.3, angle_tol=5,
                                    primitive_cell=False, scale=True,
                                    attempt_supercell=True,
                                    allow_subset=True,
                                    supercell_size = 'num_atoms',
                                    comparator=OrderDisorderElementComparator())
        sm_sites = StructureMatcher(ltol=0.2, stol=0.3, angle_tol=5,
                                    primitive_cell=False, scale=True,
                                    attempt_supercell=True,
                                    allow_subset=True,
                                    supercell_size = 'num_sites',
                                    comparator=OrderDisorderElementComparator())
        lp = Lattice.orthorhombic(10, 20, 30)
        pcoords = [[0,   0,   0],
                   [0.5, 0.5, 0.5]]
        ls = Lattice.orthorhombic(20,20,30)
        scoords = [[0,    0,   0],
                   [0.5,  0,   0],
                   [0.25, 0.5, 0.5],
                   [0.75, 0.5, 0.5]]
        s1 = Structure(lp, ['Na', 'Cl'], pcoords)
        s2 = Structure(ls, [{'Na':0.5}, {'Na':0.5}, {'Cl':0.5}, {'Cl':0.5}], scoords)

        self.assertTrue(sm_sites.fit(s1, s2))
        self.assertFalse(sm_atoms.fit(s1, s2))
    def test_disordered_primitive_to_ordered_supercell(self):
        sm_atoms = StructureMatcher(
            ltol=0.2,
            stol=0.3,
            angle_tol=5,
            primitive_cell=False,
            scale=True,
            attempt_supercell=True,
            allow_subset=True,
            supercell_size='num_atoms',
            comparator=OrderDisorderElementComparator())
        sm_sites = StructureMatcher(
            ltol=0.2,
            stol=0.3,
            angle_tol=5,
            primitive_cell=False,
            scale=True,
            attempt_supercell=True,
            allow_subset=True,
            supercell_size='num_sites',
            comparator=OrderDisorderElementComparator())
        lp = Lattice.orthorhombic(10, 20, 30)
        pcoords = [[0, 0, 0], [0.5, 0.5, 0.5]]
        ls = Lattice.orthorhombic(20, 20, 30)
        scoords = [[0, 0, 0], [0.75, 0.5, 0.5]]
        prim = Structure(lp, [{'Na': 0.5}, {'Cl': 0.5}], pcoords)
        supercell = Structure(ls, ['Na', 'Cl'], scoords)
        supercell.make_supercell([[-1, 1, 0], [0, 1, 1], [1, 0, 0]])

        self.assertFalse(sm_sites.fit(prim, supercell))
        self.assertTrue(sm_atoms.fit(prim, supercell))

        self.assertRaises(ValueError, sm_atoms.get_s2_like_s1, prim, supercell)
        self.assertEqual(len(sm_atoms.get_s2_like_s1(supercell, prim)), 4)
    def get_framework_rms_plot(self,
                               plt=None,
                               granularity=200,
                               matching_s=None):
        """
        Get the plot of rms framework displacement vs time. Useful for checking
        for melting, especially if framework atoms can move via paddle-wheel
        or similar mechanism (which would show up in max framework displacement
        but doesn't constitute melting).

        Args:
            plt (matplotlib.pyplot): If plt is supplied, changes will be made
                to an existing plot. Otherwise, a new plot will be created.
            granularity (int): Number of structures to match
            matching_s (Structure): Optionally match to a disordered structure
                instead of the first structure in the analyzer. Required when
                a secondary mobile ion is present.
        Notes:
            The method doesn't apply to NPT-AIMD simulation analysis.
        """
        from pymatgen.util.plotting import pretty_plot

        if self.lattices is not None and len(self.lattices) > 1:
            warnings.warn(
                "Note the method doesn't apply to NPT-AIMD simulation analysis!"
            )

        plt = pretty_plot(12, 8, plt=plt)
        step = (self.corrected_displacements.shape[1] - 1) // (granularity - 1)
        f = (matching_s or self.structure).copy()
        f.remove_species([self.specie])
        sm = StructureMatcher(
            primitive_cell=False,
            stol=0.6,
            comparator=OrderDisorderElementComparator(),
            allow_subset=True,
        )
        rms = []
        for s in self.get_drift_corrected_structures(step=step):
            s.remove_species([self.specie])
            d = sm.get_rms_dist(f, s)
            if d:
                rms.append(d)
            else:
                rms.append((1, 1))
        max_dt = (len(rms) - 1) * step * self.step_skip * self.time_step
        if max_dt > 100000:
            plot_dt = np.linspace(0, max_dt / 1000, len(rms))
            unit = "ps"
        else:
            plot_dt = np.linspace(0, max_dt, len(rms))
            unit = "fs"
        rms = np.array(rms)
        plt.plot(plot_dt, rms[:, 0], label="RMS")
        plt.plot(plot_dt, rms[:, 1], label="max")
        plt.legend(loc="best")
        plt.xlabel(f"Timestep ({unit})")
        plt.ylabel("normalized distance")
        plt.tight_layout()
        return plt
    def get_framework_rms_plot(self,
                               plt=None,
                               granularity=200,
                               matching_s=None):
        """
        Get the plot of rms framework displacement vs time. Useful for checking
        for melting, especially if framework atoms can move via paddle-wheel
        or similar mechanism (which would show up in max framework displacement
        but doesn't constitute melting).

        Args:
            granularity (int): Number of structures to match
            matching_s (Structure): Optionally match to a disordered structure
                instead of the first structure in the analyzer. Required when
                a secondary mobile ion is present.
        """
        from pymatgen.util.plotting_utils import get_publication_quality_plot
        plt = get_publication_quality_plot(12, 8, plt=plt)
        step = (self.corrected_displacements.shape[1] - 1) // (granularity - 1)
        f = (matching_s or self.structure).copy()
        f.remove_species([self.specie])
        sm = StructureMatcher(primitive_cell=False,
                              stol=0.6,
                              comparator=OrderDisorderElementComparator(),
                              allow_subset=True)
        rms = []
        for s in self.get_drift_corrected_structures(step=step):
            s.remove_species([self.specie])
            d = sm.get_rms_dist(f, s)
            if d:
                rms.append(d)
            else:
                rms.append((1, 1))
        max_dt = (len(rms) - 1) * step * self.step_skip * self.time_step
        if max_dt > 100000:
            plot_dt = np.linspace(0, max_dt / 1000, len(rms))
            unit = 'ps'
        else:
            plot_dt = np.linspace(0, max_dt, len(rms))
            unit = 'fs'
        rms = np.array(rms)
        plt.plot(plot_dt, rms[:, 0], label='RMS')
        plt.plot(plot_dt, rms[:, 1], label='max')
        plt.legend(loc='best')
        plt.xlabel("Timestep ({})".format(unit))
        plt.ylabel("normalized distance")
        plt.tight_layout()
        return plt
Exemple #5
0
    def _calculate_energies(self):
        energies = np.zeros((len(self.structure), len(self.structure)))

        dm = self.structure.distance_matrix

        non_dup_sites = []
        blocked = []
        for i, site in enumerate(self.structure):
            if i in blocked:
                continue
            non_dup_sites.append(site)
            blocked.extend(np.where(dm[i] < 0.001)[0])

        cs = self.ce.supercell_from_structure(
            Structure.from_sites(non_dup_sites))
        sm = StructureMatcher(primitive_cell=False,
                              attempt_supercell=False,
                              allow_subset=True,
                              scale=True,
                              comparator=OrderDisorderElementComparator())

        aligned = sm.get_s2_like_s1(cs.supercell, self.structure)
        dists = aligned.lattice.get_all_distances(aligned.frac_coords,
                                                  cs.supercell.frac_coords)
        lw_mapping = np.argmin(dists, axis=-1)

        bits = cs.bits
        exp_inds = np.where(
            np.sum(aligned.distance_matrix < 0.001, axis=-1) > 1)[0]

        for i in exp_inds:
            for j in exp_inds:
                if lw_mapping[i] == lw_mapping[j]:
                    continue
                occu = np.copy(cs.nbits)
                occu[lw_mapping[i]] = bits[lw_mapping[i]].index(
                    str(aligned[i].specie))
                occu[lw_mapping[j]] = bits[lw_mapping[j]].index(
                    str(aligned[j].specie))
                energies[i][j] = np.dot(cs.corr_from_occupancy(occu),
                                        self.ecis)

        return energies
Exemple #6
0
    def occu_from_structure(self, structure, return_mapping=False):
        """
        Calculates the correlation vector. Structure must be on this supercell
        """
        #calculate mapping to supercell
        sm_no_sc = StructureMatcher(primitive_cell=False,
                                    attempt_supercell=False,
                                    allow_subset=True,
                                    comparator=OrderDisorderElementComparator(),
                                    supercell_size=self.cluster_expansion.supercell_size,
                                    scale=True,
                                    ltol=self.cluster_expansion.ltol,
                                    stol=self.cluster_expansion.stol,
                                    angle_tol=self.cluster_expansion.angle_tol)

        #print('sc:\n',self.supercell_matrix,\
        #      '\nstr:\n',self.cluster_expansion.supercell_matrix_from_structure(structure))
        mapping = sm_no_sc.get_mapping(self.supercell, structure)

        if mapping is None:
            raise ValueError('Structure cannot be mapped to this supercell. Structure:{}\nSupercell:{}'.format(structure,self.supercell))

        mapping = mapping.tolist()
        #cs.supercell[mapping] = structure
        occu = np.zeros(len(self.supercell), dtype=np.int)
        for i, bit in enumerate(self.bits):
            # print('i=',i)
            # print('bit=', bit)
            #rather than starting with all vacancies and looping
            #only over mapping, explicitly loop over everything to
            #catch vacancies on improper sites
            if i in mapping:
                sp = str(structure[mapping.index(i)].specie)
            else:
                sp = 'Vacancy'
            occu[i] = bit.index(sp)
        if not return_mapping:
            return occu
        else:
            return occu, mapping
Exemple #7
0
    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 = {}