Example #1
1
def get_unique_diffusion_pathways(structure: SymmetrizedStructure, dummy_atom: Element, site_i: int = -1,
                                  only_positive_direction=False, positive_weight=10, abreviated_search=1e6):
    if type(structure) != SymmetrizedStructure:
        try:
            sga = SpacegroupAnalyzer(structure, symprec=0.1)
            structure = sga.get_symmetrized_structure()
        except TypeError:
            sga = SpacegroupAnalyzer(structure, symprec=0.01)
            structure = sga.get_symmetrized_structure()

    equivalent_dummies = [ x for x in structure.equivalent_indices if structure[x[0]].specie == dummy_atom]
    all_combinations = itertools.product(*equivalent_dummies)
    # print(equivalent_dummies)
    combinations_to_check = np.prod([ float(len(x)) for x in equivalent_dummies])
    if combinations_to_check > abreviated_search:
        print(combinations_to_check)
        new_eq_dummies = [ [] for _ in equivalent_dummies ]
        radius = 0.1
        pt = structure.lattice.get_cartesian_coords([0.75,0.75,0.75])
        while not all( new_eq_dummies ):
            sites_in_sphere = structure.get_sites_in_sphere(pt, radius, include_index=True, include_image=True)
            sites = [ i for _,_,i,image in sites_in_sphere if all(image == (0,0,0)) ]
            new_eq_dummies = [ [y for y in x if y in sites] for x in equivalent_dummies ]
            radius = radius + 0.1
        equivalent_dummies = new_eq_dummies
        combinations_to_check = np.prod([float(len(x)) for x in equivalent_dummies])
        if combinations_to_check > abreviated_search:
            equivalent_dummies = random.sample(equivalent_dummies, abreviated_search)
        print(combinations_to_check)
    best_sites = equivalent_dummies*2 + [[]] + [[]]
    best_pathway = None
    most_overlap = 0
    best_weight = 9e9
    path_count = 0
    for dummy_is in all_combinations:
        # print(dummy_is)
        break_early = False
        path_count = path_count + 1
        sites = {(site_i, (0,0,0)): 0}
        pathway = []
        for i in dummy_is:
            neighbors = structure[i].properties['neighbors'].copy()
            image = structure[i].properties['image']
            if only_positive_direction and (-1 in image or -2 in image):
                break_early = True
                break
            neighbors[0] = (neighbors[0], (0,0,0))
            neighbors[1] = (neighbors[1], image)
            pathway.append(neighbors)
            for neighbor_i in neighbors:
                if neighbor_i in sites:
                    sites[neighbor_i] = sites[neighbor_i] + 1
                else:
                    sites[neighbor_i] = 1
        if only_positive_direction:
            if break_early:
                break_early = False
                continue
        cell_directions = [None,None,None]
        weight = 0
        for _, direction in sites.keys():  # make sure directions are consistent
            for i, d in enumerate(direction):
                if d: # if it is in a cell outside unit
                    if d > 0:
                        weight = weight + 1
                    elif d < 0:
                        weight = weight + positive_weight
                    # if not cell_directions[i]: # haven't looked outside unit cell in this direction yet
                    #     cell_directions[i] = d
                    # elif cell_directions[i] != d: # if the directions dont match
                    #     pass
                    #     break_early = True
                    #     break
                # else:
                #     weight = weight - positive_weight
            # if break_early:
            #     break
        # if break_early:
        #     continue
        if len(sites) < len(best_sites) or (len(sites) == len(best_sites) and most_overlap < sites[(site_i, (0,0,0))]):
            if weight <= best_weight:
                best_sites = sites
                best_pathway = pathway
                most_overlap = sites[(site_i, (0,0,0))]
                best_weight = weight

    return best_pathway
    def _get_data_from_single_dirc(dirc, src_str="str_relax.out",
                                   src_ene='energy'):
        """
        指定したdircから構造とエネルギーを読み取る
        """
        src = os.path.join(dirc, src_str)
        strout = StrOut.from_file(src)

        src = os.path.join(dirc, src_ene)
        with open(src, 'r') as rfile:
            lines = rfile.readlines()
        num_atoms = sum(strout.structure.composition.
                        to_data_dict['unit_cell_composition'].values())
        energy = float(lines[0]) / num_atoms

        analyzer = SpacegroupAnalyzer(strout.structure)
        #std_prim = analyzer.get_primitive_standard_structure()
        std_str = analyzer.get_conventional_standard_structure()
        analyzer = SpacegroupAnalyzer(std_str)
        wyckoffs = analyzer.get_symmetry_dataset()['wyckoffs']
        formula = std_str.composition.to_data_dict['unit_cell_composition']

        symbol_spg = analyzer.get_spacegroup_symbol()
        num_spg = analyzer.get_spacegroup_number()
        spg = [symbol_spg, num_spg]

        lattice = std_str.as_dict()['lattice']

        equiv_sites = analyzer.get_symmetrized_structure().equivalent_sites
        equiv_indices = analyzer.get_symmetrized_structure().equivalent_indices
        # Wycoffs labelと組み合わせたsites_groupのlistを作る
        sites_and_wyckoffs = []
        for eq_s, eq_i in zip(equiv_sites, equiv_indices):
            sites_and_wyckoffs.append({'wyckoffs': wyckoffs[eq_i[0]],
                                       'site_grp': eq_s})
        # check
            for i in range(len(eq_i)-1):
                if wyckoffs[eq_i[i]] != wyckoffs[eq_i[i+1]] or \
                           len(eq_s) != len(eq_i):
                    print("wyckoffs label is wrong !!!!")
                    print(wyckoffs)
                    print(eq_i)
                    print(len(eq_s))
                    print(dirc)
                    exit()
        return {'formula': formula, 'lattice': lattice, 'spg': spg,
                'sites_and_wyckoffs': sites_and_wyckoffs, 'energy': energy,
                'str_id': os.path.basename(dirc)}
Example #3
0
    def __init__(self, structure, element):
        """
        Initializes an Interstitial generator using Voronoi sites
        Args:
            structure (Structure): pymatgen structure object
            element (str or Element or Specie): element for the interstitial
        """
        self.structure = structure
        self.element = element

        framework = list(self.structure.symbol_set)
        get_voronoi = TopographyAnalyzer(self.structure, framework, [], check_volume=False)
        get_voronoi.cluster_nodes()
        get_voronoi.remove_collisions()

        # trim equivalent nodes with symmetry analysis
        struct_to_trim = self.structure.copy()
        for poss_inter in get_voronoi.vnodes:
            struct_to_trim.append(self.element, poss_inter.frac_coords, coords_are_cartesian=False)

        symmetry_finder = SpacegroupAnalyzer(struct_to_trim, symprec=1e-1)
        equiv_sites_list = symmetry_finder.get_symmetrized_structure().equivalent_sites

        self.equiv_site_seq = []
        for poss_site_list in equiv_sites_list:
            if poss_site_list[0] not in self.structure:
                self.equiv_site_seq.append(poss_site_list)

        self.count_def = 0  # for counting the index of the generated defect
Example #4
0
    def __init__(self, structure, element):
        """
        Initializes a Substitution Generator
        note: an Antisite is considered a type of substitution
        Args:
            structure(Structure): pymatgen structure object
            element (str or Element or Specie): element for the substitution
        """
        self.structure = structure
        self.element = element

        # Find equivalent site list
        sga = SpacegroupAnalyzer(self.structure)
        self.symm_structure = sga.get_symmetrized_structure()

        self.equiv_sub = []
        for equiv_site_set in list(self.symm_structure.equivalent_sites):
            vac_site = equiv_site_set[0]
            if isinstance(element, str):  # make sure you compare with specie symbol or Element type
                vac_specie = vac_site.specie.symbol
            else:
                vac_specie = vac_site.specie
            if element != vac_specie:
                defect_site = PeriodicSite(element, vac_site.coords, structure.lattice, coords_are_cartesian=True)
                sub = Substitution(structure, defect_site)
                self.equiv_sub.append(sub)
Example #5
0
    def multiplicity(self):
        """
        Returns the multiplicity of a defect site within the structure (needed for concentration analysis)
        """
        if self._multiplicity is None:
            # generate multiplicity based on space group symmetry operations performed on defect coordinates
            try:
                d_structure = create_saturated_interstitial_structure(self)
            except ValueError:
                logger.debug('WARNING! Multiplicity was not able to be calculated adequately '
                             'for interstitials...setting this to 1 and skipping for now...')
                return 1

            sga = SpacegroupAnalyzer(d_structure)
            periodic_struc = sga.get_symmetrized_structure()
            poss_deflist = sorted(
                periodic_struc.get_sites_in_sphere(self.site.coords, 2, include_index=True),
                key=lambda x: x[1])
            defindex = poss_deflist[0][2]

            equivalent_sites = periodic_struc.find_equivalent_sites(periodic_struc[defindex])
            return len(equivalent_sites)

        else:
            return self._multiplicity
Example #6
0
    def multiplicity(self):
        """
        Returns the multiplicity of a defect site within the structure (needed for concentration analysis)
        """
        sga = SpacegroupAnalyzer(self.bulk_structure)
        periodic_struc = sga.get_symmetrized_structure()
        poss_deflist = sorted(
            periodic_struc.get_sites_in_sphere(self.site.coords, 2, include_index=True), key=lambda x: x[1])
        defindex = poss_deflist[0][2]

        equivalent_sites = periodic_struc.find_equivalent_sites(self.bulk_structure[defindex])
        return len(equivalent_sites)
Example #7
0
def sulfide_type(structure):
    """
    Determines if a structure is a sulfide/polysulfide

    Args:
        structure (Structure): Input structure.

    Returns:
        (str) sulfide/polysulfide/sulfate
    """
    structure = structure.copy()
    structure.remove_oxidation_states()
    s = Element("S")
    comp = structure.composition
    if comp.is_element or s not in comp:
        return None

    finder = SpacegroupAnalyzer(structure, symprec=0.1)
    symm_structure = finder.get_symmetrized_structure()
    s_sites = [sites[0] for sites in symm_structure.equivalent_sites if
               sites[0].specie == s]

    def process_site(site):

        # in an exceptionally rare number of structures, the search
        # radius needs to be increased to find a neighbor atom
        search_radius = 4
        neighbors = []
        while len(neighbors) == 0:
            neighbors = structure.get_neighbors(site, search_radius)
            search_radius *= 2
            if search_radius > max(structure.lattice.abc)*2:
                break

        neighbors = sorted(neighbors, key=lambda n: n[1])
        nn, dist = neighbors[0]
        coord_elements = [site.specie for site, d in neighbors
                          if d < dist + 0.4][:4]
        avg_electroneg = np.mean([e.X for e in coord_elements])
        if avg_electroneg > s.X:
            return "sulfate"
        elif avg_electroneg == s.X and s in coord_elements:
            return "polysulfide"
        else:
            return "sulfide"

    types = set([process_site(site) for site in s_sites])
    if "sulfate" in types:
        return None
    elif "polysulfide" in types:
        return "polysulfide"
    else:
        return "sulfide"
Example #8
0
def remove_unstable_interstitials(structure: Structure, relaxed_interstitials: list, dist=0.2, site_indices=None):
    """

    :param structure: Structure decorated with all interstitials
    :param relaxed_interstitials: list of structures with interstitial as last index
    :param dist: tolerance for determining if site belongs to another site
    :return:
    """
    to_keep = list(range(len(relaxed_interstitials[0])-1))
    try:
        sga = SpacegroupAnalyzer(structure, symprec=0.1)
        structure = sga.get_symmetrized_structure()
    except TypeError:
        sga = SpacegroupAnalyzer(structure, symprec=0.01)
        structure = sga.get_symmetrized_structure()
    for ri in relaxed_interstitials:  #type:  Structure
        sites=structure.get_sites_in_sphere(ri.cart_coords[-1], dist, include_index=True)

        for indices in structure.equivalent_indices:  #look at all sets of equivalent indices
            index = sites[0][2]
            if index in to_keep: # Already keeping this index
                continue
            if index in indices:
                to_keep = to_keep + indices  #keep equivalent indices
                break

        if len(sites) != 1: # make sure only one site is found
            okay = False
            if len(sites) > 1:
                if all([ x[2] in indices for x in sites]):
                    okay = True
            if not okay:
                if site_indices:
                    raise Exception('Found {} sites for {}'.format(len(sites), site_indices[relaxed_interstitials.index(ri)]))
                raise Exception('Found {} sites'.format(len(sites)))
    to_remove = [i for i in range(len(structure)) if i not in to_keep]
    structure.remove_sites(to_remove)
    return structure
def add_voronoi(structure):

    """
    Args:
    :param structure1: (Structure) target structure
    :return structure3: (Structure) structure with all Voronoi points added to the structure
    """

    #choose an element that is not present in any of the structures to use to identify the voronoi sites
    transformer = VoronoiInsertionTransformation("Rn", midpoints=True)
    structure_with_voronoi = transformer.apply_transformation(structure)

    findsim=SpacegroupAnalyzer(structure_with_voronoi, symprec=1e-1)
    symmetrized_structure_with_voronoi = findsim.get_symmetrized_structure()

    return symmetrized_structure_with_voronoi
Example #10
0
    def analyze_symmetry(self, tol):
        s = Structure.from_sites(self.framework)
        site_to_vindex = {}
        for i, v in enumerate(self.vnodes):
            s.append("Li", v.frac_coords)
            site_to_vindex[s[-1]] = i

        print(len(s))
        finder = SpacegroupAnalyzer(s, tol)
        print(finder.get_space_group_operations())
        symm_structure = finder.get_symmetrized_structure()
        print(len(symm_structure.equivalent_sites))
        return [[site_to_vindex[site]
                 for site in sites]
                for sites in symm_structure.equivalent_sites
                if sites[0].specie.symbol == "Li"]
Example #11
0
 def get_multiplicity(self):
     """
     Returns the multiplicity of a defect site within the structure (needed for concentration analysis)
     and confirms that defect_site is a site in bulk_structure.
     """
     sga = SpacegroupAnalyzer(self.bulk_structure)
     periodic_struc = sga.get_symmetrized_structure()
     poss_deflist = sorted(
         periodic_struc.get_sites_in_sphere(self.site.coords, 0.1, include_index=True), key=lambda x: x[1])
     if not len(poss_deflist):
         raise ValueError("Site {} is not in bulk structure! Cannot create Substitution object.".format( self.site))
     else:
         defindex = poss_deflist[0][2]
         defect_site = self.bulk_structure[defindex]
         equivalent_sites = periodic_struc.find_equivalent_sites(defect_site)
         return len(equivalent_sites)
Example #12
0
    def __init__(self, structure, include_bv_charge=False):
        """
        Initializes a Vacancy Generator
        Args:
            structure(Structure): pymatgen structure object
        """
        self.structure = structure
        self.include_bv_charge = include_bv_charge

        # Find equivalent site list
        sga = SpacegroupAnalyzer(self.structure)
        self.symm_structure = sga.get_symmetrized_structure()
        self.equiv_site_seq = list(self.symm_structure.equivalent_sites)

        self.struct_valences = None
        if self.include_bv_charge:
            bv = BVAnalyzer()
            self.struct_valences = bv.get_valences(self.structure)
Example #13
0
def sulfide_type(structure):
    """
    Determines if a structure is a sulfide/polysulfide

    Args:
        structure (Structure): Input structure.

    Returns:
        (str) sulfide/polysulfide/sulfate
    """
    structure = structure.copy()
    structure.remove_oxidation_states()
    s = Element("S")
    comp = structure.composition
    if comp.is_element or s not in comp:
        return None

    finder = SpacegroupAnalyzer(structure, symprec=0.1)
    symm_structure = finder.get_symmetrized_structure()
    s_sites = [sites[0] for sites in symm_structure.equivalent_sites if
               sites[0].specie == s]

    def process_site(site):
        neighbors = structure.get_neighbors(site, 4)
        neighbors = sorted(neighbors, key=lambda n: n[1])
        nn, dist = neighbors[0]
        coord_elements = [site.specie for site, d in neighbors
                          if d < dist + 0.4][:4]
        avg_electroneg = np.mean([e.X for e in coord_elements])
        if avg_electroneg > s.X:
            return "sulfate"
        elif avg_electroneg == s.X and s in coord_elements:
            return "polysulfide"
        else:
            return "sulfide"

    types = set([process_site(site) for site in s_sites])
    if "sulfate" in types:
        return None
    elif "polysulfide" in types:
        return "polysulfide"
    else:
        return "sulfide"
Example #14
0
 def get_multiplicity(self):
     """
     Returns the multiplicity of a defect site within the structure (needed for concentration analysis)
     and confirms that defect_site is a site in bulk_structure.
     """
     sga = SpacegroupAnalyzer(self.bulk_structure)
     periodic_struc = sga.get_symmetrized_structure()
     poss_deflist = sorted(
         periodic_struc.get_sites_in_sphere(self.site.coords,
                                            0.1,
                                            include_index=True),
         key=lambda x: x[1],
     )
     if not len(poss_deflist):
         raise ValueError(
             "Site {} is not in bulk structure! Cannot create Vacancy object."
             .format(self.site))
     defindex = poss_deflist[0][2]
     defect_site = self.bulk_structure[defindex]
     equivalent_sites = periodic_struc.find_equivalent_sites(defect_site)
     return len(equivalent_sites)
Example #15
0
    def __init__(self, structure, element):
        """
        Initializes an Interstitial generator using Voronoi sites
        Args:
            structure (Structure): pymatgen structure object
            element (str or Element or Specie): element for the interstitial
        """
        self.structure = structure
        self.element = element

        framework = list(self.structure.symbol_set)
        get_voronoi = TopographyAnalyzer(self.structure, framework, [], check_volume=False)
        get_voronoi.cluster_nodes()
        get_voronoi.remove_collisions()

        # trim equivalent nodes with symmetry analysis
        struct_to_trim = self.structure.copy()
        for poss_inter in get_voronoi.vnodes:
            struct_to_trim.append(self.element, poss_inter.frac_coords, coords_are_cartesian=False)

        symmetry_finder = SpacegroupAnalyzer(struct_to_trim, symprec=1e-1)
        equiv_sites_list = symmetry_finder.get_symmetrized_structure().equivalent_sites

        # do additional screening for sublattice equivalent
        # defects which may have slipped through
        pdc = PointDefectComparator()
        self.unique_defect_seq = []
        for poss_site_list in equiv_sites_list:
            poss_site = poss_site_list[0]
            if poss_site not in self.structure:
                now_defect = Interstitial( self.structure, poss_site)
                append_defect = True
                for unique_defect in self.unique_defect_seq:
                    if pdc.are_equal( now_defect, unique_defect):
                        append_defect = False
                if append_defect:
                    self.unique_defect_seq.append( now_defect)

        self.count_def = 0  # for counting the index of the generated defect
Example #16
0
 def get_defects_concentration_old(self, temp=300, ef=0.0):
     """
     get the defect concentration for a temperature and Fermi level
     Written when the site multiplicity is not supplied with ComputedDefect
     Args:
         temp:
             the temperature in K
         Ef:
             the fermi level in eV (with respect to the VBM)
     Returns:
         a list of dict of {'name': defect name, 'charge': defect charge
                            'conc': defects concentration in m-3}
     """
     conc = []
     spga = SpacegroupAnalyzer(self._entry_bulk.structure, symprec=1e-1)
     struct = spga.get_symmetrized_structure()
     i = 0
     for d in self._defects:
         df_coords = d.site.frac_coords
         target_site = None
         for s in struct.sites:
             sf_coords = s.frac_coords
             if abs(s.frac_coords[0]-df_coords[0]) < 0.1 \
                     and abs(s.frac_coords[1]-df_coords[1]) < 0.1 \
                     and abs(s.frac_coords[2]-df_coords[2]) < 0.1:
                 target_site = s
                 break
         equiv_site_no = len(struct.find_equivalent_sites(target_site))
         n = equiv_site_no * 1e30 / struct.volume
         conc.append({
             'name':
             d.name,
             'charge':
             d.charge,
             'conc':
             n * exp(-self._get_form_energy(ef, i) / (kb * temp))
         })
         i += 1
     return conc
Example #17
0
    def transform_one(self, structure: Structure) -> pd.DataFrame:
        """

        Args:
            structure (pymatgen Structure): pymatgen structure for descriptor computation

        Returns:
            pd.DataFrame that contains the distinct position labeled features

        """
        a = SpacegroupAnalyzer(structure, self.symprec)
        symm = a.get_symmetrized_structure()
        data = []
        names = []
        if self.wyckoffs is None:
            self.wyckoffs = symm.wyckoff_symbols
        for w in self.wyckoffs:
            site = symm.equivalent_sites[symm.wyckoff_symbols.index(w)][0]
            for p in self.properties:
                data.append(getattr(site.specie, p))
                names.append("%s-%s" % (w, p))
        return pd.DataFrame([data], columns=names)
Example #18
0
    def make_one_substitution(self, supercell):
        sga = SpacegroupAnalyzer(supercell)
        symm_structure = sga.get_symmetrized_structure()
        substitution_site_coords = []

        for equiv_site in list(symm_structure.equivalent_sites):
            if self.atom_to_substitute == equiv_site[0].species.__str__(
            ).replace('1', ''):
                substitution_site_coords.append(equiv_site[0].coords)

        substituted_structures = [
            copy.deepcopy(supercell) for _ in substitution_site_coords
        ]

        for i, struct in enumerate(substituted_structures):
            for no, site in enumerate(struct.__dict__['_sites']):
                if np.array_equal(site.__dict__['_coords'],
                                  substitution_site_coords[i]):
                    site.__dict__["_species"] = Composition(
                        self.atom_substitute_to)

        return substituted_structures
Example #19
0
 def __init__(self,
              structure,
              migrating_specie,
              max_path_length=5,
              symprec=0.1):
     """
     Args:
         structure: Input structure that contains all sites.
         migrating_specie (Specie-like): The specie that migrates. E.g., 
             "Li".
         max_path_length (float): Maximum length of NEB path. Defaults to 5
             Angstrom. Usually, you'd want to set this close to the longest
             lattice parameter / diagonal in the cell to ensure all paths
             are found.
         symprec (float): Symmetry precision to determine equivalence. 
     """
     self.structure = structure
     self.migrating_specie = get_el_sp(migrating_specie)
     self.max_path_length = max_path_length
     self.symprec = symprec
     a = SpacegroupAnalyzer(self.structure, symprec=self.symprec)
     self.symm_structure = a.get_symmetrized_structure()
Example #20
0
    def __init__(self, structure, element):
        """
        Initializes an Interstitial generator using Voronoi sites
        Args:
            structure (Structure): pymatgen structure object
            element (str or Element or Species): element for the interstitial
        """
        self.structure = structure
        self.element = element

        framework = list(self.structure.symbol_set)
        get_voronoi = TopographyAnalyzer(self.structure, framework, [], check_volume=False)
        get_voronoi.cluster_nodes()
        get_voronoi.remove_collisions()

        # trim equivalent nodes with symmetry analysis
        struct_to_trim = self.structure.copy()
        for poss_inter in get_voronoi.vnodes:
            struct_to_trim.append(self.element, poss_inter.frac_coords, coords_are_cartesian=False)

        symmetry_finder = SpacegroupAnalyzer(struct_to_trim, symprec=1e-1)
        equiv_sites_list = symmetry_finder.get_symmetrized_structure().equivalent_sites

        # do additional screening for sublattice equivalent
        # defects which may have slipped through
        pdc = PointDefectComparator()
        self.unique_defect_seq = []
        for poss_site_list in equiv_sites_list:
            poss_site = poss_site_list[0]
            if poss_site not in self.structure:
                now_defect = Interstitial(self.structure, poss_site)
                append_defect = True
                for unique_defect in self.unique_defect_seq:
                    if pdc.are_equal(now_defect, unique_defect):
                        append_defect = False
                if append_defect:
                    self.unique_defect_seq.append(now_defect)

        self.count_def = 0  # for counting the index of the generated defect
Example #21
0
    def number_of_unique_magnetic_sites(self, symprec=1e-3, angle_tolerance=5):
        """
        :param symprec (float): same as in SpacegroupAnalyzer
        :param angle_tolerance (float): same as in SpacegroupAnalyzer
        :return (int): Number of symmetrically-distinct magnetic sites present
        in structure.
        """

        structure = self.get_nonmagnetic_structure()

        sga = SpacegroupAnalyzer(structure, symprec=symprec,
                                 angle_tolerance=angle_tolerance)

        symm_structure = sga.get_symmetrized_structure()

        num_unique_mag_sites = 0

        for group_of_sites in symm_structure.equivalent_sites:
            if group_of_sites[0].specie in self.types_of_magnetic_specie:
                num_unique_mag_sites += 1

        return num_unique_mag_sites
Example #22
0
    def number_of_unique_magnetic_sites(self, symprec=1e-3, angle_tolerance=5):
        """
        :param symprec (float): same as in SpacegroupAnalyzer
        :param angle_tolerance (float): same as in SpacegroupAnalyzer
        :return (int): Number of symmetrically-distinct magnetic sites present
        in structure.
        """

        structure = self.get_nonmagnetic_structure()

        sga = SpacegroupAnalyzer(structure, symprec=symprec,
                                 angle_tolerance=angle_tolerance)

        symm_structure = sga.get_symmetrized_structure()

        num_unique_mag_sites = 0

        for group_of_sites in symm_structure.equivalent_sites:
            if group_of_sites[0].specie in self.types_of_magnetic_specie:
                num_unique_mag_sites += 1

        return num_unique_mag_sites
Example #23
0
def get_chgint_plot(args):
    chgcar = Chgcar.from_file(args.chgcar_file)
    s = chgcar.structure

    if args.inds:
        atom_ind = [int(i) for i in args.inds[0].split(",")]
    else:
        finder = SpacegroupAnalyzer(s, symprec=0.1)
        sites = [sites[0] for sites in
                 finder.get_symmetrized_structure().equivalent_sites]
        atom_ind = [s.sites.index(site) for site in sites]

    from pymatgen.util.plotting_utils import get_publication_quality_plot
    plt = get_publication_quality_plot(12, 8)
    for i in atom_ind:
        d = chgcar.get_integrated_diff(i, args.radius, 30)
        plt.plot(d[:, 0], d[:, 1],
                 label="Atom {} - {}".format(i, s[i].species_string))
    plt.legend(loc="upper left")
    plt.xlabel("Radius (A)")
    plt.ylabel("Integrated charge (e)")
    plt.tight_layout()
    return plt
Example #24
0
def get_chgint_plot(args):
    chgcar = Chgcar.from_file(args.chgcar_file)
    s = chgcar.structure

    if args.inds:
        atom_ind = [int(i) for i in args.inds[0].split(",")]
    else:
        finder = SpacegroupAnalyzer(s, symprec=0.1)
        sites = [sites[0] for sites in
                 finder.get_symmetrized_structure().equivalent_sites]
        atom_ind = [s.sites.index(site) for site in sites]

    from pymatgen.util.plotting import pretty_plot
    plt = pretty_plot(12, 8)
    for i in atom_ind:
        d = chgcar.get_integrated_diff(i, args.radius, 30)
        plt.plot(d[:, 0], d[:, 1],
                 label="Atom {} - {}".format(i, s[i].species_string))
    plt.legend(loc="upper left")
    plt.xlabel("Radius (A)")
    plt.ylabel("Integrated charge (e)")
    plt.tight_layout()
    return plt
Example #25
0
def get_equiv_site_list(structure):
    """
    print a list of the equivalent sites of a structure
    with one representative position (mainIndex)
    the list on all the indices , the element on the site and the
    multiplicity
    """

    anal = SpacegroupAnalyzer(structure, symprec=0.05)
    sym_struct = anal.get_symmetrized_structure()
    sym_list = sym_struct.equivalent_indices
    equivalent_sites = []
    for k in range(0, len(sym_list), 1):
        # ACHTUNG ! : Position in the structure start at 0
        equivalent_sites.append({
            'mainIndex': sym_list[k][0],
            'indices': sym_list[k],
            'element': structure[sym_list[k][0]].species_string,
            'multiplicity': len(sym_list[k]),
            'charge': -1,
            'magnetization': -1
        })
    return equivalent_sites
Example #26
0
    def get_multiplicity(self):
        """
        Returns the multiplicity of a defect site within the structure (needed for concentration analysis)
        """
        try:
            d_structure = create_saturated_interstitial_structure(self)
        except ValueError:
            logger.debug(
                "WARNING! Multiplicity was not able to be calculated adequately "
                "for interstitials...setting this to 1 and skipping for now..."
            )
            return 1

        sga = SpacegroupAnalyzer(d_structure)
        periodic_struc = sga.get_symmetrized_structure()
        poss_deflist = sorted(
            periodic_struc.get_sites_in_sphere(self.site.coords, 0.1, include_index=True),
            key=lambda x: x[1],
        )
        defindex = poss_deflist[0][2]

        equivalent_sites = periodic_struc.find_equivalent_sites(periodic_struc[defindex])
        return len(equivalent_sites)
Example #27
0
def get_surface_sites(slab, tol, tag=True):
    """
    Returns the surface sites and their indices in a dictionary. The
    oriented unit cell of the slab will determine the coordination number
    of a typical site. We use VoronoiNN to determine the
    coordination number of bulk sites and slab sites. Due to the
    pathological error resulting from some surface sites in the
    VoronoiNN, we assume any site that has this error is a surface
    site as well. This will work for elemental systems only for now. Useful
    for analysis involving broken bonds and for finding adsorption sites.
        Args:
            tag (bool): Option to adds site attribute "is_surfsite" (bool)
                to all sites of slab. Defaults to False
        Returns:
            A dictionary grouping sites on top and bottom of the slab
            together.
            {"top": [sites with indices], "bottom": [sites with indices}
    TODO:
        Is there a way to determine site equivalence between sites in a slab
        and bulk system? This would allow us get the coordination number of
        a specific site for multi-elemental systems or systems with more
        than one unequivalent site. This will allow us to use this for
        compound systems.
    """

    from pymatgen.analysis.local_env import VoronoiNN

    # Get a dictionary of coordination numbers
    # for each distinct site in the structure
    a = SpacegroupAnalyzer(slab.oriented_unit_cell)
    ucell = a.get_symmetrized_structure()
    cn_dict = {}
    v = VoronoiNN(tol=tol)
    unique_indices = [equ[0] for equ in ucell.equivalent_indices]

    for i in unique_indices:
        el = ucell[i].species_string
        if el not in cn_dict.keys():
            cn_dict[el] = []
        # Since this will get the cn as a result of the weighted polyhedra, the
        # slightest difference in cn will indicate a different environment for a
        # species, eg. bond distance of each neighbor or neighbor species. The
        # decimal place to get some cn to be equal.
        cn = v.get_cn(ucell, i, use_weights=True)
        cn = float('%.5f' % (round(cn, 5)))
        if cn not in cn_dict[el]:
            cn_dict[el].append(cn)

    v = VoronoiNN(tol=tol)

    surf_sites_dict, properties = {"top": [], "bottom": []}, [],
    for i, site in enumerate(slab):
        # Determine if site is closer to the top or bottom of the slab
        top = site.frac_coords[2] > slab.center_of_mass[2]

        cn = float('%.5f' % (round(v.get_cn(slab, i, use_weights=True), 5)))
        try:
            # A site is a surface site, if its environment does
            # not fit the environment of other sites
            cn = float('%.5f' %
                       (round(v.get_cn(slab, i, use_weights=True), 5)))
            if cn < min(cn_dict[site.species_string]):
                properties.append(True)
                key = "top" if top else "bottom"
                surf_sites_dict[key].append([site, i])
            else:
                properties.append(False)
        except RuntimeError:
            # or if pathological error is returned, indicating a surface site
            properties.append(True)
            key = "top" if top else "bottom"
            surf_sites_dict[key].append([site, i])

    if tag:
        slab.add_site_property("is_surf_site", properties)
    return surf_sites_dict
Example #28
0
    def _generate_transformations(self, structure):
        """
        The central problem with trying to enumerate magnetic orderings is
        that we have to enumerate orderings that might plausibly be magnetic
        ground states, while not enumerating orderings that are physically
        implausible. The problem is that it is not always obvious by e.g.
        symmetry arguments alone which orderings to prefer. Here, we use a
        variety of strategies (heuristics) to enumerate plausible orderings,
        and later discard any duplicates that might be found by multiple
        strategies. This approach is not ideal, but has been found to be
        relatively robust over a wide range of magnetic structures.
        Args:
            structure: A sanitized input structure (_sanitize_input_structure)
        Returns: A dict of a transformation class instance (values) and name of
        enumeration strategy (keys)
        """

        formula = structure.composition.reduced_formula
        transformations = {}

        # analyzer is used to obtain information on sanitized input
        analyzer = CollinearMagneticStructureAnalyzer(
            structure,
            default_magmoms=self.default_magmoms,
            overwrite_magmom_mode="replace_all",
        )

        if not analyzer.is_magnetic:
            raise ValueError(
                "Not detected as magnetic, add a new default magmom for the "
                "element you believe may be magnetic?")

        # now we can begin to generate our magnetic orderings
        self.logger.info(
            "Generating magnetic orderings for {}".format(formula))

        mag_species_spin = analyzer.magnetic_species_and_magmoms
        types_mag_species = sorted(
            analyzer.types_of_magnetic_specie,
            key=lambda sp: analyzer.default_magmoms.get(str(sp), 0),
            reverse=True,
        )
        num_mag_sites = analyzer.number_of_magnetic_sites
        num_unique_sites = analyzer.number_of_unique_magnetic_sites()

        # enumerations become too slow as number of unique sites (and thus
        # permutations) increase, 8 is a soft limit, this can be increased
        # but do so with care
        if num_unique_sites > self.max_unique_sites:
            raise ValueError(
                "Too many magnetic sites to sensibly perform enumeration.")

        # maximum cell size to consider: as a rule of thumb, if the primitive cell
        # contains a large number of magnetic sites, perhaps we only need to enumerate
        # within one cell, whereas on the other extreme if the primitive cell only
        # contains a single magnetic site, we have to create larger supercells
        if "max_cell_size" not in self.transformation_kwargs:
            # TODO: change to 8 / num_mag_sites ?
            self.transformation_kwargs["max_cell_size"] = max(
                1, int(4 / num_mag_sites))
        self.logger.info("Max cell size set to {}".format(
            self.transformation_kwargs["max_cell_size"]))

        # when enumerating ferrimagnetic structures, it's useful to detect
        # symmetrically distinct magnetic sites, since different
        # local environments can result in different magnetic order
        # (e.g. inverse spinels)
        # initially, this was done by co-ordination number, but is
        # now done by a full symmetry analysis
        sga = SpacegroupAnalyzer(structure)
        structure_sym = sga.get_symmetrized_structure()
        wyckoff = ["n/a"] * len(structure)
        for indices, symbol in zip(structure_sym.equivalent_indices,
                                   structure_sym.wyckoff_symbols):
            for index in indices:
                wyckoff[index] = symbol
        is_magnetic_sites = [
            True if site.specie in types_mag_species else False
            for site in structure
        ]
        # we're not interested in sites that we don't think are magnetic,
        # set these symbols to None to filter them out later
        wyckoff = [
            symbol if is_magnetic_site else "n/a"
            for symbol, is_magnetic_site in zip(wyckoff, is_magnetic_sites)
        ]
        structure.add_site_property("wyckoff", wyckoff)
        wyckoff_symbols = set(wyckoff) - {"n/a"}

        # if user doesn't specifically request ferrimagnetic_Cr2NiO4 orderings,
        # we apply a heuristic as to whether to attempt them or not
        if self.automatic:
            if ("ferrimagnetic_by_motif" not in self.strategies
                    and len(wyckoff_symbols) > 1
                    and len(types_mag_species) == 1):
                self.strategies += ("ferrimagnetic_by_motif", )

            if ("antiferromagnetic_by_motif" not in self.strategies
                    and len(wyckoff_symbols) > 1
                    and len(types_mag_species) == 1):
                self.strategies += ("antiferromagnetic_by_motif", )

            if ("ferrimagnetic_by_species" not in self.strategies
                    and len(types_mag_species) > 1):
                self.strategies += ("ferrimagnetic_by_species", )

        # we start with a ferromagnetic ordering
        if "ferromagnetic" in self.strategies:

            # TODO: remove 0 spins !

            fm_structure = analyzer.get_ferromagnetic_structure()
            # store magmom as spin property, to be consistent with output from
            # other transformations
            fm_structure.add_spin_by_site(
                fm_structure.site_properties["magmom"])
            fm_structure.remove_site_property("magmom")

            # we now have our first magnetic ordering...
            self.ordered_structures.append(fm_structure)
            self.ordered_structure_origins.append("fm")

        # we store constraint(s) for each strategy first,
        # and then use each to perform a transformation later
        all_constraints = {}

        # ...to which we can add simple AFM cases first...
        if "antiferromagnetic" in self.strategies:

            constraint = MagOrderParameterConstraint(
                0.5,
                # TODO: update MagOrderParameterConstraint in pymatgen to take types_mag_species directly
                species_constraints=list(map(str, types_mag_species)),
            )
            all_constraints["afm"] = [constraint]

            # allows for non-magnetic sublattices
            if len(types_mag_species) > 1:
                for sp in types_mag_species:

                    constraints = [
                        MagOrderParameterConstraint(
                            0.5, species_constraints=str(sp))
                    ]

                    all_constraints["afm_by_{}".format(sp)] = constraints

        # ...and then we also try ferrimagnetic orderings by motif if a
        # single magnetic species is present...
        if "ferrimagnetic_by_motif" in self.strategies and len(
                wyckoff_symbols) > 1:

            # these orderings are AFM on one local environment, and FM on the rest
            for symbol in wyckoff_symbols:

                constraints = [
                    MagOrderParameterConstraint(0.5,
                                                site_constraint_name="wyckoff",
                                                site_constraints=symbol),
                    MagOrderParameterConstraint(
                        1.0,
                        site_constraint_name="wyckoff",
                        site_constraints=list(wyckoff_symbols - {symbol}),
                    ),
                ]

                all_constraints["ferri_by_motif_{}".format(
                    symbol)] = constraints

        # and also try ferrimagnetic when there are multiple magnetic species
        if "ferrimagnetic_by_species" in self.strategies:

            sp_list = [str(site.specie) for site in structure]
            num_sp = {sp: sp_list.count(str(sp)) for sp in types_mag_species}
            total_mag_sites = sum(num_sp.values())

            for sp in types_mag_species:

                # attempt via a global order parameter
                all_constraints["ferri_by_{}".format(
                    sp)] = num_sp[sp] / total_mag_sites

                # attempt via afm on sp, fm on remaining species

                constraints = [
                    MagOrderParameterConstraint(0.5,
                                                species_constraints=str(sp)),
                    MagOrderParameterConstraint(
                        1.0,
                        species_constraints=list(
                            map(str,
                                set(types_mag_species) - {sp})),
                    ),
                ]

                all_constraints["ferri_by_{}_afm".format(sp)] = constraints

        # ...and finally, we can try orderings that are AFM on one local
        # environment, and non-magnetic on the rest -- this is less common
        # but unless explicitly attempted, these states are unlikely to be found
        if "antiferromagnetic_by_motif" in self.strategies:

            for symbol in wyckoff_symbols:

                constraints = [
                    MagOrderParameterConstraint(0.5,
                                                site_constraint_name="wyckoff",
                                                site_constraints=symbol)
                ]

                all_constraints["afm_by_motif_{}".format(symbol)] = constraints

        # and now construct all our transformations for each strategy
        transformations = {}
        for name, constraints in all_constraints.items():

            trans = MagOrderingTransformation(mag_species_spin,
                                              order_parameter=constraints,
                                              **self.transformation_kwargs)

            transformations[name] = trans

        return transformations
Example #29
0
    def __init__(self, struct, symprec=None):
        format_str = "{:.8f}"

        block = OrderedDict()
        loops = []
        spacegroup = ("P 1", 1)
        if symprec is not None:
            sf = SpacegroupAnalyzer(struct, symprec)
            spacegroup = (sf.get_space_group_symbol(),
                          sf.get_space_group_number())
            # Needs the refined struture when using symprec. This converts
            # primitive to conventional structures, the standard for CIF.
            struct = sf.get_refined_structure()

        latt = struct.lattice
        comp = struct.composition
        no_oxi_comp = comp.element_composition
        block["_symmetry_space_group_name_H-M"] = spacegroup[0]
        for cell_attr in ['a', 'b', 'c']:
            block["_cell_length_" + cell_attr] = format_str.format(
                getattr(latt, cell_attr))
        for cell_attr in ['alpha', 'beta', 'gamma']:
            block["_cell_angle_" + cell_attr] = format_str.format(
                getattr(latt, cell_attr))
        block["_symmetry_Int_Tables_number"] = spacegroup[1]
        block["_chemical_formula_structural"] = no_oxi_comp.reduced_formula
        block["_chemical_formula_sum"] = no_oxi_comp.formula
        block["_cell_volume"] = latt.volume.__str__()

        reduced_comp, fu = no_oxi_comp.get_reduced_composition_and_factor()
        block["_cell_formula_units_Z"] = str(int(fu))

        if symprec is None:
            block["_symmetry_equiv_pos_site_id"] = ["1"]
            block["_symmetry_equiv_pos_as_xyz"] = ["x, y, z"]
        else:
            sf = SpacegroupAnalyzer(struct, symprec)

            def round_symm_trans(i):

                for t in TRANSLATIONS.values():
                    if abs(i - t) < symprec:
                        return t
                if abs(i - round(i)) < symprec:
                    return 0
                raise ValueError("Invalid translation!")

            symmops = []
            for op in sf.get_symmetry_operations():
                v = op.translation_vector
                v = [round_symm_trans(i) for i in v]
                symmops.append(SymmOp.from_rotation_and_translation(
                    op.rotation_matrix, v))

            ops = [op.as_xyz_string() for op in symmops]
            block["_symmetry_equiv_pos_site_id"] = \
                ["%d" % i for i in range(1, len(ops) + 1)]
            block["_symmetry_equiv_pos_as_xyz"] = ops

        loops.append(["_symmetry_equiv_pos_site_id",
                      "_symmetry_equiv_pos_as_xyz"])

        contains_oxidation = True
        try:
            symbol_to_oxinum = OrderedDict([
                (el.__str__(), float(el.oxi_state))
                for el in sorted(comp.elements)])
        except AttributeError:
            symbol_to_oxinum = OrderedDict([(el.symbol, 0) for el in
                                            sorted(comp.elements)])
            contains_oxidation = False
        if contains_oxidation:
            block["_atom_type_symbol"] = symbol_to_oxinum.keys()
            block["_atom_type_oxidation_number"] = symbol_to_oxinum.values()
            loops.append(["_atom_type_symbol", "_atom_type_oxidation_number"])

        atom_site_type_symbol = []
        atom_site_symmetry_multiplicity = []
        atom_site_fract_x = []
        atom_site_fract_y = []
        atom_site_fract_z = []
        atom_site_label = []
        atom_site_occupancy = []
        count = 1
        if symprec is None:
            for site in struct:
                for sp, occu in site.species_and_occu.items():
                    atom_site_type_symbol.append(sp.__str__())
                    atom_site_symmetry_multiplicity.append("1")
                    atom_site_fract_x.append("{0:f}".format(site.a))
                    atom_site_fract_y.append("{0:f}".format(site.b))
                    atom_site_fract_z.append("{0:f}".format(site.c))
                    atom_site_label.append("{}{}".format(sp.symbol, count))
                    atom_site_occupancy.append(occu.__str__())
                    count += 1
        else:
            # The following just presents a deterministic ordering.
            unique_sites = [
                (sorted(sites, key=lambda s: tuple([abs(x) for x in
                                                   s.frac_coords]))[0],
                 len(sites))
                for sites in sf.get_symmetrized_structure().equivalent_sites
            ]
            for site, mult in sorted(
                    unique_sites,
                    key=lambda t: (t[0].species_and_occu.average_electroneg,
                                   -t[1], t[0].a, t[0].b, t[0].c)):
                for sp, occu in site.species_and_occu.items():
                    atom_site_type_symbol.append(sp.__str__())
                    atom_site_symmetry_multiplicity.append("%d" % mult)
                    atom_site_fract_x.append("{0:f}".format(site.a))
                    atom_site_fract_y.append("{0:f}".format(site.b))
                    atom_site_fract_z.append("{0:f}".format(site.c))
                    atom_site_label.append("{}{}".format(sp.symbol, count))
                    atom_site_occupancy.append(occu.__str__())
                    count += 1

        block["_atom_site_type_symbol"] = atom_site_type_symbol
        block["_atom_site_label"] = atom_site_label
        block["_atom_site_symmetry_multiplicity"] = \
            atom_site_symmetry_multiplicity
        block["_atom_site_fract_x"] = atom_site_fract_x
        block["_atom_site_fract_y"] = atom_site_fract_y
        block["_atom_site_fract_z"] = atom_site_fract_z
        block["_atom_site_occupancy"] = atom_site_occupancy
        loops.append(["_atom_site_type_symbol",
                      "_atom_site_label",
                      "_atom_site_symmetry_multiplicity",
                      "_atom_site_fract_x",
                      "_atom_site_fract_y",
                      "_atom_site_fract_z",
                      "_atom_site_occupancy"])
        d = OrderedDict()
        d[comp.reduced_formula] = CifBlock(block, loops, comp.reduced_formula)
        self._cf = CifFile(d)
Example #30
0
    def get_valences(self, structure):
        """
        Returns a list of valences for the structure. This currently works only
        for ordered structures only.

        Args:
            structure: Structure to analyze

        Returns:
            A list of valences for each site in the structure (for an ordered
            structure), e.g., [1, 1, -2] or a list of lists with the
            valences for each fractional element of each site in the
            structure (for an unordered structure),
            e.g., [[2, 4], [3], [-2], [-2], [-2]]

        Raises:
            A ValueError if the valences cannot be determined.
        """
        els = [Element(el.symbol) for el in structure.composition.elements]

        if not set(els).issubset(set(BV_PARAMS.keys())):
            raise ValueError(
                "Structure contains elements not in set of BV parameters!")

        # Perform symmetry determination and get sites grouped by symmetry.
        if self.symm_tol:
            finder = SpacegroupAnalyzer(structure, self.symm_tol)
            symm_structure = finder.get_symmetrized_structure()
            equi_sites = symm_structure.equivalent_sites
        else:
            equi_sites = [[site] for site in structure]

        # Sort the equivalent sites by decreasing electronegativity.
        equi_sites = sorted(
            equi_sites, key=lambda sites: -sites[0].species.average_electroneg)

        # Get a list of valences and probabilities for each symmetrically
        # distinct site.
        valences = []
        all_prob = []
        if structure.is_ordered:
            for sites in equi_sites:
                test_site = sites[0]
                nn = structure.get_neighbors(test_site, self.max_radius)
                prob = self._calc_site_probabilities(test_site, nn)
                all_prob.append(prob)
                val = list(prob.keys())
                # Sort valences in order of decreasing probability.
                val = sorted(val, key=lambda v: -prob[v])
                # Retain probabilities that are at least 1/100 of highest prob.
                valences.append(
                    list(filter(lambda v: prob[v] > 0.01 * prob[val[0]], val)))
        else:
            full_all_prob = []
            for sites in equi_sites:
                test_site = sites[0]
                nn = structure.get_neighbors(test_site, self.max_radius)
                prob = self._calc_site_probabilities_unordered(test_site, nn)
                all_prob.append(prob)
                full_all_prob.extend(prob.values())
                vals = []
                for (elsp, occ) in get_z_ordered_elmap(test_site.species):
                    val = list(prob[elsp.symbol].keys())
                    # Sort valences in order of decreasing probability.
                    val = sorted(val, key=lambda v: -prob[elsp.symbol][v])
                    # Retain probabilities that are at least 1/100 of highest
                    # prob.
                    vals.append(
                        list(
                            filter(
                                lambda v: prob[elsp.symbol][v] > 0.001 * prob[
                                    elsp.symbol][val[0]],
                                val,
                            )))
                valences.append(vals)

        # make variables needed for recursion
        if structure.is_ordered:
            nsites = np.array([len(i) for i in equi_sites])
            vmin = np.array([min(i) for i in valences])
            vmax = np.array([max(i) for i in valences])

            self._n = 0
            self._best_score = 0
            self._best_vset = None

            def evaluate_assignment(v_set):
                el_oxi = collections.defaultdict(list)
                for i, sites in enumerate(equi_sites):
                    el_oxi[sites[0].specie.symbol].append(v_set[i])
                max_diff = max([max(v) - min(v) for v in el_oxi.values()])
                if max_diff > 1:
                    return
                score = functools.reduce(
                    operator.mul,
                    [all_prob[i][v] for i, v in enumerate(v_set)])
                if score > self._best_score:
                    self._best_vset = v_set
                    self._best_score = score

            def _recurse(assigned=[]):
                # recurses to find permutations of valences based on whether a
                # charge balanced assignment can still be found
                if self._n > self.max_permutations:
                    return None

                i = len(assigned)
                highest = vmax.copy()
                highest[:i] = assigned
                highest *= nsites
                highest = np.sum(highest)

                lowest = vmin.copy()
                lowest[:i] = assigned
                lowest *= nsites
                lowest = np.sum(lowest)

                if highest < 0 or lowest > 0:
                    self._n += 1
                    return None

                if i == len(valences):
                    evaluate_assignment(assigned)
                    self._n += 1
                    return None
                for v in valences[i]:
                    new_assigned = list(assigned)
                    _recurse(new_assigned + [v])
                return None

        else:
            nsites = np.array([len(i) for i in equi_sites])
            tmp = []
            attrib = []
            for insite, nsite in enumerate(nsites):
                for val in valences[insite]:
                    tmp.append(nsite)
                    attrib.append(insite)
            new_nsites = np.array(tmp)
            fractions = []
            elements = []
            for sites in equi_sites:
                for sp, occu in get_z_ordered_elmap(sites[0].species):
                    elements.append(sp.symbol)
                    fractions.append(occu)
            fractions = np.array(fractions, np.float_)
            new_valences = []
            for vals in valences:
                for val in vals:
                    new_valences.append(val)
            vmin = np.array([min(i) for i in new_valences], np.float_)
            vmax = np.array([max(i) for i in new_valences], np.float_)

            self._n = 0
            self._best_score = 0
            self._best_vset = None

            def evaluate_assignment(v_set):
                el_oxi = collections.defaultdict(list)
                jj = 0
                for i, sites in enumerate(equi_sites):
                    for specie, occu in get_z_ordered_elmap(sites[0].species):
                        el_oxi[specie.symbol].append(v_set[jj])
                        jj += 1
                max_diff = max([max(v) - min(v) for v in el_oxi.values()])
                if max_diff > 2:
                    return

                score = functools.reduce(
                    operator.mul,
                    [
                        all_prob[attrib[iv]][elements[iv]][vv]
                        for iv, vv in enumerate(v_set)
                    ],
                )
                if score > self._best_score:
                    self._best_vset = v_set
                    self._best_score = score

            def _recurse(assigned=[]):
                # recurses to find permutations of valences based on whether a
                # charge balanced assignment can still be found
                if self._n > self.max_permutations:
                    return None

                i = len(assigned)
                highest = vmax.copy()
                highest[:i] = assigned
                highest *= new_nsites
                highest *= fractions
                highest = np.sum(highest)

                lowest = vmin.copy()
                lowest[:i] = assigned
                lowest *= new_nsites
                lowest *= fractions
                lowest = np.sum(lowest)

                if highest < -self.charge_neutrality_tolerance or lowest > self.charge_neutrality_tolerance:
                    self._n += 1
                    return None

                if i == len(new_valences):
                    evaluate_assignment(assigned)
                    self._n += 1
                    return None

                for v in new_valences[i]:
                    new_assigned = list(assigned)
                    _recurse(new_assigned + [v])

                return None

        _recurse()

        if self._best_vset:
            if structure.is_ordered:
                assigned = {}
                for val, sites in zip(self._best_vset, equi_sites):
                    for site in sites:
                        assigned[site] = val

                return [int(assigned[site]) for site in structure]
            assigned = {}
            new_best_vset = []
            for ii in range(len(equi_sites)):
                new_best_vset.append(list())
            for ival, val in enumerate(self._best_vset):
                new_best_vset[attrib[ival]].append(val)
            for val, sites in zip(new_best_vset, equi_sites):
                for site in sites:
                    assigned[site] = val

            return [[int(frac_site) for frac_site in assigned[site]]
                    for site in structure]
        raise ValueError("Valences cannot be assigned!")
    # print(fi)
    # exit()
    parser = CifParser(fi)
    structure = parser.get_structures(primitive=True)[0]
    els = [Element(el.symbol) for el in structure.composition.elements]

    if not set(els).issubset(set(BV_PARAMS.keys())):
        raise ValueError(
            "Structure contains elements not in set of BV parameters!")

    bv = pma.BVAnalyzer()

    # Perform symmetry determination and get sites grouped by symmetry.
    if bv.symm_tol:
        finder = SpacegroupAnalyzer(structure, bv.symm_tol)
        symm_structure = finder.get_symmetrized_structure()
        equi_sites = symm_structure.equivalent_sites
    else:
        equi_sites = [[site] for site in structure]

    # Sort the equivalent sites by decreasing electronegativity.
    equi_sites = sorted(equi_sites,
                        key=lambda sites: -sites[0].species.average_electroneg)

    # Get a list of valences and probabilities for each symmetrically
    # distinct site.
    all_prob = []
    if structure.is_ordered:
        for sites in equi_sites:
            test_site = sites[0]
            # print(test_site)
Example #32
0
def get_vacancy_diffusion_pathways_from_cell(structure : Structure, atom_i : int, vis=False, get_midpoints=False):
    """

    Find Vacancy Strucutres for diffusion into and out of the specified atom_i site.

    :param structure: Structure
        Structure to calculate diffusion pathways
    :param atom_i: int
        Atom to get diffion path from
    :return: [ Structure ]
    """

    # To Find Pathway, look for voronoi edges
    orig_structure = structure.copy()
    structure = structure.copy() # type: Structure
    target_atom = structure[atom_i].specie
    vnn = VoronoiNN(targets=[target_atom])
    edges = vnn.get_nn_info(structure, atom_i)
    base_coords = structure[atom_i].coords

    # Add H in middle of the discovered pathways.  Use symmetry analysis to elminate equivlent H and therfore
    # equivalent pathways
    site_dir = {}
    for edge in edges:
        coords = np.round((base_coords + edge['site'].coords)/2,3)
        structure.append('H', coords, True)
       # site_dir[tuple(np.round(coords))] = structure.index(edge['site']) # Use Tuple for indexing dict, need to round
        site_dir[tuple(np.round(coords))] =  [list(x) for x in np.round(structure.frac_coords % 1,2) ].index(list(np.round(edge['site'].frac_coords % 1, 2))) # Use Tuple for indexing dict, need to round
    # Add H for all other diffusion atoms, so symmetry is preserved
    for i in get_atom_i(orig_structure, target_atom):
        sym_edges = vnn.get_nn_info(orig_structure, i)
        base_coords = structure[i].coords
        for edge in sym_edges:
            coords = (base_coords + edge['site'].coords) / 2
            try:
                structure.append('H', coords, True, True)
            except:
                pass

    # Remove symmetrically equivalent pathways:
    sga = SpacegroupAnalyzer(structure, 0.5, angle_tolerance=20)
    ss = sga.get_symmetrized_structure()

    final_structure = structure.copy()
    indices = []
    for i in range(len(orig_structure), len(orig_structure)+len(edges)): # get all 'original' edge sites
        sites = ss.find_equivalent_sites(ss[i])
        new_indices = [ss.index(site) for site in sites if ss.index(site) < len(orig_structure) + len(edges)] # Check if symmetrically equivalent to other original edge sites
        new_indices.remove(i)
        if i not in indices: # Don't duplicate effort
            indices = indices + new_indices
            indices.sort()
    indices = indices + list(range(len(orig_structure)+len(edges), len(final_structure)))
    final_structure.remove_sites(indices)
    diffusion_elements = [ site_dir[tuple(np.round(h.coords))] for h in final_structure[len(orig_structure):] ]
    if vis:
        view(final_structure, 'VESTA')
        print(diffusion_elements)

    if get_midpoints:
        centers = [h.frac_coords for h in final_structure[len(orig_structure):]]
        return (diffusion_elements, centers)


    return diffusion_elements
class FullPathMapper:
    """
    Find all hops in a given crystal structure using the StructureGraph.
    Each hop is an edge in the StructureGraph object and each node is a position of the migrating species in the structure
    The equivalence of the hops is checked using the MigrationPath.__eq__ funciton.
    The funtions here are reponsible for distinguishing the individual hops and analysis
    """

    def __init__(self,
                 structure,
                 migrating_specie,
                 max_path_length=10,
                 symprec=0.1,
                 vac_mode=False):
        """
        Args:
            structure: Input structure that contains all sites.
            migrating_specie (Specie-like): The specie that migrates. E.g.,
                "Li".
            max_path_length (float): Maximum length of NEB path in the unit
                of Angstrom. Defaults to None, which means you are setting the
                value to the min cutoff until finding 1D or >1D percolating paths.
            symprec (float): Symmetry precision to determine equivalence.
        """
        self.structure = structure
        self.migrating_specie = get_el_sp(migrating_specie)
        self.symprec = symprec
        self.a = SpacegroupAnalyzer(self.structure, symprec=self.symprec)
        self.symm_structure = self.a.get_symmetrized_structure()
        self.only_sites = self.get_only_sites()
        self.unique_hops = None

        # Generate the graph edges between these all the sites
        self.s_graph = StructureGraph.with_local_env_strategy(
            self.only_sites,
            MinimumDistanceNN(
                cutoff=max_path_length,
                get_all_sites=True))  # weights in this graph are the distances
        self.s_graph.set_node_attributes()

    # TODO add classmethod for creating the FullPathMapper from the charge density

    def get_only_sites(self):
        """
        Get a copy of the structure with only the sites

        Args:

        Returns:
          Structure: Structure with all possible migrating ion sites

        """
        migrating_ion_sites = list(
            filter(
                lambda site: site.species == Composition(
                    {self.migrating_specie: 1}), self.structure.sites))
        return Structure.from_sites(migrating_ion_sites)

    def _get_pos_and_migration_path(self, u, v, w):
        """
        insert a single MigrationPath object on a graph edge
        Args:
          u (int): index of initial node
          v (int): index of final node
          w (int): index for multiple edges that share the same two nodes

        """
        edge = self.s_graph.graph[u][v][w]
        i_site = self.only_sites.sites[u]
        f_site = PeriodicSite(
            self.only_sites.sites[v].species,
            self.only_sites.sites[v].frac_coords + np.array(edge['to_jimage']),
            lattice=self.only_sites.lattice)
        # Positions might be useful for plotting
        edge['ipos'] = i_site.frac_coords
        edge['epos'] = f_site.frac_coords
        edge['ipos_cart'] = np.dot(i_site.frac_coords,
                                   self.only_sites.lattice.matrix)
        edge['epos_cart'] = np.dot(f_site.frac_coords,
                                   self.only_sites.lattice.matrix)

        edge['hop'] = MigrationPath(i_site, f_site, self.symm_structure)

    def populate_edges_with_migration_paths(self):
        """
        Populate the edges with the data for the Migration Paths
        """
        list(
            starmap(self._get_pos_and_migration_path,
                    self.s_graph.graph.edges))

    def group_and_label_hops(self):
        """
        Group the MigrationPath objects together and label all the symmetrically equlivaelnt hops with the same label
        """
        hops = [(g_index, val) for g_index, val in nx.get_edge_attributes(
            self.s_graph.graph, "hop").items()]
        labs = generic_groupby(hops, comp=lambda x, y: x[1] == y[1])
        new_attr = {
            g_index: {
                'hop_label': labs[edge_index]
            }
            for edge_index, (g_index, _) in enumerate(hops)
        }
        nx.set_edge_attributes(self.s_graph.graph, new_attr)
        return new_attr

    def get_unique_hops_dict(self):
        """
        Get the list of the unique objects
        Returns:
            dictionary {label : MigrationPath}

        """
        self.unique_hops = {
            d['hop_label']: d['hop']
            for u, v, d in self.s_graph.graph.edges(data=True)
        }

    def add_data_to_similar_edges(self, taget_label, data=dict()):
        """
        Insert data to all edges with the same label
        """
        for u, v, d in self.s_graph.graph.edges(data=True):
            if d['hop_label'] == taget_label:
                d.update(data)
Example #34
0
class SpacegroupAnalyzerTest(PymatgenTest):

    def setUp(self):
        p = Poscar.from_file(os.path.join(test_dir, 'POSCAR'))
        self.structure = p.structure
        self.sg = SpacegroupAnalyzer(self.structure, 0.001)
        self.disordered_structure = self.get_structure('Li10GeP2S12')
        self.disordered_sg = SpacegroupAnalyzer(self.disordered_structure, 0.001)
        s = p.structure.copy()
        site = s[0]
        del s[0]
        s.append(site.species_and_occu, site.frac_coords)
        self.sg3 = SpacegroupAnalyzer(s, 0.001)
        graphite = self.get_structure('Graphite')
        graphite.add_site_property("magmom", [0.1] * len(graphite))
        self.sg4 = SpacegroupAnalyzer(graphite, 0.001)
        self.structure4 = graphite

    def test_primitive(self):
        s = Structure.from_spacegroup("Fm-3m", np.eye(3) * 3, ["Cu"],
                                      [[0, 0, 0]])
        a = SpacegroupAnalyzer(s)
        self.assertEqual(len(s), 4)
        self.assertEqual(len(a.find_primitive()), 1)

    def test_is_laue(self):
        s = Structure.from_spacegroup("Fm-3m", np.eye(3) * 3, ["Cu"],
                                      [[0, 0, 0]])
        a = SpacegroupAnalyzer(s)
        self.assertTrue(a.is_laue())

    def test_magnetic(self):
        lfp = PymatgenTest.get_structure("LiFePO4")
        sg = SpacegroupAnalyzer(lfp, 0.1)
        self.assertEqual(sg.get_space_group_symbol(), "Pnma")
        magmoms = [0] * len(lfp)
        magmoms[4] = 1
        magmoms[5] = -1
        magmoms[6] = 1
        magmoms[7] = -1
        lfp.add_site_property("magmom", magmoms)
        sg = SpacegroupAnalyzer(lfp, 0.1)
        self.assertEqual(sg.get_space_group_symbol(), "Pnma")

    def test_get_space_symbol(self):
        self.assertEqual(self.sg.get_space_group_symbol(), "Pnma")
        self.assertEqual(self.disordered_sg.get_space_group_symbol(),
                         "P4_2/nmc")
        self.assertEqual(self.sg3.get_space_group_symbol(), "Pnma")
        self.assertEqual(self.sg4.get_space_group_symbol(), "P6_3/mmc")

    def test_get_space_number(self):
        self.assertEqual(self.sg.get_space_group_number(), 62)
        self.assertEqual(self.disordered_sg.get_space_group_number(), 137)
        self.assertEqual(self.sg4.get_space_group_number(), 194)

    def test_get_hall(self):
        self.assertEqual(self.sg.get_hall(), '-P 2ac 2n')
        self.assertEqual(self.disordered_sg.get_hall(), 'P 4n 2n -1n')

    def test_get_pointgroup(self):
        self.assertEqual(self.sg.get_point_group_symbol(), 'mmm')
        self.assertEqual(self.disordered_sg.get_point_group_symbol(), '4/mmm')

    def test_get_symmetry_dataset(self):
        ds = self.sg.get_symmetry_dataset()
        self.assertEqual(ds['international'], 'Pnma')

    def test_get_crystal_system(self):
        crystal_system = self.sg.get_crystal_system()
        self.assertEqual('orthorhombic', crystal_system)
        self.assertEqual('tetragonal', self.disordered_sg.get_crystal_system())

    def test_get_symmetry_operations(self):
        for sg, structure in [(self.sg, self.structure),
                              (self.sg4, self.structure4)]:

            pgops = sg.get_point_group_operations()
            fracsymmops = sg.get_symmetry_operations()
            symmops = sg.get_symmetry_operations(True)
            latt = structure.lattice
            for fop, op, pgop in zip(fracsymmops, symmops, pgops):
                # translation vector values should all be 0 or 0.5
                t = fop.translation_vector * 2
                self.assertArrayAlmostEqual(t - np.round(t), 0)

                self.assertArrayAlmostEqual(fop.rotation_matrix,
                                            pgop.rotation_matrix)
                for site in structure:
                    newfrac = fop.operate(site.frac_coords)
                    newcart = op.operate(site.coords)
                    self.assertTrue(np.allclose(latt.get_fractional_coords(newcart),
                                                newfrac))
                    found = False
                    newsite = PeriodicSite(site.species_and_occu, newcart, latt,
                                           coords_are_cartesian=True)
                    for testsite in structure:
                        if newsite.is_periodic_image(testsite, 1e-3):
                            found = True
                            break
                    self.assertTrue(found)

                # Make sure this works for any position, not just the atomic
                # ones.
                random_fcoord = np.random.uniform(size=(3))
                random_ccoord = latt.get_cartesian_coords(random_fcoord)
                newfrac = fop.operate(random_fcoord)
                newcart = op.operate(random_ccoord)
                self.assertTrue(np.allclose(latt.get_fractional_coords(newcart),
                                            newfrac))

    def test_get_refined_structure(self):
        for a in self.sg.get_refined_structure().lattice.angles:
            self.assertEqual(a, 90)
        refined = self.disordered_sg.get_refined_structure()
        for a in refined.lattice.angles:
            self.assertEqual(a, 90)
        self.assertEqual(refined.lattice.a, refined.lattice.b)
        s = self.get_structure('Li2O')
        sg = SpacegroupAnalyzer(s, 0.01)
        self.assertEqual(sg.get_refined_structure().num_sites, 4 * s.num_sites)

    def test_get_symmetrized_structure(self):
        symm_struct = self.sg.get_symmetrized_structure()
        for a in symm_struct.lattice.angles:
            self.assertEqual(a, 90)
        self.assertEqual(len(symm_struct.equivalent_sites), 5)

        symm_struct = self.disordered_sg.get_symmetrized_structure()
        self.assertEqual(len(symm_struct.equivalent_sites), 8)
        self.assertEqual([len(i) for i in symm_struct.equivalent_sites],
                         [16,4,8,4,2,8,8,8])
        s1 = symm_struct.equivalent_sites[1][1]
        s2 = symm_struct[symm_struct.equivalent_indices[1][1]]
        self.assertEqual(s1, s2)
        self.assertEqual(self.sg4.get_symmetrized_structure()[0].magmom, 0.1)
        self.assertEqual(symm_struct.wyckoff_symbols[0], '16h')
        # self.assertEqual(symm_struct[0].wyckoff, "16h")

    def test_find_primitive(self):
        """
        F m -3 m Li2O testing of converting to primitive cell
        """
        parser = CifParser(os.path.join(test_dir, 'Li2O.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure)
        primitive_structure = s.find_primitive()
        self.assertEqual(primitive_structure.formula, "Li2 O1")
        # This isn't what is expected. All the angles should be 60
        self.assertAlmostEqual(primitive_structure.lattice.alpha, 60)
        self.assertAlmostEqual(primitive_structure.lattice.beta, 60)
        self.assertAlmostEqual(primitive_structure.lattice.gamma, 60)
        self.assertAlmostEqual(primitive_structure.lattice.volume,
                               structure.lattice.volume / 4.0)

    def test_get_ir_reciprocal_mesh(self):
        grid = self.sg.get_ir_reciprocal_mesh()
        self.assertEqual(len(grid), 216)
        self.assertAlmostEqual(grid[1][0][0], 0.1)
        self.assertAlmostEqual(grid[1][0][1], 0.0)
        self.assertAlmostEqual(grid[1][0][2], 0.0)
        self.assertAlmostEqual(grid[1][1], 2)

    def test_get_conventional_standard_structure(self):
        parser = CifParser(os.path.join(test_dir, 'bcc_1927.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 9.1980270633769461)
        self.assertAlmostEqual(conv.lattice.b, 9.1980270633769461)
        self.assertAlmostEqual(conv.lattice.c, 9.1980270633769461)

        parser = CifParser(os.path.join(test_dir, 'btet_1915.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 5.0615106678044235)
        self.assertAlmostEqual(conv.lattice.b, 5.0615106678044235)
        self.assertAlmostEqual(conv.lattice.c, 4.2327080177761687)

        parser = CifParser(os.path.join(test_dir, 'orci_1010.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 2.9542233922299999)
        self.assertAlmostEqual(conv.lattice.b, 4.6330325651443296)
        self.assertAlmostEqual(conv.lattice.c, 5.373703587040775)

        parser = CifParser(os.path.join(test_dir, 'orcc_1003.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 4.1430033493799998)
        self.assertAlmostEqual(conv.lattice.b, 31.437979757624728)
        self.assertAlmostEqual(conv.lattice.c, 3.99648651)

        parser = CifParser(os.path.join(test_dir, 'orac_632475.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 3.1790663399999999)
        self.assertAlmostEqual(conv.lattice.b, 9.9032878699999998)
        self.assertAlmostEqual(conv.lattice.c, 3.5372412099999999)

        parser = CifParser(os.path.join(test_dir, 'monoc_1028.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 117.53832420192903)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 14.033435583000625)
        self.assertAlmostEqual(conv.lattice.b, 3.96052850731)
        self.assertAlmostEqual(conv.lattice.c, 6.8743926325200002)

        parser = CifParser(os.path.join(test_dir, 'hex_1170.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 120)
        self.assertAlmostEqual(conv.lattice.a, 3.699919902005897)
        self.assertAlmostEqual(conv.lattice.b, 3.699919902005897)
        self.assertAlmostEqual(conv.lattice.c, 6.9779585500000003)

    def test_get_primitive_standard_structure(self):
        parser = CifParser(os.path.join(test_dir, 'bcc_1927.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 109.47122063400001)
        self.assertAlmostEqual(prim.lattice.beta, 109.47122063400001)
        self.assertAlmostEqual(prim.lattice.gamma, 109.47122063400001)
        self.assertAlmostEqual(prim.lattice.a, 7.9657251015812145)
        self.assertAlmostEqual(prim.lattice.b, 7.9657251015812145)
        self.assertAlmostEqual(prim.lattice.c, 7.9657251015812145)

        parser = CifParser(os.path.join(test_dir, 'btet_1915.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 105.015053349)
        self.assertAlmostEqual(prim.lattice.beta, 105.015053349)
        self.assertAlmostEqual(prim.lattice.gamma, 118.80658411899999)
        self.assertAlmostEqual(prim.lattice.a, 4.1579321075608791)
        self.assertAlmostEqual(prim.lattice.b, 4.1579321075608791)
        self.assertAlmostEqual(prim.lattice.c, 4.1579321075608791)

        parser = CifParser(os.path.join(test_dir, 'orci_1010.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 134.78923546600001)
        self.assertAlmostEqual(prim.lattice.beta, 105.856239333)
        self.assertAlmostEqual(prim.lattice.gamma, 91.276341676000001)
        self.assertAlmostEqual(prim.lattice.a, 3.8428217771014852)
        self.assertAlmostEqual(prim.lattice.b, 3.8428217771014852)
        self.assertAlmostEqual(prim.lattice.c, 3.8428217771014852)

        parser = CifParser(os.path.join(test_dir, 'orcc_1003.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 90)
        self.assertAlmostEqual(prim.lattice.beta, 90)
        self.assertAlmostEqual(prim.lattice.gamma, 164.985257335)
        self.assertAlmostEqual(prim.lattice.a, 15.854897098324196)
        self.assertAlmostEqual(prim.lattice.b, 15.854897098324196)
        self.assertAlmostEqual(prim.lattice.c, 3.99648651)

        parser = CifParser(os.path.join(test_dir, 'orac_632475.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 90)
        self.assertAlmostEqual(prim.lattice.beta, 90)
        self.assertAlmostEqual(prim.lattice.gamma, 144.40557588533386)
        self.assertAlmostEqual(prim.lattice.a, 5.2005185662155391)
        self.assertAlmostEqual(prim.lattice.b, 5.2005185662155391)
        self.assertAlmostEqual(prim.lattice.c, 3.5372412099999999)

        parser = CifParser(os.path.join(test_dir, 'monoc_1028.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 63.579155761999999)
        self.assertAlmostEqual(prim.lattice.beta, 116.42084423747779)
        self.assertAlmostEqual(prim.lattice.gamma, 148.47965136208569)
        self.assertAlmostEqual(prim.lattice.a, 7.2908007159612325)
        self.assertAlmostEqual(prim.lattice.b, 7.2908007159612325)
        self.assertAlmostEqual(prim.lattice.c, 6.8743926325200002)

        parser = CifParser(os.path.join(test_dir, 'hex_1170.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 90)
        self.assertAlmostEqual(prim.lattice.beta, 90)
        self.assertAlmostEqual(prim.lattice.gamma, 120)
        self.assertAlmostEqual(prim.lattice.a, 3.699919902005897)
        self.assertAlmostEqual(prim.lattice.b, 3.699919902005897)
        self.assertAlmostEqual(prim.lattice.c, 6.9779585500000003)

        parser = CifParser(os.path.join(test_dir, 'rhomb_3478_conv.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 28.049186140546812)
        self.assertAlmostEqual(prim.lattice.beta, 28.049186140546812)
        self.assertAlmostEqual(prim.lattice.gamma, 28.049186140546812)
        self.assertAlmostEqual(prim.lattice.a, 5.9352627428399982)
        self.assertAlmostEqual(prim.lattice.b, 5.9352627428399982)
        self.assertAlmostEqual(prim.lattice.c, 5.9352627428399982)
Example #35
0
    def __init__(self, struct, symprec=None, write_magmoms=False):
        """
        A wrapper around CifFile to write CIF files from pymatgen structures.

        Args:
            struct (Structure): structure to write
            symprec (float): If not none, finds the symmetry of the structure
                and writes the cif with symmetry information. Passes symprec
                to the SpacegroupAnalyzer
            write_magmoms (bool): If True, will write magCIF file. Incompatible
                with symprec
        """

        if write_magmoms and symprec:
            warnings.warn(
                "Magnetic symmetry cannot currently be detected by pymatgen.")
            symprec = None

        format_str = "{:.8f}"

        block = OrderedDict()
        loops = []
        spacegroup = ("P 1", 1)
        if symprec is not None:
            sf = SpacegroupAnalyzer(struct, symprec)
            spacegroup = (sf.get_space_group_symbol(),
                          sf.get_space_group_number())
            # Needs the refined struture when using symprec. This converts
            # primitive to conventional structures, the standard for CIF.
            struct = sf.get_refined_structure()

        latt = struct.lattice
        comp = struct.composition
        no_oxi_comp = comp.element_composition
        block["_symmetry_space_group_name_H-M"] = spacegroup[0]
        for cell_attr in ['a', 'b', 'c']:
            block["_cell_length_" + cell_attr] = format_str.format(
                getattr(latt, cell_attr))
        for cell_attr in ['alpha', 'beta', 'gamma']:
            block["_cell_angle_" + cell_attr] = format_str.format(
                getattr(latt, cell_attr))
        block["_symmetry_Int_Tables_number"] = spacegroup[1]
        block["_chemical_formula_structural"] = no_oxi_comp.reduced_formula
        block["_chemical_formula_sum"] = no_oxi_comp.formula
        block["_cell_volume"] = latt.volume.__str__()

        reduced_comp, fu = no_oxi_comp.get_reduced_composition_and_factor()
        block["_cell_formula_units_Z"] = str(int(fu))

        if symprec is None:
            block["_symmetry_equiv_pos_site_id"] = ["1"]
            block["_symmetry_equiv_pos_as_xyz"] = ["x, y, z"]
        else:
            sf = SpacegroupAnalyzer(struct, symprec)

            symmops = []
            for op in sf.get_symmetry_operations():
                v = op.translation_vector
                symmops.append(
                    SymmOp.from_rotation_and_translation(
                        op.rotation_matrix, v))

            ops = [op.as_xyz_string() for op in symmops]
            block["_symmetry_equiv_pos_site_id"] = \
                ["%d" % i for i in range(1, len(ops) + 1)]
            block["_symmetry_equiv_pos_as_xyz"] = ops

        loops.append(
            ["_symmetry_equiv_pos_site_id", "_symmetry_equiv_pos_as_xyz"])

        contains_oxidation = True
        try:
            symbol_to_oxinum = OrderedDict([(el.__str__(), float(el.oxi_state))
                                            for el in sorted(comp.elements)])
        except AttributeError:
            symbol_to_oxinum = OrderedDict([(el.symbol, 0)
                                            for el in sorted(comp.elements)])
            contains_oxidation = False
        if contains_oxidation:
            block["_atom_type_symbol"] = symbol_to_oxinum.keys()
            block["_atom_type_oxidation_number"] = symbol_to_oxinum.values()
            loops.append(["_atom_type_symbol", "_atom_type_oxidation_number"])

        atom_site_type_symbol = []
        atom_site_symmetry_multiplicity = []
        atom_site_fract_x = []
        atom_site_fract_y = []
        atom_site_fract_z = []
        atom_site_label = []
        atom_site_occupancy = []
        atom_site_moment_label = []
        atom_site_moment_crystalaxis_x = []
        atom_site_moment_crystalaxis_y = []
        atom_site_moment_crystalaxis_z = []
        count = 1
        if symprec is None:
            for site in struct:
                for sp, occu in sorted(site.species_and_occu.items()):
                    atom_site_type_symbol.append(sp.__str__())
                    atom_site_symmetry_multiplicity.append("1")
                    atom_site_fract_x.append("{0:f}".format(site.a))
                    atom_site_fract_y.append("{0:f}".format(site.b))
                    atom_site_fract_z.append("{0:f}".format(site.c))
                    atom_site_label.append("{}{}".format(sp.symbol, count))
                    atom_site_occupancy.append(occu.__str__())

                    magmom = site.properties.get('magmom', Magmom(0))
                    moment = Magmom.get_moment_relative_to_crystal_axes(
                        magmom, latt)
                    if write_magmoms and abs(magmom) > 0:
                        atom_site_moment_label.append("{}{}".format(
                            sp.symbol, count))
                        atom_site_moment_crystalaxis_x.append(moment[0])
                        atom_site_moment_crystalaxis_y.append(moment[1])
                        atom_site_moment_crystalaxis_z.append(moment[2])

                    count += 1
        else:
            # The following just presents a deterministic ordering.
            unique_sites = [
                (sorted(sites,
                        key=lambda s: tuple([abs(x)
                                             for x in s.frac_coords]))[0],
                 len(sites))
                for sites in sf.get_symmetrized_structure().equivalent_sites
            ]
            for site, mult in sorted(unique_sites,
                                     key=lambda t:
                                     (t[0].species_and_occu.average_electroneg,
                                      -t[1], t[0].a, t[0].b, t[0].c)):
                for sp, occu in site.species_and_occu.items():
                    atom_site_type_symbol.append(sp.__str__())
                    atom_site_symmetry_multiplicity.append("%d" % mult)
                    atom_site_fract_x.append("{0:f}".format(site.a))
                    atom_site_fract_y.append("{0:f}".format(site.b))
                    atom_site_fract_z.append("{0:f}".format(site.c))
                    atom_site_label.append("{}{}".format(sp.symbol, count))
                    atom_site_occupancy.append(occu.__str__())
                    count += 1

        block["_atom_site_type_symbol"] = atom_site_type_symbol
        block["_atom_site_label"] = atom_site_label
        block["_atom_site_symmetry_multiplicity"] = \
            atom_site_symmetry_multiplicity
        block["_atom_site_fract_x"] = atom_site_fract_x
        block["_atom_site_fract_y"] = atom_site_fract_y
        block["_atom_site_fract_z"] = atom_site_fract_z
        block["_atom_site_occupancy"] = atom_site_occupancy
        loops.append([
            "_atom_site_type_symbol", "_atom_site_label",
            "_atom_site_symmetry_multiplicity", "_atom_site_fract_x",
            "_atom_site_fract_y", "_atom_site_fract_z", "_atom_site_occupancy"
        ])
        if write_magmoms:
            block["_atom_site_moment_label"] = atom_site_moment_label
            block[
                "_atom_site_moment_crystalaxis_x"] = atom_site_moment_crystalaxis_x
            block[
                "_atom_site_moment_crystalaxis_y"] = atom_site_moment_crystalaxis_y
            block[
                "_atom_site_moment_crystalaxis_z"] = atom_site_moment_crystalaxis_z
            loops.append([
                "_atom_site_moment_label", "_atom_site_moment_crystalaxis_x",
                "_atom_site_moment_crystalaxis_y",
                "_atom_site_moment_crystalaxis_z"
            ])
        d = OrderedDict()
        d[comp.reduced_formula] = CifBlock(block, loops, comp.reduced_formula)
        self._cf = CifFile(d)
Example #36
0
def screen_data(species, transition, path=None):
    # TODO: docstring

    import pymatgen as mg

    total_cell_counter = 0
    bad_cell_counter = 0

    correct_string = str(species) + "-" + str(transition)
    forbidden = [".DS_Store", "CONTCAR", "collection.pkl"]

    if path is None:
        path = t4iss_defaults['t4iss_xanes_data']

    all_cells_in_path = os.listdir(path)

    for i in range(len(all_cells_in_path[:120])):
        total_cell_counter += 1
        cell = all_cells_in_path[i]
        cell_path = os.path.join(path, cell)
        all_spectra_in_cell = os.listdir(cell_path)

        # exclusion criteria
        if 'WARNING_MISSING' in all_spectra_in_cell \
           or 'WARNING_CORRUPT' in all_spectra_in_cell\
           or 'CONTCAR' not in all_spectra_in_cell:
            all_spectra_in_cell = []  # skip the entire thing
            total_cell_counter -= 1
        else:
            contcar_name = os.path.join(cell_path, 'CONTCAR')
            structure = mg.Structure.from_file(contcar_name)
            finder = SpacegroupAnalyzer(structure)
            structure2 = finder.get_symmetrized_structure()
            indices = structure2.equivalent_indices

        all_spectra_in_cell = [
            xx for xx in all_spectra_in_cell if xx not in forbidden
        ]

        for j in range(len(all_spectra_in_cell)):
            spectra = all_spectra_in_cell[j]
            csp = os.path.join(cell_path, spectra)

            if correct_string not in csp:
                pass

            elif 'xanes.dat' not in os.listdir(csp) \
                 and 'xmu.dat' not in os.listdir(csp):
                # this means something went wrong, there should be an
                # xanes.dat file for every one of the cells,
                # something is missing and this entire cell could be corrupt
                print("Warning, xanes data missing from %s" % csp)
                f = open(os.path.join(cell_path, 'WARNING_MISSING'), 'w+')
                f.write('xanes data is missing from one or more entry')
                f.close()
                bad_cell_counter -= 1
                break

            elif 'xanes.pkl' not in os.listdir(csp):
                print("screening %s" % csp)
                for m in indices:
                    if str(m[0] + 1).zfill(3) in spectra:
                        multiplicity = len(m)
                        ind = m[0]
                        break

                try:
                    # this file better exist, otherwise problems
                    f = 'feff_{:03d}_{}-{}'.format(ind + 1, species,
                                                   transition)
                except:  # noqa
                    print(csp)
                    raise RuntimeError("Unknown error during execution")

                if 'xanes.dat' in os.listdir(csp):
                    xanes_data_path = os.path.join(csp, 'xanes.dat')
                    data = np.loadtxt(xanes_data_path)
                else:
                    xanes_data_path = os.path.join(csp, 'xmu.dat')
                    data = np.loadtxt(xanes_data_path,
                                      unpack=True,
                                      comments='#',
                                      skiprows=0)

                x = np.array(data[:, 0]).squeeze()
                y = np.array(data[:, 1]).squeeze()
                if len(x) < 3:
                    raise RuntimeError("You did something stupid.")
                xanes = mXANES(data=[x, y],
                               structure=[structure, ind],
                               xanesid=cell,
                               source='from_local_feff',
                               edge=transition,
                               multiplicity=multiplicity)
                if xanes.vcn == []:
                    f = open(os.path.join(cell_path, 'WARNING_MISSING'), 'w+')
                    f.write('xanes data is missing from one or more entry')
                    f.close()
                else:
                    pickle.dump(xanes,
                                open(os.path.join(csp, 'xanes.pkl'), 'wb'))
    print("good/total = %i/%i" %
          (total_cell_counter + bad_cell_counter, total_cell_counter))
Example #37
0
    def get_valences(self, structure):
        """
        Returns a list of valences for the structure. This currently works only
        for ordered structures only.

        Args:
            structure: Structure to analyze

        Returns:
            A list of valences for each site in the structure (for an ordered
            structure), e.g., [1, 1, -2] or a list of lists with the
            valences for each fractional element of each site in the
            structure (for an unordered structure),
            e.g., [[2, 4], [3], [-2], [-2], [-2]]

        Raises:
            A ValueError if the valences cannot be determined.
        """
        els = [Element(el.symbol) for el in structure.composition.elements]

        if not set(els).issubset(set(BV_PARAMS.keys())):
            raise ValueError(
                "Structure contains elements not in set of BV parameters!"
            )

        #Perform symmetry determination and get sites grouped by symmetry.
        if self.symm_tol:
            finder = SpacegroupAnalyzer(structure, self.symm_tol)
            symm_structure = finder.get_symmetrized_structure()
            equi_sites = symm_structure.equivalent_sites
        else:
            equi_sites = [[site] for site in structure]

        #Sort the equivalent sites by decreasing electronegativity.
        equi_sites = sorted(equi_sites,
                            key=lambda sites: -sites[0].species_and_occu
                            .average_electroneg)

        #Get a list of valences and probabilities for each symmetrically
        #distinct site.
        valences = []
        all_prob = []
        if structure.is_ordered:
            for sites in equi_sites:
                test_site = sites[0]
                nn = structure.get_neighbors(test_site, self.max_radius)
                prob = self._calc_site_probabilities(test_site, nn)
                all_prob.append(prob)
                val = list(prob.keys())
                #Sort valences in order of decreasing probability.
                val = sorted(val, key=lambda v: -prob[v])
                #Retain probabilities that are at least 1/100 of highest prob.
                valences.append(
                    list(filter(lambda v: prob[v] > 0.01 * prob[val[0]],
                                val)))
        else:
            full_all_prob = []
            for sites in equi_sites:
                test_site = sites[0]
                nn = structure.get_neighbors(test_site, self.max_radius)
                prob = self._calc_site_probabilities_unordered(test_site, nn)
                all_prob.append(prob)
                full_all_prob.extend(prob.values())
                vals = []
                for (elsp, occ) in get_z_ordered_elmap(
                        test_site.species_and_occu):
                    val = list(prob[elsp.symbol].keys())
                    #Sort valences in order of decreasing probability.
                    val = sorted(val, key=lambda v: -prob[elsp.symbol][v])
                    # Retain probabilities that are at least 1/100 of highest
                    # prob.
                    vals.append(
                        list(filter(
                            lambda v: prob[elsp.symbol][v] > 0.001 * prob[
                                elsp.symbol][val[0]], val)))
                valences.append(vals)

        #make variables needed for recursion
        if structure.is_ordered:
            nsites = np.array([len(i) for i in equi_sites])
            vmin = np.array([min(i) for i in valences])
            vmax = np.array([max(i) for i in valences])

            self._n = 0
            self._best_score = 0
            self._best_vset = None

            def evaluate_assignment(v_set):
                el_oxi = collections.defaultdict(list)
                for i, sites in enumerate(equi_sites):
                    el_oxi[sites[0].specie.symbol].append(v_set[i])
                max_diff = max([max(v) - min(v) for v in el_oxi.values()])
                if max_diff > 1:
                    return
                score = six.moves.reduce(
                    operator.mul, [all_prob[i][v] for i, v in enumerate(v_set)])
                if score > self._best_score:
                    self._best_vset = v_set
                    self._best_score = score

            def _recurse(assigned=[]):
                #recurses to find permutations of valences based on whether a
                #charge balanced assignment can still be found
                if self._n > self.max_permutations:
                    return

                i = len(assigned)
                highest = vmax.copy()
                highest[:i] = assigned
                highest *= nsites
                highest = np.sum(highest)

                lowest = vmin.copy()
                lowest[:i] = assigned
                lowest *= nsites
                lowest = np.sum(lowest)

                if highest < 0 or lowest > 0:
                    self._n += 1
                    return

                if i == len(valences):
                    evaluate_assignment(assigned)
                    self._n += 1
                    return
                else:
                    for v in valences[i]:
                        new_assigned = list(assigned)
                        _recurse(new_assigned + [v])
        else:
            nsites = np.array([len(i) for i in equi_sites])
            tmp = []
            attrib = []
            for insite, nsite in enumerate(nsites):
                for val in valences[insite]:
                    tmp.append(nsite)
                    attrib.append(insite)
            new_nsites = np.array(tmp)
            fractions = []
            elements = []
            for sites in equi_sites:
                for sp, occu in get_z_ordered_elmap(sites[0].species_and_occu):
                    elements.append(sp.symbol)
                    fractions.append(occu)
            fractions = np.array(fractions, np.float)
            new_valences = []
            for vals in valences:
                for val in vals:
                    new_valences.append(val)
            vmin = np.array([min(i) for i in new_valences], np.float)
            vmax = np.array([max(i) for i in new_valences], np.float)

            self._n = 0
            self._best_score = 0
            self._best_vset = None

            def evaluate_assignment(v_set):
                el_oxi = collections.defaultdict(list)
                jj = 0
                for i, sites in enumerate(equi_sites):
                    for specie, occu in get_z_ordered_elmap(
                            sites[0].species_and_occu):
                        el_oxi[specie.symbol].append(v_set[jj])
                        jj += 1
                max_diff = max([max(v) - min(v) for v in el_oxi.values()])
                if max_diff > 2:
                    return

                score = six.moves.reduce(
                    operator.mul,
                    [all_prob[attrib[iv]][elements[iv]][vv]
                     for iv, vv in enumerate(v_set)])
                if score > self._best_score:
                    self._best_vset = v_set
                    self._best_score = score

            def _recurse(assigned=[]):
                #recurses to find permutations of valences based on whether a
                #charge balanced assignment can still be found
                if self._n > self.max_permutations:
                    return

                i = len(assigned)
                highest = vmax.copy()
                highest[:i] = assigned
                highest *= new_nsites
                highest *= fractions
                highest = np.sum(highest)

                lowest = vmin.copy()
                lowest[:i] = assigned
                lowest *= new_nsites
                lowest *= fractions
                lowest = np.sum(lowest)

                if (highest < -self.charge_neutrality_tolerance or
                        lowest > self.charge_neutrality_tolerance):
                    self._n += 1
                    return

                if i == len(new_valences):
                    evaluate_assignment(assigned)
                    self._n += 1
                    return
                else:
                    for v in new_valences[i]:
                        new_assigned = list(assigned)
                        _recurse(new_assigned + [v])

        _recurse()

        if self._best_vset:
            if structure.is_ordered:
                assigned = {}
                for val, sites in zip(self._best_vset, equi_sites):
                    for site in sites:
                        assigned[site] = val

                return [int(assigned[site]) for site in structure]
            else:
                assigned = {}
                new_best_vset = []
                for ii in range(len(equi_sites)):
                    new_best_vset.append(list())
                for ival, val in enumerate(self._best_vset):
                    new_best_vset[attrib[ival]].append(val)
                for val, sites in zip(new_best_vset, equi_sites):
                    for site in sites:
                        assigned[site] = val

                return [[int(frac_site) for frac_site in assigned[site]]
                        for site in structure]
        else:
            raise ValueError("Valences cannot be assigned!")
Example #38
0
        def get_chemenv_analysis(struct, distance_cutoff, angle_cutoff):

            if not struct:
                raise PreventUpdate

            struct = self.from_data(struct)
            kwargs = self.reconstruct_kwargs_from_state(
                callback_context.inputs)
            distance_cutoff = kwargs["distance_cutoff"]
            angle_cutoff = kwargs["angle_cutoff"]

            # TODO: remove these brittle guard statements, figure out more robust way to handle multiple input types
            if isinstance(struct, StructureGraph):
                struct = struct.structure

            def get_valences(struct):
                valences = [
                    getattr(site.specie, "oxi_state", None) for site in struct
                ]
                valences = [v for v in valences if v is not None]
                if len(valences) == len(struct):
                    return valences
                else:
                    return "undefined"

            # decide which indices to present to user
            sga = SpacegroupAnalyzer(struct)
            symm_struct = sga.get_symmetrized_structure()
            inequivalent_indices = [
                indices[0] for indices in symm_struct.equivalent_indices
            ]
            wyckoffs = symm_struct.wyckoff_symbols

            lgf = LocalGeometryFinder()
            lgf.setup_structure(structure=struct)

            se = lgf.compute_structure_environments(
                maximum_distance_factor=distance_cutoff + 0.01,
                only_indices=inequivalent_indices,
                valences=get_valences(struct),
            )
            strategy = SimplestChemenvStrategy(distance_cutoff=distance_cutoff,
                                               angle_cutoff=angle_cutoff)
            lse = LightStructureEnvironments.from_structure_environments(
                strategy=strategy, structure_environments=se)
            all_ce = AllCoordinationGeometries()

            envs = []
            unknown_sites = []

            for index, wyckoff in zip(inequivalent_indices, wyckoffs):

                datalist = {
                    "Site": unicodeify_species(struct[index].species_string),
                    "Wyckoff Label": wyckoff,
                }

                if not lse.neighbors_sets[index]:
                    unknown_sites.append(
                        f"{struct[index].species_string} ({wyckoff})")
                    continue

                # represent the local environment as a molecule
                mol = Molecule.from_sites(
                    [struct[index]] +
                    lse.neighbors_sets[index][0].neighb_sites)
                mol = mol.get_centered_molecule()
                mg = MoleculeGraph.with_empty_graph(molecule=mol)
                for i in range(1, len(mol)):
                    mg.add_edge(0, i)

                view = html.Div(
                    [
                        StructureMoleculeComponent(
                            struct_or_mol=mg,
                            disable_callbacks=True,
                            id=
                            f"{struct.composition.reduced_formula}_site_{index}",
                            scene_settings={
                                "enableZoom": False,
                                "defaultZoom": 0.6
                            },
                        )._sub_layouts["struct"]
                    ],
                    style={
                        "width": "300px",
                        "height": "300px"
                    },
                )

                env = lse.coordination_environments[index]
                co = all_ce.get_geometry_from_mp_symbol(env[0]["ce_symbol"])
                name = co.name
                if co.alternative_names:
                    name += f" (also known as {', '.join(co.alternative_names)})"

                datalist.update({
                    "Environment":
                    name,
                    "IUPAC Symbol":
                    co.IUPAC_symbol_str,
                    get_tooltip(
                        "CSM",
                        "The continuous symmetry measure (CSM) describes the similarity to an "
                        "ideal coordination environment. It can be understood as a 'distance' to "
                        "a shape and ranges from 0 to 100 in which 0 corresponds to a "
                        "coordination environment that is exactly identical to the ideal one. A "
                        "CSM larger than 5.0 already indicates a relatively strong distortion of "
                        "the investigated coordination environment.",
                    ):
                    f"{env[0]['csm']:.2f}",
                    "Interactive View":
                    view,
                })

                envs.append(get_data_list(datalist))

            # TODO: switch to tiles?
            envs_grouped = [envs[i:i + 2] for i in range(0, len(envs), 2)]
            analysis_contents = []
            for env_group in envs_grouped:
                analysis_contents.append(
                    Columns([Column(e, size=6) for e in env_group]))

            if unknown_sites:
                unknown_sites = html.Strong(
                    f"The following sites were not identified: {', '.join(unknown_sites)}. "
                    f"Please try changing the distance or angle cut-offs to identify these sites, "
                    f"or try an alternative algorithm such as LocalEnv.")
            else:
                unknown_sites = html.Span()

            return html.Div(
                [html.Div(analysis_contents),
                 html.Br(), unknown_sites])
Example #39
0
class SpacegroupAnalyzerTest(PymatgenTest):
    def setUp(self):
        p = Poscar.from_file(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "POSCAR"))
        self.structure = p.structure
        self.sg = SpacegroupAnalyzer(self.structure, 0.001)
        self.disordered_structure = self.get_structure("Li10GeP2S12")
        self.disordered_sg = SpacegroupAnalyzer(self.disordered_structure,
                                                0.001)
        s = p.structure.copy()
        site = s[0]
        del s[0]
        s.append(site.species, site.frac_coords)
        self.sg3 = SpacegroupAnalyzer(s, 0.001)
        graphite = self.get_structure("Graphite")
        graphite.add_site_property("magmom", [0.1] * len(graphite))
        self.sg4 = SpacegroupAnalyzer(graphite, 0.001)
        self.structure4 = graphite

    def test_primitive(self):
        s = Structure.from_spacegroup("Fm-3m",
                                      np.eye(3) * 3, ["Cu"], [[0, 0, 0]])
        a = SpacegroupAnalyzer(s)
        self.assertEqual(len(s), 4)
        self.assertEqual(len(a.find_primitive()), 1)

    def test_is_laue(self):
        s = Structure.from_spacegroup("Fm-3m",
                                      np.eye(3) * 3, ["Cu"], [[0, 0, 0]])
        a = SpacegroupAnalyzer(s)
        self.assertTrue(a.is_laue())

    def test_magnetic(self):
        lfp = PymatgenTest.get_structure("LiFePO4")
        sg = SpacegroupAnalyzer(lfp, 0.1)
        self.assertEqual(sg.get_space_group_symbol(), "Pnma")
        magmoms = [0] * len(lfp)
        magmoms[4] = 1
        magmoms[5] = -1
        magmoms[6] = 1
        magmoms[7] = -1
        lfp.add_site_property("magmom", magmoms)
        sg = SpacegroupAnalyzer(lfp, 0.1)
        self.assertEqual(sg.get_space_group_symbol(), "Pnma")

    def test_get_space_symbol(self):
        self.assertEqual(self.sg.get_space_group_symbol(), "Pnma")
        self.assertEqual(self.disordered_sg.get_space_group_symbol(),
                         "P4_2/nmc")
        self.assertEqual(self.sg3.get_space_group_symbol(), "Pnma")
        self.assertEqual(self.sg4.get_space_group_symbol(), "P6_3/mmc")

    def test_get_space_number(self):
        self.assertEqual(self.sg.get_space_group_number(), 62)
        self.assertEqual(self.disordered_sg.get_space_group_number(), 137)
        self.assertEqual(self.sg4.get_space_group_number(), 194)

    def test_get_hall(self):
        self.assertEqual(self.sg.get_hall(), "-P 2ac 2n")
        self.assertEqual(self.disordered_sg.get_hall(), "P 4n 2n -1n")

    def test_get_pointgroup(self):
        self.assertEqual(self.sg.get_point_group_symbol(), "mmm")
        self.assertEqual(self.disordered_sg.get_point_group_symbol(), "4/mmm")

    def test_get_symmetry_operations(self):

        for sg, structure in [(self.sg, self.structure),
                              (self.sg4, self.structure4)]:

            pgops = sg.get_point_group_operations()
            fracsymmops = sg.get_symmetry_operations()
            symmops = sg.get_symmetry_operations(True)
            latt = structure.lattice
            for fop, op, pgop in zip(fracsymmops, symmops, pgops):
                # translation vector values should all be 0 or 0.5
                t = fop.translation_vector * 2
                self.assertArrayAlmostEqual(t - np.round(t), 0)

                self.assertArrayAlmostEqual(fop.rotation_matrix,
                                            pgop.rotation_matrix)
                for site in structure:
                    newfrac = fop.operate(site.frac_coords)
                    newcart = op.operate(site.coords)
                    self.assertTrue(
                        np.allclose(latt.get_fractional_coords(newcart),
                                    newfrac))
                    found = False
                    newsite = PeriodicSite(site.species,
                                           newcart,
                                           latt,
                                           coords_are_cartesian=True)
                    for testsite in structure:
                        if newsite.is_periodic_image(testsite, 1e-3):
                            found = True
                            break
                    self.assertTrue(found)

                # Make sure this works for any position, not just the atomic
                # ones.
                random_fcoord = np.random.uniform(size=(3))
                random_ccoord = latt.get_cartesian_coords(random_fcoord)
                newfrac = fop.operate(random_fcoord)
                newcart = op.operate(random_ccoord)
                self.assertTrue(
                    np.allclose(latt.get_fractional_coords(newcart), newfrac))

    def test_get_symmetry_dataset(self):
        ds = self.sg.get_symmetry_dataset()
        self.assertEqual(ds["international"], "Pnma")

    def test_get_crystal_system(self):
        crystal_system = self.sg.get_crystal_system()
        self.assertEqual("orthorhombic", crystal_system)
        self.assertEqual("tetragonal", self.disordered_sg.get_crystal_system())

        orig_spg = self.sg._space_group_data["number"]
        self.sg._space_group_data["number"] = 0
        try:
            crystal_system = self.sg.get_crystal_system()
        except ValueError as exc:
            self.assertEqual(str(exc), "Received invalid space group 0")
        finally:
            self.sg._space_group_data["number"] = orig_spg

    def test_get_refined_structure(self):
        for a in self.sg.get_refined_structure().lattice.angles:
            self.assertEqual(a, 90)
        refined = self.disordered_sg.get_refined_structure()
        for a in refined.lattice.angles:
            self.assertEqual(a, 90)
        self.assertEqual(refined.lattice.a, refined.lattice.b)

        structure = self.get_structure("Li2O")
        structure.add_site_property("magmom", [1.0] * len(structure))
        sg = SpacegroupAnalyzer(structure, 0.01)
        refined_struct = sg.get_refined_structure(keep_site_properties=True)
        self.assertEqual(refined_struct.site_properties["magmom"],
                         [1.0] * len(refined_struct))

        structure = self.get_structure("Li2O")
        structure.add_site_property("magmom", [1.0] * len(structure))
        sg = SpacegroupAnalyzer(structure, 0.01)
        refined_struct = sg.get_refined_structure(keep_site_properties=False)
        self.assertEqual(refined_struct.site_properties.get("magmom", None),
                         None)

    def test_get_symmetrized_structure(self):
        symm_struct = self.sg.get_symmetrized_structure()
        for a in symm_struct.lattice.angles:
            self.assertEqual(a, 90)
        self.assertEqual(len(symm_struct.equivalent_sites), 5)

        symm_struct = self.disordered_sg.get_symmetrized_structure()
        self.assertEqual(len(symm_struct.equivalent_sites), 8)
        self.assertEqual([len(i) for i in symm_struct.equivalent_sites],
                         [16, 4, 8, 4, 2, 8, 8, 8])
        s1 = symm_struct.equivalent_sites[1][1]
        s2 = symm_struct[symm_struct.equivalent_indices[1][1]]
        self.assertEqual(s1, s2)
        self.assertEqual(self.sg4.get_symmetrized_structure()[0].magmom, 0.1)
        self.assertEqual(symm_struct.wyckoff_symbols[0], "16h")
        # self.assertEqual(symm_struct[0].wyckoff, "16h")

        # Check copying
        self.assertEqual(symm_struct.copy(), symm_struct)
        d = symm_struct.as_dict()
        from pymatgen.symmetry.structure import SymmetrizedStructure

        ss = SymmetrizedStructure.from_dict(d)
        self.assertEqual(ss.wyckoff_symbols[0], "16h")
        self.assertIn("SymmetrizedStructure", ss.__str__())

    def test_find_primitive(self):
        """
        F m -3 m Li2O testing of converting to primitive cell
        """
        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "Li2O.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure)
        primitive_structure = s.find_primitive()
        self.assertEqual(primitive_structure.formula, "Li2 O1")
        self.assertTrue(
            primitive_structure.site_properties.get("magmom", None) is None)
        # This isn't what is expected. All the angles should be 60
        self.assertAlmostEqual(primitive_structure.lattice.alpha, 60)
        self.assertAlmostEqual(primitive_structure.lattice.beta, 60)
        self.assertAlmostEqual(primitive_structure.lattice.gamma, 60)
        self.assertAlmostEqual(primitive_structure.lattice.volume,
                               structure.lattice.volume / 4.0)

        structure = parser.get_structures(False)[0]
        structure.add_site_property("magmom", [1.0] * len(structure))
        s = SpacegroupAnalyzer(structure)
        primitive_structure = s.find_primitive(keep_site_properties=True)
        self.assertEqual(primitive_structure.site_properties["magmom"],
                         [1.0] * len(primitive_structure))

        structure = parser.get_structures(False)[0]
        structure.add_site_property("magmom", [1.0] * len(structure))
        s = SpacegroupAnalyzer(structure)
        primitive_structure = s.find_primitive(keep_site_properties=False)
        self.assertEqual(
            primitive_structure.site_properties.get("magmom", None), None)

    def test_get_ir_reciprocal_mesh(self):
        grid = self.sg.get_ir_reciprocal_mesh()
        self.assertEqual(len(grid), 216)
        self.assertAlmostEqual(grid[1][0][0], 0.1)
        self.assertAlmostEqual(grid[1][0][1], 0.0)
        self.assertAlmostEqual(grid[1][0][2], 0.0)
        self.assertAlmostEqual(grid[1][1], 2)

    def test_get_conventional_standard_structure(self):
        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "bcc_1927.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 9.1980270633769461)
        self.assertAlmostEqual(conv.lattice.b, 9.1980270633769461)
        self.assertAlmostEqual(conv.lattice.c, 9.1980270633769461)

        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "btet_1915.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 5.0615106678044235)
        self.assertAlmostEqual(conv.lattice.b, 5.0615106678044235)
        self.assertAlmostEqual(conv.lattice.c, 4.2327080177761687)

        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "orci_1010.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 2.9542233922299999)
        self.assertAlmostEqual(conv.lattice.b, 4.6330325651443296)
        self.assertAlmostEqual(conv.lattice.c, 5.373703587040775)

        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "orcc_1003.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 4.1430033493799998)
        self.assertAlmostEqual(conv.lattice.b, 31.437979757624728)
        self.assertAlmostEqual(conv.lattice.c, 3.99648651)

        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "orac_632475.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 3.1790663399999999)
        self.assertAlmostEqual(conv.lattice.b, 9.9032878699999998)
        self.assertAlmostEqual(conv.lattice.c, 3.5372412099999999)

        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "monoc_1028.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 117.53832420192903)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 14.033435583000625)
        self.assertAlmostEqual(conv.lattice.b, 3.96052850731)
        self.assertAlmostEqual(conv.lattice.c, 6.8743926325200002)
        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "hex_1170.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 120)
        self.assertAlmostEqual(conv.lattice.a, 3.699919902005897)
        self.assertAlmostEqual(conv.lattice.b, 3.699919902005897)
        self.assertAlmostEqual(conv.lattice.c, 6.9779585500000003)

        structure = Structure.from_file(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "tric_684654.json"))
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 74.09581916308757)
        self.assertAlmostEqual(conv.lattice.beta, 75.72817279281173)
        self.assertAlmostEqual(conv.lattice.gamma, 63.63234318667333)
        self.assertAlmostEqual(conv.lattice.a, 3.741372924048738)
        self.assertAlmostEqual(conv.lattice.b, 3.9883228679270686)
        self.assertAlmostEqual(conv.lattice.c, 7.288495840048958)

        structure = Structure.from_file(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "tric_684654.json"))
        structure.add_site_property("magmom", [1.0] * len(structure))
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure(keep_site_properties=True)
        self.assertEqual(conv.site_properties["magmom"], [1.0] * len(conv))

        structure = Structure.from_file(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "tric_684654.json"))
        structure.add_site_property("magmom", [1.0] * len(structure))
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure(
            keep_site_properties=False)
        self.assertEqual(conv.site_properties.get("magmom", None), None)

    def test_get_primitive_standard_structure(self):
        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "bcc_1927.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 109.47122063400001)
        self.assertAlmostEqual(prim.lattice.beta, 109.47122063400001)
        self.assertAlmostEqual(prim.lattice.gamma, 109.47122063400001)
        self.assertAlmostEqual(prim.lattice.a, 7.9657251015812145)
        self.assertAlmostEqual(prim.lattice.b, 7.9657251015812145)
        self.assertAlmostEqual(prim.lattice.c, 7.9657251015812145)

        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "btet_1915.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 105.015053349)
        self.assertAlmostEqual(prim.lattice.beta, 105.015053349)
        self.assertAlmostEqual(prim.lattice.gamma, 118.80658411899999)
        self.assertAlmostEqual(prim.lattice.a, 4.1579321075608791)
        self.assertAlmostEqual(prim.lattice.b, 4.1579321075608791)
        self.assertAlmostEqual(prim.lattice.c, 4.1579321075608791)

        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "orci_1010.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 134.78923546600001)
        self.assertAlmostEqual(prim.lattice.beta, 105.856239333)
        self.assertAlmostEqual(prim.lattice.gamma, 91.276341676000001)
        self.assertAlmostEqual(prim.lattice.a, 3.8428217771014852)
        self.assertAlmostEqual(prim.lattice.b, 3.8428217771014852)
        self.assertAlmostEqual(prim.lattice.c, 3.8428217771014852)

        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "orcc_1003.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 90)
        self.assertAlmostEqual(prim.lattice.beta, 90)
        self.assertAlmostEqual(prim.lattice.gamma, 164.985257335)
        self.assertAlmostEqual(prim.lattice.a, 15.854897098324196)
        self.assertAlmostEqual(prim.lattice.b, 15.854897098324196)
        self.assertAlmostEqual(prim.lattice.c, 3.99648651)

        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "orac_632475.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 90)
        self.assertAlmostEqual(prim.lattice.beta, 90)
        self.assertAlmostEqual(prim.lattice.gamma, 144.40557588533386)
        self.assertAlmostEqual(prim.lattice.a, 5.2005185662155391)
        self.assertAlmostEqual(prim.lattice.b, 5.2005185662155391)
        self.assertAlmostEqual(prim.lattice.c, 3.5372412099999999)

        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "monoc_1028.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 63.579155761999999)
        self.assertAlmostEqual(prim.lattice.beta, 116.42084423747779)
        self.assertAlmostEqual(prim.lattice.gamma, 148.47965136208569)
        self.assertAlmostEqual(prim.lattice.a, 7.2908007159612325)
        self.assertAlmostEqual(prim.lattice.b, 7.2908007159612325)
        self.assertAlmostEqual(prim.lattice.c, 6.8743926325200002)

        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "hex_1170.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 90)
        self.assertAlmostEqual(prim.lattice.beta, 90)
        self.assertAlmostEqual(prim.lattice.gamma, 120)
        self.assertAlmostEqual(prim.lattice.a, 3.699919902005897)
        self.assertAlmostEqual(prim.lattice.b, 3.699919902005897)
        self.assertAlmostEqual(prim.lattice.c, 6.9779585500000003)

        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "rhomb_3478_conv.cif"))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 28.049186140546812)
        self.assertAlmostEqual(prim.lattice.beta, 28.049186140546812)
        self.assertAlmostEqual(prim.lattice.gamma, 28.049186140546812)
        self.assertAlmostEqual(prim.lattice.a, 5.9352627428399982)
        self.assertAlmostEqual(prim.lattice.b, 5.9352627428399982)
        self.assertAlmostEqual(prim.lattice.c, 5.9352627428399982)

        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "rhomb_3478_conv.cif"))
        structure = parser.get_structures(False)[0]
        structure.add_site_property("magmom", [1.0] * len(structure))
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure(keep_site_properties=True)
        self.assertEqual(prim.site_properties["magmom"], [1.0] * len(prim))

        parser = CifParser(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "rhomb_3478_conv.cif"))
        structure = parser.get_structures(False)[0]
        structure.add_site_property("magmom", [1.0] * len(structure))
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure(keep_site_properties=False)
        self.assertEqual(prim.site_properties.get("magmom", None), None)

    def test_tricky_structure(self):
        # for some reason this structure kills spglib1.9
        # 1.7 can't find symmetry either, but at least doesn't kill python
        s = Structure.from_file(
            os.path.join(PymatgenTest.TEST_FILES_DIR,
                         "POSCAR.tricky_symmetry"))
        sa = SpacegroupAnalyzer(s, 0.1)
        sa.get_space_group_symbol()
        sa.get_space_group_number()
        sa.get_point_group_symbol()
        sa.get_crystal_system()
        sa.get_hall()
Example #40
0
class SpacegroupAnalyzerTest(PymatgenTest):

    def setUp(self):
        p = Poscar.from_file(os.path.join(test_dir, 'POSCAR'))
        self.structure = p.structure
        self.sg = SpacegroupAnalyzer(self.structure, 0.001)
        self.disordered_structure = self.get_structure('Li10GeP2S12')
        self.disordered_sg = SpacegroupAnalyzer(self.disordered_structure, 0.001)
        s = p.structure.copy()
        site = s[0]
        del s[0]
        s.append(site.species_and_occu, site.frac_coords)
        self.sg3 = SpacegroupAnalyzer(s, 0.001)
        graphite = self.get_structure('Graphite')
        graphite.add_site_property("magmom", [0.1] * len(graphite))
        self.sg4 = SpacegroupAnalyzer(graphite, 0.001)

    def test_get_space_symbol(self):
        self.assertEqual(self.sg.get_spacegroup_symbol(), "Pnma")
        self.assertEqual(self.disordered_sg.get_spacegroup_symbol(),
                         "P4_2/nmc")
        self.assertEqual(self.sg3.get_spacegroup_symbol(), "Pnma")
        self.assertEqual(self.sg4.get_spacegroup_symbol(), "R-3m")

    def test_get_space_number(self):
        self.assertEqual(self.sg.get_spacegroup_number(), 62)
        self.assertEqual(self.disordered_sg.get_spacegroup_number(), 137)
        self.assertEqual(self.sg4.get_spacegroup_number(), 166)

    def test_get_hall(self):
        self.assertEqual(self.sg.get_hall(), '-P 2ac 2n')
        self.assertEqual(self.disordered_sg.get_hall(), 'P 4n 2n -1n')

    def test_get_pointgroup(self):
        self.assertEqual(self.sg.get_point_group(), 'mmm')
        self.assertEqual(self.disordered_sg.get_point_group(), '4/mmm')

    def test_get_symmetry_dataset(self):
        ds = self.sg.get_symmetry_dataset()
        self.assertEqual(ds['international'], 'Pnma')

    def test_get_crystal_system(self):
        crystal_system = self.sg.get_crystal_system()
        self.assertEqual('orthorhombic', crystal_system)
        self.assertEqual('tetragonal', self.disordered_sg.get_crystal_system())

    def test_get_symmetry_operations(self):
        fracsymmops = self.sg.get_symmetry_operations()
        symmops = self.sg.get_symmetry_operations(True)
        self.assertEqual(len(symmops), 8)
        latt = self.structure.lattice
        for fop, op in zip(fracsymmops, symmops):
            for site in self.structure:
                newfrac = fop.operate(site.frac_coords)
                newcart = op.operate(site.coords)
                self.assertTrue(np.allclose(latt.get_fractional_coords(newcart),
                                            newfrac))
                found = False
                newsite = PeriodicSite(site.species_and_occu, newcart, latt,
                                       coords_are_cartesian=True)
                for testsite in self.structure:
                    if newsite.is_periodic_image(testsite, 1e-3):
                        found = True
                        break
                self.assertTrue(found)

    def test_get_refined_structure(self):
        for a in self.sg.get_refined_structure().lattice.angles:
            self.assertEqual(a, 90)
        refined = self.disordered_sg.get_refined_structure()
        for a in refined.lattice.angles:
            self.assertEqual(a, 90)
        self.assertEqual(refined.lattice.a, refined.lattice.b)
        s = self.get_structure('Li2O')
        sg = SpacegroupAnalyzer(s, 0.001)
        self.assertEqual(sg.get_refined_structure().num_sites, 4 * s.num_sites)

    def test_get_symmetrized_structure(self):
        symm_struct = self.sg.get_symmetrized_structure()
        for a in symm_struct.lattice.angles:
            self.assertEqual(a, 90)
        self.assertEqual(len(symm_struct.equivalent_sites), 5)

        symm_struct = self.disordered_sg.get_symmetrized_structure()
        self.assertEqual(len(symm_struct.equivalent_sites), 8)
        self.assertEqual([len(i) for i in symm_struct.equivalent_sites],
                         [16,4,8,4,2,8,8,8])
        s1 = symm_struct.equivalent_sites[1][1]
        s2 = symm_struct[symm_struct.equivalent_indices[1][1]]
        self.assertEqual(s1, s2)
        self.assertEqual(self.sg4.get_symmetrized_structure()[0].magmom, 0.1)

    def test_find_primitive(self):
        """
        F m -3 m Li2O testing of converting to primitive cell
        """
        parser = CifParser(os.path.join(test_dir, 'Li2O.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure)
        primitive_structure = s.find_primitive()
        self.assertEqual(primitive_structure.formula, "Li2 O1")
        # This isn't what is expected. All the angles should be 60
        self.assertAlmostEqual(primitive_structure.lattice.alpha, 60)
        self.assertAlmostEqual(primitive_structure.lattice.beta, 60)
        self.assertAlmostEqual(primitive_structure.lattice.gamma, 60)
        self.assertAlmostEqual(primitive_structure.lattice.volume,
                               structure.lattice.volume / 4.0)

    def test_get_ir_reciprocal_mesh(self):
        grid=self.sg.get_ir_reciprocal_mesh()
        self.assertEqual(len(grid), 216)
        self.assertAlmostEquals(grid[1][0][0], 0.1)
        self.assertAlmostEquals(grid[1][0][1], 0.0)
        self.assertAlmostEquals(grid[1][0][2], 0.0)
        self.assertEqual(grid[1][1], 2)

    def test_get_conventional_standard_structure(self):
        parser = CifParser(os.path.join(test_dir, 'bcc_1927.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 9.1980270633769461)
        self.assertAlmostEqual(conv.lattice.b, 9.1980270633769461)
        self.assertAlmostEqual(conv.lattice.c, 9.1980270633769461)

        parser = CifParser(os.path.join(test_dir, 'btet_1915.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 5.0615106678044235)
        self.assertAlmostEqual(conv.lattice.b, 5.0615106678044235)
        self.assertAlmostEqual(conv.lattice.c, 4.2327080177761687)

        parser = CifParser(os.path.join(test_dir, 'orci_1010.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 2.9542233922299999)
        self.assertAlmostEqual(conv.lattice.b, 4.6330325651443296)
        self.assertAlmostEqual(conv.lattice.c, 5.373703587040775)

        parser = CifParser(os.path.join(test_dir, 'orcc_1003.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 4.1430033493799998)
        self.assertAlmostEqual(conv.lattice.b, 31.437979757624728)
        self.assertAlmostEqual(conv.lattice.c, 3.99648651)

        parser = CifParser(os.path.join(test_dir, 'monoc_1028.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 117.53832420192903)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 14.033435583000625)
        self.assertAlmostEqual(conv.lattice.b, 3.96052850731)
        self.assertAlmostEqual(conv.lattice.c, 6.8743926325200002)

        parser = CifParser(os.path.join(test_dir, 'rhomb_1170.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 120)
        self.assertAlmostEqual(conv.lattice.a, 3.699919902005897)
        self.assertAlmostEqual(conv.lattice.b, 3.699919902005897)
        self.assertAlmostEqual(conv.lattice.c, 6.9779585500000003)

    def test_get_primitive_standard_structure(self):
        parser = CifParser(os.path.join(test_dir, 'bcc_1927.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 109.47122063400001)
        self.assertAlmostEqual(prim.lattice.beta, 109.47122063400001)
        self.assertAlmostEqual(prim.lattice.gamma, 109.47122063400001)
        self.assertAlmostEqual(prim.lattice.a, 7.9657251015812145)
        self.assertAlmostEqual(prim.lattice.b, 7.9657251015812145)
        self.assertAlmostEqual(prim.lattice.c, 7.9657251015812145)

        parser = CifParser(os.path.join(test_dir, 'btet_1915.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 105.015053349)
        self.assertAlmostEqual(prim.lattice.beta, 105.015053349)
        self.assertAlmostEqual(prim.lattice.gamma, 118.80658411899999)
        self.assertAlmostEqual(prim.lattice.a, 4.1579321075608791)
        self.assertAlmostEqual(prim.lattice.b, 4.1579321075608791)
        self.assertAlmostEqual(prim.lattice.c, 4.1579321075608791)

        parser = CifParser(os.path.join(test_dir, 'orci_1010.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 134.78923546600001)
        self.assertAlmostEqual(prim.lattice.beta, 105.856239333)
        self.assertAlmostEqual(prim.lattice.gamma, 91.276341676000001)
        self.assertAlmostEqual(prim.lattice.a, 3.8428217771014852)
        self.assertAlmostEqual(prim.lattice.b, 3.8428217771014852)
        self.assertAlmostEqual(prim.lattice.c, 3.8428217771014852)

        parser = CifParser(os.path.join(test_dir, 'orcc_1003.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 90)
        self.assertAlmostEqual(prim.lattice.beta, 90)
        self.assertAlmostEqual(prim.lattice.gamma, 164.985257335)
        self.assertAlmostEqual(prim.lattice.a, 15.854897098324196)
        self.assertAlmostEqual(prim.lattice.b, 15.854897098324196)
        self.assertAlmostEqual(prim.lattice.c, 3.99648651)

        parser = CifParser(os.path.join(test_dir, 'monoc_1028.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 63.579155761999999)
        self.assertAlmostEqual(prim.lattice.beta, 116.42084423747779)
        self.assertAlmostEqual(prim.lattice.gamma, 148.47965136208569)
        self.assertAlmostEqual(prim.lattice.a, 7.2908007159612325)
        self.assertAlmostEqual(prim.lattice.b, 7.2908007159612325)
        self.assertAlmostEqual(prim.lattice.c, 6.8743926325200002)

        parser = CifParser(os.path.join(test_dir, 'rhomb_1170.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 90)
        self.assertAlmostEqual(prim.lattice.beta, 90)
        self.assertAlmostEqual(prim.lattice.gamma, 120)
        self.assertAlmostEqual(prim.lattice.a, 3.699919902005897)
        self.assertAlmostEqual(prim.lattice.b, 3.699919902005897)
        self.assertAlmostEqual(prim.lattice.c, 6.9779585500000003)
Example #41
0
    def __init__(self, struct, find_spacegroup=False, symprec=None):
        """
        Args:
            struct (Structure): structure to write
            find_spacegroup (bool): whether to try to determine the spacegroup
            symprec (float): If not none, finds the symmetry of the structure and
                writes the cif with symmetry information. Passes symprec to the
                SpacegroupAnalyzer
        """
        format_str = "{:.8f}"
        
        block = OrderedDict()
        loops = []
        latt = struct.lattice
        comp = struct.composition
        no_oxi_comp = comp.element_composition
        spacegroup = ("P 1", 1)
        if find_spacegroup:
            sf = SpacegroupAnalyzer(struct, 0.001)
            spacegroup = (sf.get_spacegroup_symbol(),
                          sf.get_spacegroup_number())
        block["_symmetry_space_group_name_H-M"] = spacegroup[0]
        for cell_attr in ['a', 'b', 'c']:
            block["_cell_length_" + cell_attr] = format_str.format(
                getattr(latt, cell_attr))
        for cell_attr in ['alpha', 'beta', 'gamma']:
            block["_cell_angle_" + cell_attr] = format_str.format(
                getattr(latt, cell_attr))
        block["_symmetry_Int_Tables_number"] = spacegroup[1]
        block["_chemical_formula_structural"] = no_oxi_comp.reduced_formula
        block["_chemical_formula_sum"] = no_oxi_comp.formula
        block["_cell_volume"] = latt.volume.__str__()

        reduced_comp = no_oxi_comp.reduced_composition
        el = no_oxi_comp.elements[0]
        amt = comp[el]
        fu = int(amt / reduced_comp[Element(el.symbol)])

        block["_cell_formula_units_Z"] = str(fu)

        if symprec is None:
            block["_symmetry_equiv_pos_site_id"] = ["1"]
            block["_symmetry_equiv_pos_as_xyz"] = ["x, y, z"]
        else:
            sf = SpacegroupAnalyzer(struct, symprec)
            ops = [op.as_xyz_string() for op in sf.get_symmetry_operations()]
            block["_symmetry_equiv_pos_site_id"] = \
                ["%d" % i for i in range(1, len(ops) + 1)]
            block["_symmetry_equiv_pos_as_xyz"] = ops

        loops.append(["_symmetry_equiv_pos_site_id",
                      "_symmetry_equiv_pos_as_xyz"])

        contains_oxidation = True
        try:
            symbol_to_oxinum = OrderedDict([
                (el.__str__(), float(el.oxi_state))
                for el in sorted(comp.elements)])
        except AttributeError:
            symbol_to_oxinum = OrderedDict([(el.symbol, 0) for el in 
                                            sorted(comp.elements)])
            contains_oxidation = False
        if contains_oxidation:
            block["_atom_type_symbol"] = symbol_to_oxinum.keys()
            block["_atom_type_oxidation_number"] = symbol_to_oxinum.values()
            loops.append(["_atom_type_symbol", "_atom_type_oxidation_number"])

        atom_site_type_symbol = []
        atom_site_symmetry_multiplicity = []
        atom_site_fract_x = []
        atom_site_fract_y = []
        atom_site_fract_z = []
        atom_site_label = []
        atom_site_occupancy = []
        count = 1
        if symprec is None:
            for site in struct:
                for sp, occu in site.species_and_occu.items():
                    atom_site_type_symbol.append(sp.__str__())
                    atom_site_symmetry_multiplicity.append("1")
                    atom_site_fract_x.append("{0:f}".format(site.a))
                    atom_site_fract_y.append("{0:f}".format(site.b))
                    atom_site_fract_z.append("{0:f}".format(site.c))
                    atom_site_label.append("{}{}".format(sp.symbol, count))
                    atom_site_occupancy.append(occu.__str__())
                    count += 1
        else:
            for group in sf.get_symmetrized_structure().equivalent_sites:
                site = group[0]
                for sp, occu in site.species_and_occu.items():
                    atom_site_type_symbol.append(sp.__str__())
                    atom_site_symmetry_multiplicity.append("%d" % len(group))
                    atom_site_fract_x.append("{0:f}".format(site.a))
                    atom_site_fract_y.append("{0:f}".format(site.b))
                    atom_site_fract_z.append("{0:f}".format(site.c))
                    atom_site_label.append("{}{}".format(sp.symbol, count))
                    atom_site_occupancy.append(occu.__str__())
                    count += 1

        block["_atom_site_type_symbol"] = atom_site_type_symbol
        block["_atom_site_label"] = atom_site_label
        block["_atom_site_symmetry_multiplicity"] = \
            atom_site_symmetry_multiplicity
        block["_atom_site_fract_x"] = atom_site_fract_x
        block["_atom_site_fract_y"] = atom_site_fract_y
        block["_atom_site_fract_z"] = atom_site_fract_z
        block["_atom_site_occupancy"] = atom_site_occupancy
        loops.append(["_atom_site_type_symbol",
                      "_atom_site_label",
                      "_atom_site_symmetry_multiplicity",
                      "_atom_site_fract_x",
                      "_atom_site_fract_y",
                      "_atom_site_fract_z",
                      "_atom_site_occupancy"])
        d = OrderedDict()
        d[comp.reduced_formula] = CifBlock(block, loops, comp.reduced_formula)
        self._cf = CifFile(d)
Example #42
0
    def _gen_input_file(self):
        """
        Generate the necessary struct_enum.in file for enumlib. See enumlib
        documentation for details.
        """
        coord_format = "{:.6f} {:.6f} {:.6f}"
        # Using symmetry finder, get the symmetrically distinct sites.
        fitter = SpacegroupAnalyzer(self.structure, self.symm_prec)
        symmetrized_structure = fitter.get_symmetrized_structure()
        logger.debug("Spacegroup {} ({}) with {} distinct sites".format(
            fitter.get_space_group_symbol(),
            fitter.get_space_group_number(),
            len(symmetrized_structure.equivalent_sites))
        )

        """
        Enumlib doesn"t work when the number of species get too large. To
        simplify matters, we generate the input file only with disordered sites
        and exclude the ordered sites from the enumeration. The fact that
        different disordered sites with the exact same species may belong to
        different equivalent sites is dealt with by having determined the
        spacegroup earlier and labelling the species differently.
        """

        # index_species and index_amounts store mappings between the indices
        # used in the enum input file, and the actual species and amounts.
        index_species = []
        index_amounts = []

        # Stores the ordered sites, which are not enumerated.
        ordered_sites = []
        disordered_sites = []
        coord_str = []
        for sites in symmetrized_structure.equivalent_sites:
            if sites[0].is_ordered:
                ordered_sites.append(sites)
            else:
                sp_label = []
                species = {k: v for k, v in sites[0].species.items()}
                if sum(species.values()) < 1 - EnumlibAdaptor.amount_tol:
                    # Let us first make add a dummy element for every single
                    # site whose total occupancies don't sum to 1.
                    species[DummySpecie("X")] = 1 - sum(species.values())
                for sp in species.keys():
                    if sp not in index_species:
                        index_species.append(sp)
                        sp_label.append(len(index_species) - 1)
                        index_amounts.append(species[sp] * len(sites))
                    else:
                        ind = index_species.index(sp)
                        sp_label.append(ind)
                        index_amounts[ind] += species[sp] * len(sites)
                sp_label = "/".join(["{}".format(i) for i in sorted(sp_label)])
                for site in sites:
                    coord_str.append("{} {}".format(
                        coord_format.format(*site.coords),
                        sp_label))
                disordered_sites.append(sites)

        def get_sg_info(ss):
            finder = SpacegroupAnalyzer(Structure.from_sites(ss),
                                        self.symm_prec)
            return finder.get_space_group_number()

        target_sgnum = get_sg_info(symmetrized_structure.sites)
        curr_sites = list(itertools.chain.from_iterable(disordered_sites))
        sgnum = get_sg_info(curr_sites)
        ordered_sites = sorted(ordered_sites, key=lambda sites: len(sites))
        logger.debug("Disordered sites has sg # %d" % (sgnum))
        self.ordered_sites = []

        # progressively add ordered sites to our disordered sites
        # until we match the symmetry of our input structure
        if self.check_ordered_symmetry:
            while sgnum != target_sgnum and len(ordered_sites) > 0:
                sites = ordered_sites.pop(0)
                temp_sites = list(curr_sites) + sites
                new_sgnum = get_sg_info(temp_sites)
                if sgnum != new_sgnum:
                    logger.debug("Adding %s in enum. New sg # %d"
                                 % (sites[0].specie, new_sgnum))
                    index_species.append(sites[0].specie)
                    index_amounts.append(len(sites))
                    sp_label = len(index_species) - 1
                    for site in sites:
                        coord_str.append("{} {}".format(
                            coord_format.format(*site.coords),
                            sp_label))
                    disordered_sites.append(sites)
                    curr_sites = temp_sites
                    sgnum = new_sgnum
                else:
                    self.ordered_sites.extend(sites)

        for sites in ordered_sites:
            self.ordered_sites.extend(sites)

        self.index_species = index_species

        lattice = self.structure.lattice

        output = [self.structure.formula, "bulk"]
        for vec in lattice.matrix:
            output.append(coord_format.format(*vec))
        output.append("%d" % len(index_species))
        output.append("%d" % len(coord_str))
        output.extend(coord_str)

        output.append("{} {}".format(self.min_cell_size, self.max_cell_size))
        output.append(str(self.enum_precision_parameter))
        output.append("partial")

        ndisordered = sum([len(s) for s in disordered_sites])
        base = int(ndisordered*lcm(*[f.limit_denominator(ndisordered *
                                          self.max_cell_size).denominator
                                       for f in map(fractions.Fraction,
                                                    index_amounts)]))

        # This multiplicative factor of 10 is to prevent having too small bases
        # which can lead to rounding issues in the next step.
        # An old bug was that a base was set to 8, with a conc of 0.4:0.6. That
        # resulted in a range that overlaps and a conc of 0.5 satisfying this
        # enumeration. See Cu7Te5.cif test file.
        base *= 10

        # base = ndisordered #10 ** int(math.ceil(math.log10(ndisordered)))
        # To get a reasonable number of structures, we fix concentrations to the
        # range expected in the original structure.
        total_amounts = sum(index_amounts)
        for amt in index_amounts:
            conc = amt / total_amounts

            if abs(conc * base - round(conc * base)) < 1e-5:
                output.append("{} {} {}".format(int(round(conc * base)),
                                                int(round(conc * base)),
                                                base))
            else:
                min_conc = int(math.floor(conc * base))
                output.append("{} {} {}".format(min_conc - 1, min_conc + 1,
                                                base))
        output.append("")
        logger.debug("Generated input file:\n{}".format("\n".join(output)))
        with open("struct_enum.in", "w") as f:
            f.write("\n".join(output))
Example #43
0
    def _gen_input_file(self):
        """
        Generate the necessary struct_enum.in file for enumlib. See enumlib
        documentation for details.
        """
        coord_format = "{:.6f} {:.6f} {:.6f}"

        # Using symmetry finder, get the symmetrically distinct sites.
        fitter = SpacegroupAnalyzer(self.structure, self.symm_prec)
        symmetrized_structure = fitter.get_symmetrized_structure()
        logger.debug("Spacegroup {} ({}) with {} distinct sites".format(
            fitter.get_spacegroup_symbol(),
            fitter.get_spacegroup_number(),
            len(symmetrized_structure.equivalent_sites))
        )

        """
        Enumlib doesn"t work when the number of species get too large. To
        simplify matters, we generate the input file only with disordered sites
        and exclude the ordered sites from the enumeration. The fact that
        different disordered sites with the exact same species may belong to
        different equivalent sites is dealt with by having determined the
        spacegroup earlier and labelling the species differently.
        """

        # index_species and index_amounts store mappings between the indices
        # used in the enum input file, and the actual species and amounts.
        index_species = []
        index_amounts = []

        #Stores the ordered sites, which are not enumerated.
        ordered_sites = []
        disordered_sites = []
        coord_str = []
        for sites in symmetrized_structure.equivalent_sites:
            if sites[0].is_ordered:
                ordered_sites.append(sites)
            else:
                sp_label = []
                species = {k: v for k, v in sites[0].species_and_occu.items()}
                if sum(species.values()) < 1 - EnumlibAdaptor.amount_tol:
                    #Let us first make add a dummy element for every single
                    #site whose total occupancies don't sum to 1.
                    species[DummySpecie("X")] = 1 - sum(species.values())
                for sp in species.keys():
                    if sp not in index_species:
                        index_species.append(sp)
                        sp_label.append(len(index_species) - 1)
                        index_amounts.append(species[sp] * len(sites))
                    else:
                        ind = index_species.index(sp)
                        sp_label.append(ind)
                        index_amounts[ind] += species[sp] * len(sites)
                sp_label = "/".join(["{}".format(i) for i in sorted(sp_label)])
                for site in sites:
                    coord_str.append("{} {}".format(
                        coord_format.format(*site.coords),
                        sp_label))
                disordered_sites.append(sites)

        def get_sg_info(ss):
            finder = SpacegroupAnalyzer(Structure.from_sites(ss), self.symm_prec)
            sgnum = finder.get_spacegroup_number()
            return sgnum

        curr_sites = list(itertools.chain.from_iterable(disordered_sites))
        min_sgnum = get_sg_info(curr_sites)
        logger.debug("Disorderd sites has sgnum %d" % (
            min_sgnum))
        #It could be that some of the ordered sites has a lower symmetry than
        #the disordered sites.  So we consider the lowest symmetry sites as
        #disordered in our enumeration.
        self.ordered_sites = []
        to_add = []

        if self.check_ordered_symmetry:
            for sites in ordered_sites:
                temp_sites = list(curr_sites) + sites
                sgnum = get_sg_info(temp_sites)
                if sgnum < min_sgnum:
                    logger.debug("Adding {} to sites to be ordered. "
                                 "New sgnum {}"
                                 .format(sites, sgnum))
                    to_add = sites
                    min_sgnum = sgnum

        for sites in ordered_sites:
            if sites == to_add:
                index_species.append(sites[0].specie)
                index_amounts.append(len(sites))
                sp_label = len(index_species) - 1
                logger.debug("Lowest symmetry {} sites are included in enum."
                             .format(sites[0].specie))
                for site in sites:
                    coord_str.append("{} {}".format(
                        coord_format.format(*site.coords),
                        sp_label))
                disordered_sites.append(sites)
            else:
                self.ordered_sites.extend(sites)

        self.index_species = index_species

        lattice = self.structure.lattice

        output = [self.structure.formula, "bulk"]
        for vec in lattice.matrix:
            output.append(coord_format.format(*vec))
        output.append("{}".format(len(index_species)))
        output.append("{}".format(len(coord_str)))
        output.extend(coord_str)

        output.append("{} {}".format(self.min_cell_size, self.max_cell_size))
        output.append(str(self.enum_precision_parameter))
        output.append("partial")

        ndisordered = sum([len(s) for s in disordered_sites])

        base = int(ndisordered*reduce(lcm,
                                      [f.limit_denominator(
                                          ndisordered *
                                          self.max_cell_size).denominator
                                       for f in map(fractions.Fraction,
                                                    index_amounts)]))
        #base = ndisordered #10 ** int(math.ceil(math.log10(ndisordered)))
        #To get a reasonable number of structures, we fix concentrations to the
        #range expected in the original structure.
        total_amounts = sum(index_amounts)
        for amt in index_amounts:
            conc = amt / total_amounts
            if abs(conc * base - round(conc * base)) < 1e-5:
                output.append("{} {} {}".format(int(round(conc * base)),
                                                int(round(conc * base)),
                                                base))
            else:
                min_conc = int(math.floor(conc * base))
                output.append("{} {} {}".format(min_conc - 1, min_conc + 1,
                                                base))
        output.append("")
        logger.debug("Generated input file:\n{}".format("\n".join(output)))
        with open("struct_enum.in", "w") as f:
            f.write("\n".join(output))
Example #44
0
def get_structure_graph_scene(
    self,
    origin=None,
    draw_image_atoms=True,
    bonded_sites_outside_unit_cell=True,
    hide_incomplete_edges=False,
    incomplete_edge_length_scale=0.3,
    color_edges_by_edge_weight=True,
    edge_weight_color_scale="coolwarm",
    explicitly_calculate_polyhedra_hull=False,
    legend: Optional[Legend] = None,
    group_by_symmetry: bool = True,
) -> Scene:

    origin = origin or list(
        -self.structure.lattice.get_cartesian_coords([0.5, 0.5, 0.5]))

    legend = legend or Legend(self.structure)

    primitives = defaultdict(list)

    sites_to_draw = self._get_sites_to_draw(
        draw_image_atoms=draw_image_atoms,
        bonded_sites_outside_unit_cell=bonded_sites_outside_unit_cell,
    )

    color_edges = False
    if color_edges_by_edge_weight:

        weights = [e[2].get("weight") for e in self.graph.edges(data=True)]
        weights = np.array([w for w in weights if w])

        if any(weights):

            cmap = get_cmap(edge_weight_color_scale)

            # try to keep color scheme symmetric around 0
            weight_max = max([abs(min(weights)), max(weights)])
            weight_min = -weight_max

            def get_weight_color(weight):
                if not weight:
                    weight = 0
                x = (weight - weight_min) / (weight_max - weight_min)
                return "#{:02x}{:02x}{:02x}".format(
                    *[int(c * 255) for c in cmap(x)[0:3]])

            color_edges = True

    idx_to_wyckoff = {}
    if group_by_symmetry:
        sga = SpacegroupAnalyzer(self.structure)
        struct_sym = sga.get_symmetrized_structure()
        for equiv_idxs, wyckoff in zip(struct_sym.equivalent_indices,
                                       struct_sym.wyckoff_symbols):
            for idx in equiv_idxs:
                idx_to_wyckoff[idx] = wyckoff

    for (idx, jimage) in sites_to_draw:

        site = self.structure[idx]
        if jimage != (0, 0, 0):
            connected_sites = self.get_connected_sites(idx, jimage=jimage)
            site = PeriodicSite(
                site.species,
                np.add(site.frac_coords, jimage),
                site.lattice,
                properties=site.properties,
            )
        else:
            connected_sites = self.get_connected_sites(idx)

        connected_sites = [
            cs for cs in connected_sites
            if (cs.index, cs.jimage) in sites_to_draw
        ]
        connected_sites_not_drawn = [
            cs for cs in connected_sites
            if (cs.index, cs.jimage) not in sites_to_draw
        ]

        if color_edges:

            connected_sites_colors = [
                get_weight_color(cs.weight) for cs in connected_sites
            ]
            connected_sites_not_drawn_colors = [
                get_weight_color(cs.weight) for cs in connected_sites_not_drawn
            ]

        else:

            connected_sites_colors = None
            connected_sites_not_drawn_colors = None

        site_scene = site.get_scene(
            connected_sites=connected_sites,
            connected_sites_not_drawn=connected_sites_not_drawn,
            hide_incomplete_edges=hide_incomplete_edges,
            incomplete_edge_length_scale=incomplete_edge_length_scale,
            connected_sites_colors=connected_sites_colors,
            connected_sites_not_drawn_colors=connected_sites_not_drawn_colors,
            explicitly_calculate_polyhedra_hull=
            explicitly_calculate_polyhedra_hull,
            legend=legend,
        )
        for scene in site_scene.contents:
            if group_by_symmetry and scene.name == "atoms" and idx in idx_to_wyckoff:
                # will rename to e.g. atoms_N_4e
                scene.name = f"atoms_{site_scene.name}_{idx_to_wyckoff[idx]}"
                # this is a proof-of-concept to demonstrate hover labels, could create label
                # automatically from site properties instead
                scene.contents[
                    0].tooltip = f"{site_scene.name} ({idx_to_wyckoff[idx]})"
            primitives[scene.name] += scene.contents

    primitives["unit_cell"].append(self.structure.lattice.get_scene())

    return Scene(
        name="StructureGraph",
        origin=origin,
        contents=[
            Scene(name=k, contents=v, origin=origin)
            for k, v in primitives.items()
        ],
    )
Example #45
0
    def _gen_input_file(self):
        """
        Generate the necessary struct_enum.in file for enumlib. See enumlib
        documentation for details.
        """
        coord_format = "{:.6f} {:.6f} {:.6f}"
        # Using symmetry finder, get the symmetrically distinct sites.
        fitter = SpacegroupAnalyzer(self.structure, self.symm_prec)
        symmetrized_structure = fitter.get_symmetrized_structure()
        logger.debug("Spacegroup {} ({}) with {} distinct sites".format(
            fitter.get_space_group_symbol(), fitter.get_space_group_number(),
            len(symmetrized_structure.equivalent_sites)))
        """
        Enumlib doesn"t work when the number of species get too large. To
        simplify matters, we generate the input file only with disordered sites
        and exclude the ordered sites from the enumeration. The fact that
        different disordered sites with the exact same species may belong to
        different equivalent sites is dealt with by having determined the
        spacegroup earlier and labelling the species differently.
        """

        # index_species and index_amounts store mappings between the indices
        # used in the enum input file, and the actual species and amounts.
        index_species = []
        index_amounts = []

        # Stores the ordered sites, which are not enumerated.
        ordered_sites = []
        disordered_sites = []
        coord_str = []
        for sites in symmetrized_structure.equivalent_sites:
            if sites[0].is_ordered:
                ordered_sites.append(sites)
            else:
                sp_label = []
                species = {k: v for k, v in sites[0].species_and_occu.items()}
                if sum(species.values()) < 1 - EnumlibAdaptor.amount_tol:
                    # Let us first make add a dummy element for every single
                    # site whose total occupancies don't sum to 1.
                    species[DummySpecie("X")] = 1 - sum(species.values())
                for sp in species.keys():
                    if sp not in index_species:
                        index_species.append(sp)
                        sp_label.append(len(index_species) - 1)
                        index_amounts.append(species[sp] * len(sites))
                    else:
                        ind = index_species.index(sp)
                        sp_label.append(ind)
                        index_amounts[ind] += species[sp] * len(sites)
                sp_label = "/".join(["{}".format(i) for i in sorted(sp_label)])
                for site in sites:
                    coord_str.append("{} {}".format(
                        coord_format.format(*site.coords), sp_label))
                disordered_sites.append(sites)

        def get_sg_info(ss):
            finder = SpacegroupAnalyzer(Structure.from_sites(ss),
                                        self.symm_prec)
            return finder.get_space_group_number()

        curr_sites = list(itertools.chain.from_iterable(disordered_sites))
        min_sgnum = get_sg_info(curr_sites)
        logger.debug("Disorderd sites has sgnum %d" % (min_sgnum))
        # It could be that some of the ordered sites has a lower symmetry than
        # the disordered sites.  So we consider the lowest symmetry sites as
        # disordered in our enumeration.
        self.ordered_sites = []
        to_add = []

        if self.check_ordered_symmetry:
            for sites in ordered_sites:
                temp_sites = list(curr_sites) + sites
                sgnum = get_sg_info(temp_sites)
                if sgnum < min_sgnum:
                    logger.debug("Adding {} to sites to be ordered. "
                                 "New sgnum {}".format(sites, sgnum))
                    to_add = sites
                    min_sgnum = sgnum

        for sites in ordered_sites:
            if sites == to_add:
                index_species.append(sites[0].specie)
                index_amounts.append(len(sites))
                sp_label = len(index_species) - 1
                logger.debug(
                    "Lowest symmetry {} sites are included in enum.".format(
                        sites[0].specie))
                for site in sites:
                    coord_str.append("{} {}".format(
                        coord_format.format(*site.coords), sp_label))
                disordered_sites.append(sites)
            else:
                self.ordered_sites.extend(sites)

        self.index_species = index_species

        lattice = self.structure.lattice

        output = [self.structure.formula, "bulk"]
        for vec in lattice.matrix:
            output.append(coord_format.format(*vec))
        output.append("{}".format(len(index_species)))
        output.append("{}".format(len(coord_str)))
        output.extend(coord_str)

        output.append("{} {}".format(self.min_cell_size, self.max_cell_size))
        output.append(str(self.enum_precision_parameter))
        output.append("partial")

        ndisordered = sum([len(s) for s in disordered_sites])

        base = int(ndisordered * reduce(lcm, [
            f.limit_denominator(ndisordered * self.max_cell_size).denominator
            for f in map(fractions.Fraction, index_amounts)
        ]))
        # base = ndisordered #10 ** int(math.ceil(math.log10(ndisordered)))
        # To get a reasonable number of structures, we fix concentrations to the
        # range expected in the original structure.
        total_amounts = sum(index_amounts)
        for amt in index_amounts:
            conc = amt / total_amounts
            if abs(conc * base - round(conc * base)) < 1e-5:
                output.append("{} {} {}".format(int(round(conc * base)),
                                                int(round(conc * base)), base))
            else:
                min_conc = int(math.floor(conc * base))
                output.append("{} {} {}".format(min_conc - 1, min_conc + 1,
                                                base))
        output.append("")
        logger.debug("Generated input file:\n{}".format("\n".join(output)))
        with open("struct_enum.in", "w") as f:
            f.write("\n".join(output))
Example #46
0
        def update_contents(data, symprec, angle_tolerance):

            if not data:
                return html.Div()

            struct = self.from_data(data)

            if not isinstance(struct, Structure):
                return html.Div(
                    "Can only analyze symmetry of crystal structures at present."
                )

            kwargs = self.reconstruct_kwargs_from_state(
                callback_context.inputs)
            symprec = kwargs["symprec"]
            angle_tolerance = kwargs["angle_tolerance"]

            if symprec <= 0:
                return html.Span(
                    f"Please use a positive symmetry-finding tolerance (currently {symprec})."
                )

            sga = SpacegroupAnalyzer(struct,
                                     symprec=symprec,
                                     angle_tolerance=angle_tolerance)

            try:
                data = dict()
                data["Crystal System"] = sga.get_crystal_system().title()
                data["Lattice System"] = sga.get_lattice_type().title()
                data["Hall Number"] = sga.get_hall()
                data["International Number"] = sga.get_space_group_number()
                data["Symbol"] = unicodeify_spacegroup(
                    sga.get_space_group_symbol())
                data["Point Group"] = unicodeify_spacegroup(
                    sga.get_point_group_symbol())

                sym_struct = sga.get_symmetrized_structure()
            except Exception:
                return html.Span(
                    f"Failed to calculate symmetry with this combination of "
                    f"symmetry-finding ({symprec}) and angle tolerances ({angle_tolerance})."
                )

            datalist = get_data_list(data)

            wyckoff_contents = []

            wyckoff_data = sorted(
                zip(sym_struct.wyckoff_symbols, sym_struct.equivalent_sites),
                key=lambda x: "".join(filter(lambda w: w.isalpha(), x[0])),
            )

            for symbol, equiv_sites in wyckoff_data:
                wyckoff_contents.append(
                    html.Label(
                        f"{symbol}, {unicodeify_species(equiv_sites[0].species_string)}",
                        className="mpc-label",
                    ))
                site_data = [(
                    self.pretty_frac_format(site.frac_coords[0]),
                    self.pretty_frac_format(site.frac_coords[1]),
                    self.pretty_frac_format(site.frac_coords[2]),
                ) for site in equiv_sites]
                wyckoff_contents.append(get_table(site_data))

            return Columns([
                Column([H5("Overview"), datalist]),
                Column([H5("Wyckoff Positions"),
                        html.Div(wyckoff_contents)]),
            ])
Example #47
0
    def __init__(self, struct, symprec=None):
        format_str = "{:.8f}"

        block = OrderedDict()
        loops = []
        spacegroup = ("P 1", 1)
        if symprec is not None:
            sf = SpacegroupAnalyzer(struct, symprec)
            spacegroup = (sf.get_spacegroup_symbol(),
                          sf.get_spacegroup_number())
            # Needs the refined struture when using symprec. This converts
            # primitive to conventional structures, the standard for CIF.
            struct = sf.get_refined_structure()

        latt = struct.lattice
        comp = struct.composition
        no_oxi_comp = comp.element_composition
        block["_symmetry_space_group_name_H-M"] = spacegroup[0]
        for cell_attr in ['a', 'b', 'c']:
            block["_cell_length_" + cell_attr] = format_str.format(
                getattr(latt, cell_attr))
        for cell_attr in ['alpha', 'beta', 'gamma']:
            block["_cell_angle_" + cell_attr] = format_str.format(
                getattr(latt, cell_attr))
        block["_symmetry_Int_Tables_number"] = spacegroup[1]
        block["_chemical_formula_structural"] = no_oxi_comp.reduced_formula
        block["_chemical_formula_sum"] = no_oxi_comp.formula
        block["_cell_volume"] = latt.volume.__str__()

        reduced_comp, fu = no_oxi_comp.get_reduced_composition_and_factor()
        block["_cell_formula_units_Z"] = str(int(fu))

        if symprec is None:
            block["_symmetry_equiv_pos_site_id"] = ["1"]
            block["_symmetry_equiv_pos_as_xyz"] = ["x, y, z"]
        else:
            sf = SpacegroupAnalyzer(struct, symprec)

            def round_symm_trans(i):

                for t in TRANSLATIONS.values():
                    if abs(i - t) < symprec:
                        return t
                if abs(i - round(i)) < symprec:
                    return 0
                raise ValueError("Invalid translation!")

            symmops = []
            for op in sf.get_symmetry_operations():
                v = op.translation_vector
                v = [round_symm_trans(i) for i in v]
                symmops.append(
                    SymmOp.from_rotation_and_translation(
                        op.rotation_matrix, v))

            ops = [op.as_xyz_string() for op in symmops]
            block["_symmetry_equiv_pos_site_id"] = \
                ["%d" % i for i in range(1, len(ops) + 1)]
            block["_symmetry_equiv_pos_as_xyz"] = ops

        loops.append(
            ["_symmetry_equiv_pos_site_id", "_symmetry_equiv_pos_as_xyz"])

        contains_oxidation = True
        try:
            symbol_to_oxinum = OrderedDict([(el.__str__(), float(el.oxi_state))
                                            for el in sorted(comp.elements)])
        except AttributeError:
            symbol_to_oxinum = OrderedDict([(el.symbol, 0)
                                            for el in sorted(comp.elements)])
            contains_oxidation = False
        if contains_oxidation:
            block["_atom_type_symbol"] = symbol_to_oxinum.keys()
            block["_atom_type_oxidation_number"] = symbol_to_oxinum.values()
            loops.append(["_atom_type_symbol", "_atom_type_oxidation_number"])

        atom_site_type_symbol = []
        atom_site_symmetry_multiplicity = []
        atom_site_fract_x = []
        atom_site_fract_y = []
        atom_site_fract_z = []
        atom_site_label = []
        atom_site_occupancy = []
        count = 1
        if symprec is None:
            for site in struct:
                for sp, occu in site.species_and_occu.items():
                    atom_site_type_symbol.append(sp.__str__())
                    atom_site_symmetry_multiplicity.append("1")
                    atom_site_fract_x.append("{0:f}".format(site.a))
                    atom_site_fract_y.append("{0:f}".format(site.b))
                    atom_site_fract_z.append("{0:f}".format(site.c))
                    atom_site_label.append("{}{}".format(sp.symbol, count))
                    atom_site_occupancy.append(occu.__str__())
                    count += 1
        else:
            # The following just presents a deterministic ordering.
            unique_sites = [
                (sorted(sites,
                        key=lambda s: tuple([abs(x)
                                             for x in s.frac_coords]))[0],
                 len(sites))
                for sites in sf.get_symmetrized_structure().equivalent_sites
            ]
            for site, mult in sorted(unique_sites,
                                     key=lambda t:
                                     (t[0].species_and_occu.average_electroneg,
                                      -t[1], t[0].a, t[0].b, t[0].c)):
                for sp, occu in site.species_and_occu.items():
                    atom_site_type_symbol.append(sp.__str__())
                    atom_site_symmetry_multiplicity.append("%d" % mult)
                    atom_site_fract_x.append("{0:f}".format(site.a))
                    atom_site_fract_y.append("{0:f}".format(site.b))
                    atom_site_fract_z.append("{0:f}".format(site.c))
                    atom_site_label.append("{}{}".format(sp.symbol, count))
                    atom_site_occupancy.append(occu.__str__())
                    count += 1

        block["_atom_site_type_symbol"] = atom_site_type_symbol
        block["_atom_site_label"] = atom_site_label
        block["_atom_site_symmetry_multiplicity"] = \
            atom_site_symmetry_multiplicity
        block["_atom_site_fract_x"] = atom_site_fract_x
        block["_atom_site_fract_y"] = atom_site_fract_y
        block["_atom_site_fract_z"] = atom_site_fract_z
        block["_atom_site_occupancy"] = atom_site_occupancy
        loops.append([
            "_atom_site_type_symbol", "_atom_site_label",
            "_atom_site_symmetry_multiplicity", "_atom_site_fract_x",
            "_atom_site_fract_y", "_atom_site_fract_z", "_atom_site_occupancy"
        ])
        d = OrderedDict()
        d[comp.reduced_formula] = CifBlock(block, loops, comp.reduced_formula)
        self._cf = CifFile(d)
Example #48
0
    def write_input(self, atoms, properties=None, system_changes=None):
        FileIOCalculator.write_input(self, atoms, properties, system_changes)
        p = self.parameters

        # Create a pymatgen struct for writing symmetrical structures
        # Wrting only assymetric sites for sanity and suppressing GULP warnings
        if p.symmetry:
            from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
            from pymatgen.io.ase import AseAtomsAdaptor
            struct = AseAtomsAdaptor.get_structure(self.atoms)
            struct_sg = SpacegroupAnalyzer(struct)
            struct_symm = struct_sg.get_symmetrized_structure()
            struct_assym_sites = [i[0] for i in struct_symm.equivalent_sites]

        # Build string to hold .gin input file:
        s = p.keywords
        s += '\ntitle\nASE calculation\nend\n\n'

        if all(self.atoms.pbc):
            if p.symmetry:
                coords = np.array([i.frac_coords for i in struct_assym_sites])
            else:
                coords = self.atoms.get_scaled_positions()

            cell_params = self.atoms.get_cell_lengths_and_angles()

            s += 'cell\n{0} {1} {2} {3} {4} {5}\n'.format(
                *(np.around(cell_params, 5)))
            s += 'frac\n'

        if self.conditions is not None:
            c = self.conditions
            labels = c.get_atoms_labels()
            self.atom_types = c.get_atom_types()
        else:
            if p.symmetry:
                labels = [i.species_string for i in struct_assym_sites]
            else:
                labels = self.atoms.get_chemical_symbols()

        for xyz, symbol in zip(coords, labels):

            s += ' {0:2} core'.format(symbol)

            def print_xyz(xyz) -> str:
                # so we need to convert coords to fraction notation for precision
                from fractions import Fraction

                xyz_string = ''
                for i in xyz:
                    # if it's 0 it can stay as 0 no worries
                    if i == 0:
                        xyz_string += '  {:8}'.format(np.around(i, 5))
                    else:
                        # 50 seems a good number. would be adjustable
                        new_frac = Fraction(i).limit_denominator(50)
                        # ensuring a very small difference in actual value
                        # note default symprec=1e-05
                        if abs(float(new_frac) - i) / i < 1e-6:
                            xyz_string += '       {}'.format(new_frac)
                        else:
                            xyz_string += '  {:8}'.format(np.around(i, 5))
                return xyz_string

            s += print_xyz(xyz) + '\n'

            if symbol in p.shel:
                s += ' {0:2} shel'.format(symbol)
                s += print_xyz(xyz) + '\n'

        if p.symmetry:
            s += '\nspace\n'
            s += '%s\n' % p.symmetry
        s += '\nlibrary {0}\n'.format(p.library)
        if p.options:
            for t in p.options:
                s += '%s\n' % t
        with open(self.prefix + '.gin', 'w') as f:
            f.write(s)
def get_dos_plot(vasprun_file: str,
                 cbm_vbm: list = None,
                 pdos_type: str = "element",
                 specific: list = None,
                 orbital: list = True,
                 xlim: list = None,
                 ymaxs: list = None,
                 zero_at_efermi: bool = True,
                 legend: bool = True,
                 crop_first_value: bool = True,
                 show_spg: bool = True,
                 symprec: float = SYMMETRY_TOLERANCE,
                 angle_tolerance: float = ANGLE_TOL):
    """

    Args:
        vasprun_file (str):
            vasprun.xml-type file name
        cbm_vbm (list):
            List of [cbm, vbm]
        pdos_type (str): Plot type of PDOS.
            "element": PDOS grouped by element type
            "site": PDOS grouped by equivalent sites
            "none": PDOS are not grouped.
        specific (list): Show specific PDOS. If list elements are integers,
            PDOS at particular sites are shown. If elements are shown,
            PDOS of particular elements are shown.
            ["1", "2"] --> At site 1 and 2 compatible with pdos_type = "none"
            ["Mg", "O"] --> Summed at Mg and O sites coompatible with
                            pdos_type = "element"
        orbital (bool):
            Whether to show orbital decomposed PDOS.
        xlim (list):
            Specifies the x-axis limits. Set to None for automatic determination.
        ymaxs (list):
            Specifies the maxima of absolute y-axis limits.
        zero_at_efermi (bool):
            Whether to show the plot in the absolute scale.
        legend (bool):
            Whether to show the figure legend.
        crop_first_value (bool):
            Whether to crop the fist DOS.
        show_spg (bool):
            Whether to show space group number in the title.
        symprec (float):
            Symprec for determining the equivalent sites.
    """

    v = Vasprun(vasprun_file, ionic_step_skip=True, parse_eigen=False)
    if v.converged_electronic is False:
        logger.warning("SCF is not attained in the vasp calculation.")

    complete_dos = v.complete_dos

    # check cbm
    if cbm_vbm is None:
        if complete_dos.get_gap() > 0.1:
            cbm_vbm = complete_dos.get_cbm_vbm()

    structure = v.final_structure

    dos = OrderedDict()
    # The CompleteDos behaves as DOS for total dos.
    dos["Total"] = complete_dos

    if specific and specific[0].isdigit():
        if pdos_type is not "none":
            logger.warning(
                "pdos_type is changed from {} to none".format(pdos_type))
        pdos_type = "none"
    elif specific and specific[0].isalpha():
        if pdos_type is not "none":
            logger.warning(
                "pdos_type is changed from {} to element".format(pdos_type))
        pdos_type = "element"

    sga = None
    grouped_indices = defaultdict(list)
    if pdos_type == "element":
        for indices, s in enumerate(structure):
            grouped_indices[str(s.specie)].append(indices)
    elif pdos_type == "site":
        # equivalent_sites: Equivalent site indices from SpacegroupAnalyzer.
        sga = SpacegroupAnalyzer(structure=structure,
                                 symprec=symprec,
                                 angle_tolerance=angle_tolerance)
        symmetrized_structure = sga.get_symmetrized_structure()
        # equiv_indices = [[0], [1], [2, 3], [4, 5]]
        equiv_index_lists = symmetrized_structure.equivalent_indices

        for l in equiv_index_lists:
            name = str(structure[l[0]].specie) + " " \
                   + sga.get_symmetry_dataset()["wyckoffs"][l[0]]
            grouped_indices[name] = l

    elif pdos_type == "none":
        for indices, s in enumerate(structure):
            grouped_indices[str(s.specie) + " site:" +
                            str(indices)].append(indices)
    else:
        raise KeyError("The given pdos_type is not supported.")

    # TODO: Add specific handling
    # if specific:
    #     tmp = defaultdict(list)
    #     for key, value in grouped_indices.items():
    #         if pdos_type == "element" and key in specific:
    #             tmp[key] = value
    #         else:
    #             # type(index) is str
    #             index = ''.join(c for c in key if c.isdigit())
    #             if index in specific:
    #                 tmp[key] = value
    #     grouped_indices = tmp

    # efermi is set to VBM if exists.
    efermi = cbm_vbm[1] if cbm_vbm else complete_dos.efermi
    complete_dos.efermi = efermi
    energies = complete_dos.energies

    for key, value in grouped_indices.items():
        for indices in value:
            site = structure[indices]
            if orbital:
                for orb, pdos in complete_dos.get_site_spd_dos(site).items():
                    # " " is used for grouping the plots.
                    if pdos_type == "none":
                        name = key + " " + str(orb)
                    else:
                        name = \
                            key + " #" + str(len(value)) + " " + str(orb)
                    density = divide_densities(pdos.densities, len(value))
                    if name in dos:
                        density = add_densities(dos[name].densities, density)
                        dos[name] = Dos(efermi, energies, density)
                    else:
                        dos[name] = Dos(efermi, energies, density)
            else:
                name = key + "(" + str(len(key)) + ")"
                pdos = complete_dos.get_site_dos(site)
                if name in dos:
                    dos[name] = add_densities(dos[name], pdos)
                else:
                    dos[name] = pdos

    # use complete_dos.efermi for total dos.
    plotter = ViseDosPlotter(zero_at_efermi=zero_at_efermi)
    plotter.add_dos_dict(dos)

    if xlim is None:
        xlim = [-10, 10]

    if ymaxs:
        ylims = [[-y, y] for y in ymaxs] \
            if v.incar.get("ISPIN", 1) == 2 else [[0, y] for y in ymaxs]
    else:
        energies = complete_dos.energies - efermi
        tdos_max = max_density(complete_dos.densities, energies, xlim,
                               crop_first_value)
        tdos_max *= 1.1
        ylims = [[-tdos_max, tdos_max]] if v.incar.get("ISPIN", 1) == 2 \
            else [[0, tdos_max]]

        pdos_max = 0.0
        for k, d in dos.items():
            if k == "Total":
                continue
            pdos_max = \
                max(max_density(d.densities, energies, xlim), pdos_max)

        pdos_max *= 1.1
        ylims.append([-pdos_max, pdos_max] if v.incar.get("ISPIN", 1) ==
                     2 else [0, pdos_max])

        print("y-range", ylims)

    if show_spg:
        if sga is None:
            sga = SpacegroupAnalyzer(structure, symprec=symprec)
        sg_num_str = str(sga.get_space_group_number())
        sg = f" {sga.get_space_group_symbol()} ({sg_num_str})"
        print(f"Space group number: {sg}")
        title = f"{structure.composition} SG: {sg}"
    else:
        title = str(structure.composition)

    return plotter.get_plot(xlim=xlim,
                            ylims=ylims,
                            cbm_vbm=cbm_vbm,
                            legend=legend,
                            crop_first_value=crop_first_value,
                            title=title)
Example #50
0
    def _generate_transformations(self, structure):
        """
        The central problem with trying to enumerate magnetic orderings is
        that we have to enumerate orderings that might plausibly be magnetic
        ground states, while not enumerating orderings that are physically
        implausible. The problem is that it is not always obvious by e.g.
        symmetry arguments alone which orderings to prefer. Here, we use a
        variety of strategies (heuristics) to enumerate plausible orderings,
        and later discard any duplicates that might be found by multiple
        strategies. This approach is not ideal, but has been found to be
        relatively robust over a wide range of magnetic structures.

        Args:
            structure: A sanitized input structure (_sanitize_input_structure)

        Returns: A dict of a transformation class instance (values) and name of
        enumeration strategy (keys)
        """

        formula = structure.composition.reduced_formula
        transformations = {}

        # analyzer is used to obtain information on sanitized input
        analyzer = CollinearMagneticStructureAnalyzer(
            structure,
            default_magmoms=self.default_magmoms,
            overwrite_magmom_mode="replace_all",
        )

        if not analyzer.is_magnetic:
            raise ValueError(
                "Not detected as magnetic, add a new default magmom for the "
                "element you believe may be magnetic?"
            )

        # now we can begin to generate our magnetic orderings
        self.logger.info("Generating magnetic orderings for {}".format(formula))

        mag_species_spin = analyzer.magnetic_species_and_magmoms
        types_mag_species = sorted(
            analyzer.types_of_magnetic_specie,
            key=lambda sp: analyzer.default_magmoms.get(str(sp), 0),
            reverse=True,
        )
        num_mag_sites = analyzer.number_of_magnetic_sites
        num_unique_sites = analyzer.number_of_unique_magnetic_sites()

        # enumerations become too slow as number of unique sites (and thus
        # permutations) increase, 8 is a soft limit, this can be increased
        # but do so with care
        if num_unique_sites > self.max_unique_sites:
            raise ValueError("Too many magnetic sites to sensibly perform enumeration.")

        # maximum cell size to consider: as a rule of thumb, if the primitive cell
        # contains a large number of magnetic sites, perhaps we only need to enumerate
        # within one cell, whereas on the other extreme if the primitive cell only
        # contains a single magnetic site, we have to create larger supercells
        if "max_cell_size" not in self.transformation_kwargs:
            # TODO: change to 8 / num_mag_sites ?
            self.transformation_kwargs["max_cell_size"] = max(1, int(4 / num_mag_sites))
        self.logger.info(
            "Max cell size set to {}".format(
                self.transformation_kwargs["max_cell_size"]
            )
        )

        # when enumerating ferrimagnetic structures, it's useful to detect
        # symmetrically distinct magnetic sites, since different
        # local environments can result in different magnetic order
        # (e.g. inverse spinels)
        # initially, this was done by co-ordination number, but is
        # now done by a full symmetry analysis
        sga = SpacegroupAnalyzer(structure)
        structure_sym = sga.get_symmetrized_structure()
        wyckoff = ["n/a"] * len(structure)
        for indices, symbol in zip(
            structure_sym.equivalent_indices, structure_sym.wyckoff_symbols
        ):
            for index in indices:
                wyckoff[index] = symbol
        is_magnetic_sites = [
            True if site.specie in types_mag_species else False for site in structure
        ]
        # we're not interested in sites that we don't think are magnetic,
        # set these symbols to None to filter them out later
        wyckoff = [
            symbol if is_magnetic_site else "n/a"
            for symbol, is_magnetic_site in zip(wyckoff, is_magnetic_sites)
        ]
        structure.add_site_property("wyckoff", wyckoff)
        wyckoff_symbols = set(wyckoff) - {"n/a"}

        # if user doesn't specifically request ferrimagnetic_Cr2NiO4 orderings,
        # we apply a heuristic as to whether to attempt them or not
        if self.automatic:
            if (
                "ferrimagnetic_by_motif" not in self.strategies
                and len(wyckoff_symbols) > 1
                and len(types_mag_species) == 1
            ):
                self.strategies += ("ferrimagnetic_by_motif",)

            if (
                "antiferromagnetic_by_motif" not in self.strategies
                and len(wyckoff_symbols) > 1
                and len(types_mag_species) == 1
            ):
                self.strategies += ("antiferromagnetic_by_motif",)

            if (
                "ferrimagnetic_by_species" not in self.strategies
                and len(types_mag_species) > 1
            ):
                self.strategies += ("ferrimagnetic_by_species",)

        # we start with a ferromagnetic ordering
        if "ferromagnetic" in self.strategies:

            # TODO: remove 0 spins !

            fm_structure = analyzer.get_ferromagnetic_structure()
            # store magmom as spin property, to be consistent with output from
            # other transformations
            fm_structure.add_spin_by_site(fm_structure.site_properties["magmom"])
            fm_structure.remove_site_property("magmom")

            # we now have our first magnetic ordering...
            self.ordered_structures.append(fm_structure)
            self.ordered_structure_origins.append("fm")

        # we store constraint(s) for each strategy first,
        # and then use each to perform a transformation later
        all_constraints = {}

        # ...to which we can add simple AFM cases first...
        if "antiferromagnetic" in self.strategies:

            constraint = MagOrderParameterConstraint(
                0.5,
                # TODO: update MagOrderParameterConstraint in pymatgen to take types_mag_species directly
                species_constraints=list(map(str, types_mag_species)),
            )
            all_constraints["afm"] = [constraint]

            # allows for non-magnetic sublattices
            if len(types_mag_species) > 1:
                for sp in types_mag_species:

                    constraints = [
                        MagOrderParameterConstraint(0.5, species_constraints=str(sp))
                    ]

                    all_constraints["afm_by_{}".format(sp)] = constraints

        # ...and then we also try ferrimagnetic orderings by motif if a
        # single magnetic species is present...
        if "ferrimagnetic_by_motif" in self.strategies and len(wyckoff_symbols) > 1:

            # these orderings are AFM on one local environment, and FM on the rest
            for symbol in wyckoff_symbols:

                constraints = [
                    MagOrderParameterConstraint(
                        0.5, site_constraint_name="wyckoff", site_constraints=symbol
                    ),
                    MagOrderParameterConstraint(
                        1.0,
                        site_constraint_name="wyckoff",
                        site_constraints=list(wyckoff_symbols - {symbol}),
                    ),
                ]

                all_constraints["ferri_by_motif_{}".format(symbol)] = constraints

        # and also try ferrimagnetic when there are multiple magnetic species
        if "ferrimagnetic_by_species" in self.strategies:

            sp_list = [str(site.specie) for site in structure]
            num_sp = {sp: sp_list.count(str(sp)) for sp in types_mag_species}
            total_mag_sites = sum(num_sp.values())

            for sp in types_mag_species:

                # attempt via a global order parameter
                all_constraints["ferri_by_{}".format(sp)] = num_sp[sp] / total_mag_sites

                # attempt via afm on sp, fm on remaining species

                constraints = [
                    MagOrderParameterConstraint(0.5, species_constraints=str(sp)),
                    MagOrderParameterConstraint(
                        1.0,
                        species_constraints=list(
                            map(str, set(types_mag_species) - {sp})
                        ),
                    ),
                ]

                all_constraints["ferri_by_{}_afm".format(sp)] = constraints

        # ...and finally, we can try orderings that are AFM on one local
        # environment, and non-magnetic on the rest -- this is less common
        # but unless explicitly attempted, these states are unlikely to be found
        if "antiferromagnetic_by_motif" in self.strategies:

            for symbol in wyckoff_symbols:

                constraints = [
                    MagOrderParameterConstraint(
                        0.5, site_constraint_name="wyckoff", site_constraints=symbol
                    )
                ]

                all_constraints["afm_by_motif_{}".format(symbol)] = constraints

        # and now construct all our transformations for each strategy
        transformations = {}
        for name, constraints in all_constraints.items():

            trans = MagOrderingTransformation(
                mag_species_spin,
                order_parameter=constraints,
                **self.transformation_kwargs
            )

            transformations[name] = trans

        return transformations
Example #51
0
    def _gen_input_file(self):
        """
        Generate the necessary struct_enum.in file for enumlib. See enumlib
        documentation for details.
        """
        coord_format = "{:.6f} {:.6f} {:.6f}"
        # Using symmetry finder, get the symmetrically distinct sites.
        fitter = SpacegroupAnalyzer(self.structure, self.symm_prec)
        symmetrized_structure = fitter.get_symmetrized_structure()
        logger.debug("Spacegroup {} ({}) with {} distinct sites".format(
            fitter.get_space_group_symbol(), fitter.get_space_group_number(),
            len(symmetrized_structure.equivalent_sites)))
        """
        Enumlib doesn"t work when the number of species get too large. To
        simplify matters, we generate the input file only with disordered sites
        and exclude the ordered sites from the enumeration. The fact that
        different disordered sites with the exact same species may belong to
        different equivalent sites is dealt with by having determined the
        spacegroup earlier and labelling the species differently.
        """

        # index_species and index_amounts store mappings between the indices
        # used in the enum input file, and the actual species and amounts.
        index_species = []
        index_amounts = []

        # Stores the ordered sites, which are not enumerated.
        ordered_sites = []
        disordered_sites = []
        coord_str = []
        for sites in symmetrized_structure.equivalent_sites:
            if sites[0].is_ordered:
                ordered_sites.append(sites)
            else:
                sp_label = []
                species = {k: v for k, v in sites[0].species.items()}
                if sum(species.values()) < 1 - EnumlibAdaptor.amount_tol:
                    # Let us first make add a dummy element for every single
                    # site whose total occupancies don't sum to 1.
                    species[DummySpecie("X")] = 1 - sum(species.values())
                for sp in species.keys():
                    if sp not in index_species:
                        index_species.append(sp)
                        sp_label.append(len(index_species) - 1)
                        index_amounts.append(species[sp] * len(sites))
                    else:
                        ind = index_species.index(sp)
                        sp_label.append(ind)
                        index_amounts[ind] += species[sp] * len(sites)
                sp_label = "/".join(["{}".format(i) for i in sorted(sp_label)])
                for site in sites:
                    coord_str.append("{} {}".format(
                        coord_format.format(*site.coords), sp_label))
                disordered_sites.append(sites)

        def get_sg_info(ss):
            finder = SpacegroupAnalyzer(Structure.from_sites(ss),
                                        self.symm_prec)
            return finder.get_space_group_number()

        target_sgnum = get_sg_info(symmetrized_structure.sites)
        curr_sites = list(itertools.chain.from_iterable(disordered_sites))
        sgnum = get_sg_info(curr_sites)
        ordered_sites = sorted(ordered_sites, key=lambda sites: len(sites))
        logger.debug("Disordered sites has sg # %d" % (sgnum))
        self.ordered_sites = []

        # progressively add ordered sites to our disordered sites
        # until we match the symmetry of our input structure
        if self.check_ordered_symmetry:
            while sgnum != target_sgnum and len(ordered_sites) > 0:
                sites = ordered_sites.pop(0)
                temp_sites = list(curr_sites) + sites
                new_sgnum = get_sg_info(temp_sites)
                if sgnum != new_sgnum:
                    logger.debug("Adding %s in enum. New sg # %d" %
                                 (sites[0].specie, new_sgnum))
                    index_species.append(sites[0].specie)
                    index_amounts.append(len(sites))
                    sp_label = len(index_species) - 1
                    for site in sites:
                        coord_str.append("{} {}".format(
                            coord_format.format(*site.coords), sp_label))
                    disordered_sites.append(sites)
                    curr_sites = temp_sites
                    sgnum = new_sgnum
                else:
                    self.ordered_sites.extend(sites)

        for sites in ordered_sites:
            self.ordered_sites.extend(sites)

        self.index_species = index_species

        lattice = self.structure.lattice

        output = [self.structure.formula, "bulk"]
        for vec in lattice.matrix:
            output.append(coord_format.format(*vec))
        output.append("%d" % len(index_species))
        output.append("%d" % len(coord_str))
        output.extend(coord_str)

        output.append("{} {}".format(self.min_cell_size, self.max_cell_size))
        output.append(str(self.enum_precision_parameter))
        output.append("full")

        ndisordered = sum([len(s) for s in disordered_sites])
        base = int(ndisordered * lcm(*[
            f.limit_denominator(ndisordered * self.max_cell_size).denominator
            for f in map(fractions.Fraction, index_amounts)
        ]))

        # This multiplicative factor of 10 is to prevent having too small bases
        # which can lead to rounding issues in the next step.
        # An old bug was that a base was set to 8, with a conc of 0.4:0.6. That
        # resulted in a range that overlaps and a conc of 0.5 satisfying this
        # enumeration. See Cu7Te5.cif test file.
        base *= 10

        # base = ndisordered #10 ** int(math.ceil(math.log10(ndisordered)))
        # To get a reasonable number of structures, we fix concentrations to the
        # range expected in the original structure.
        total_amounts = sum(index_amounts)
        for amt in index_amounts:
            conc = amt / total_amounts

            if abs(conc * base - round(conc * base)) < 1e-5:
                output.append("{} {} {}".format(int(round(conc * base)),
                                                int(round(conc * base)), base))
            else:
                min_conc = int(math.floor(conc * base))
                output.append("{} {} {}".format(min_conc - 1, min_conc + 1,
                                                base))
        output.append("")
        logger.debug("Generated input file:\n{}".format("\n".join(output)))
        with open("struct_enum.in", "w") as f:
            f.write("\n".join(output))
    def condense_structure(self, structure: Structure) -> Dict[str, Any]:
        """Condenses the structure into an intermediate dict representation.

        Args:
            structure: A pymatgen structure object.

        Returns:
            The condensed structure information. The data is formatted as a
            :obj:`dict` with a fixed set of keys. An up-to-date example of the,
            the condensed representation of MoS2 given in the documentation.
            See: ``robocrystallographer/docs_rst/source/format.rst`` or
            https://hackingmaterials.lbl.gov/robocrystallographer/format.html
        """
        # sort so we can give proper symmetry labels
        structure = structure.get_sorted_structure()

        # wrap all site coords into unit cell
        structure.translate_sites(range(structure.num_sites), [1, 1, 1])

        sga = SpacegroupAnalyzer(structure, symprec=self.symprec)
        if self.use_conventional_cell:
            structure = sga.get_conventional_standard_structure()
        else:
            structure = sga.get_symmetrized_structure()

        bonded_structure = self.near_neighbors.get_bonded_structure(structure)

        components = get_structure_components(
            bonded_structure,
            inc_orientation=True,
            inc_site_ids=True,
            inc_molecule_graph=True,
        )

        dimensionality = max(c["dimensionality"] for c in components)
        mineral = self._condense_mineral(structure, components)
        formula = self._condense_formula(structure, components)

        structure_data = {
            "formula": formula,
            "spg_symbol": sga.get_space_group_symbol(),
            "crystal_system": sga.get_crystal_system(),
            "mineral": mineral,
            "dimensionality": dimensionality,
        }

        site_analyzer = SiteAnalyzer(
            bonded_structure,
            symprec=self.symprec,
            use_symmetry_equivalent_sites=self.use_symmetry_equivalent_sites,
        )
        structure_data["sites"] = site_analyzer.get_all_site_summaries()
        structure_data[
            "distances"] = site_analyzer.get_all_bond_distance_summaries()
        structure_data[
            "angles"] = site_analyzer.get_all_connectivity_angle_summaries()
        structure_data[
            "nnn_distances"] = site_analyzer.get_all_nnn_distance_summaries()

        component_summary, component_makeup = self._condense_components(
            components, sga, site_analyzer)
        structure_data["components"] = component_summary
        structure_data["component_makeup"] = component_makeup

        if components_are_vdw_heterostructure(components):
            hs_info = get_vdw_heterostructure_information(
                components,
                use_iupac_formula=self.use_iupac_formula,
                use_common_formulas=self.use_common_formulas,
            )
        else:
            hs_info = None

        structure_data["vdw_heterostructure_info"] = hs_info

        return structure_data
Example #53
0
class SpacegroupAnalyzerTest(PymatgenTest):
    def setUp(self):
        p = Poscar.from_file(os.path.join(test_dir, 'POSCAR'))
        self.structure = p.structure
        self.sg = SpacegroupAnalyzer(self.structure, 0.001)
        self.disordered_structure = self.get_structure('Li10GeP2S12')
        self.disordered_sg = SpacegroupAnalyzer(self.disordered_structure,
                                                0.001)
        s = p.structure.copy()
        site = s[0]
        del s[0]
        s.append(site.species_and_occu, site.frac_coords)
        self.sg3 = SpacegroupAnalyzer(s, 0.001)
        graphite = self.get_structure('Graphite')
        graphite.add_site_property("magmom", [0.1] * len(graphite))
        self.sg4 = SpacegroupAnalyzer(graphite, 0.001)
        self.structure4 = graphite

    def test_primitive(self):
        s = Structure.from_spacegroup("Fm-3m",
                                      np.eye(3) * 3, ["Cu"], [[0, 0, 0]])
        a = SpacegroupAnalyzer(s)
        self.assertEqual(len(s), 4)
        self.assertEqual(len(a.find_primitive()), 1)

    def test_is_laue(self):
        s = Structure.from_spacegroup("Fm-3m",
                                      np.eye(3) * 3, ["Cu"], [[0, 0, 0]])
        a = SpacegroupAnalyzer(s)
        self.assertTrue(a.is_laue())

    def test_magnetic(self):
        lfp = PymatgenTest.get_structure("LiFePO4")
        sg = SpacegroupAnalyzer(lfp, 0.1)
        self.assertEqual(sg.get_space_group_symbol(), "Pnma")
        magmoms = [0] * len(lfp)
        magmoms[4] = 1
        magmoms[5] = -1
        magmoms[6] = 1
        magmoms[7] = -1
        lfp.add_site_property("magmom", magmoms)
        sg = SpacegroupAnalyzer(lfp, 0.1)
        self.assertEqual(sg.get_space_group_symbol(), "Pnma")

    def test_get_space_symbol(self):
        self.assertEqual(self.sg.get_space_group_symbol(), "Pnma")
        self.assertEqual(self.disordered_sg.get_space_group_symbol(),
                         "P4_2/nmc")
        self.assertEqual(self.sg3.get_space_group_symbol(), "Pnma")
        self.assertEqual(self.sg4.get_space_group_symbol(), "P6_3/mmc")

    def test_get_space_number(self):
        self.assertEqual(self.sg.get_space_group_number(), 62)
        self.assertEqual(self.disordered_sg.get_space_group_number(), 137)
        self.assertEqual(self.sg4.get_space_group_number(), 194)

    def test_get_hall(self):
        self.assertEqual(self.sg.get_hall(), '-P 2ac 2n')
        self.assertEqual(self.disordered_sg.get_hall(), 'P 4n 2n -1n')

    def test_get_pointgroup(self):
        self.assertEqual(self.sg.get_point_group_symbol(), 'mmm')
        self.assertEqual(self.disordered_sg.get_point_group_symbol(), '4/mmm')

    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 test_get_symmetry_dataset(self):
        ds = self.sg.get_symmetry_dataset()
        self.assertEqual(ds['international'], 'Pnma')

    def test_get_crystal_system(self):
        crystal_system = self.sg.get_crystal_system()
        self.assertEqual('orthorhombic', crystal_system)
        self.assertEqual('tetragonal', self.disordered_sg.get_crystal_system())

    def test_get_symmetry_operations(self):
        for sg, structure in [(self.sg, self.structure),
                              (self.sg4, self.structure4)]:

            pgops = sg.get_point_group_operations()
            fracsymmops = sg.get_symmetry_operations()
            symmops = sg.get_symmetry_operations(True)
            latt = structure.lattice
            for fop, op, pgop in zip(fracsymmops, symmops, pgops):
                # translation vector values should all be 0 or 0.5
                t = fop.translation_vector * 2
                self.assertArrayAlmostEqual(t - np.round(t), 0)

                self.assertArrayAlmostEqual(fop.rotation_matrix,
                                            pgop.rotation_matrix)
                for site in structure:
                    newfrac = fop.operate(site.frac_coords)
                    newcart = op.operate(site.coords)
                    self.assertTrue(
                        np.allclose(latt.get_fractional_coords(newcart),
                                    newfrac))
                    found = False
                    newsite = PeriodicSite(site.species_and_occu,
                                           newcart,
                                           latt,
                                           coords_are_cartesian=True)
                    for testsite in structure:
                        if newsite.is_periodic_image(testsite, 1e-3):
                            found = True
                            break
                    self.assertTrue(found)

                # Make sure this works for any position, not just the atomic
                # ones.
                random_fcoord = np.random.uniform(size=(3))
                random_ccoord = latt.get_cartesian_coords(random_fcoord)
                newfrac = fop.operate(random_fcoord)
                newcart = op.operate(random_ccoord)
                self.assertTrue(
                    np.allclose(latt.get_fractional_coords(newcart), newfrac))

    def test_get_refined_structure(self):
        for a in self.sg.get_refined_structure().lattice.angles:
            self.assertEqual(a, 90)
        refined = self.disordered_sg.get_refined_structure()
        for a in refined.lattice.angles:
            self.assertEqual(a, 90)
        self.assertEqual(refined.lattice.a, refined.lattice.b)
        s = self.get_structure('Li2O')
        sg = SpacegroupAnalyzer(s, 0.01)
        self.assertEqual(sg.get_refined_structure().num_sites, 4 * s.num_sites)

    def test_get_symmetrized_structure(self):
        symm_struct = self.sg.get_symmetrized_structure()
        for a in symm_struct.lattice.angles:
            self.assertEqual(a, 90)
        self.assertEqual(len(symm_struct.equivalent_sites), 5)

        symm_struct = self.disordered_sg.get_symmetrized_structure()
        self.assertEqual(len(symm_struct.equivalent_sites), 8)
        self.assertEqual([len(i) for i in symm_struct.equivalent_sites],
                         [16, 4, 8, 4, 2, 8, 8, 8])
        s1 = symm_struct.equivalent_sites[1][1]
        s2 = symm_struct[symm_struct.equivalent_indices[1][1]]
        self.assertEqual(s1, s2)
        self.assertEqual(self.sg4.get_symmetrized_structure()[0].magmom, 0.1)
        self.assertEqual(symm_struct.wyckoff_symbols[0], '16h')
        # self.assertEqual(symm_struct[0].wyckoff, "16h")

    def test_find_primitive(self):
        """
        F m -3 m Li2O testing of converting to primitive cell
        """
        parser = CifParser(os.path.join(test_dir, 'Li2O.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure)
        primitive_structure = s.find_primitive()
        self.assertEqual(primitive_structure.formula, "Li2 O1")
        # This isn't what is expected. All the angles should be 60
        self.assertAlmostEqual(primitive_structure.lattice.alpha, 60)
        self.assertAlmostEqual(primitive_structure.lattice.beta, 60)
        self.assertAlmostEqual(primitive_structure.lattice.gamma, 60)
        self.assertAlmostEqual(primitive_structure.lattice.volume,
                               structure.lattice.volume / 4.0)

    def test_get_ir_reciprocal_mesh(self):
        grid = self.sg.get_ir_reciprocal_mesh()
        self.assertEqual(len(grid), 216)
        self.assertAlmostEqual(grid[1][0][0], 0.1)
        self.assertAlmostEqual(grid[1][0][1], 0.0)
        self.assertAlmostEqual(grid[1][0][2], 0.0)
        self.assertAlmostEqual(grid[1][1], 2)

    def test_get_conventional_standard_structure(self):
        parser = CifParser(os.path.join(test_dir, 'bcc_1927.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 9.1980270633769461)
        self.assertAlmostEqual(conv.lattice.b, 9.1980270633769461)
        self.assertAlmostEqual(conv.lattice.c, 9.1980270633769461)

        parser = CifParser(os.path.join(test_dir, 'btet_1915.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 5.0615106678044235)
        self.assertAlmostEqual(conv.lattice.b, 5.0615106678044235)
        self.assertAlmostEqual(conv.lattice.c, 4.2327080177761687)

        parser = CifParser(os.path.join(test_dir, 'orci_1010.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 2.9542233922299999)
        self.assertAlmostEqual(conv.lattice.b, 4.6330325651443296)
        self.assertAlmostEqual(conv.lattice.c, 5.373703587040775)

        parser = CifParser(os.path.join(test_dir, 'orcc_1003.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 4.1430033493799998)
        self.assertAlmostEqual(conv.lattice.b, 31.437979757624728)
        self.assertAlmostEqual(conv.lattice.c, 3.99648651)

        parser = CifParser(os.path.join(test_dir, 'orac_632475.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 3.1790663399999999)
        self.assertAlmostEqual(conv.lattice.b, 9.9032878699999998)
        self.assertAlmostEqual(conv.lattice.c, 3.5372412099999999)

        parser = CifParser(os.path.join(test_dir, 'monoc_1028.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 117.53832420192903)
        self.assertAlmostEqual(conv.lattice.gamma, 90)
        self.assertAlmostEqual(conv.lattice.a, 14.033435583000625)
        self.assertAlmostEqual(conv.lattice.b, 3.96052850731)
        self.assertAlmostEqual(conv.lattice.c, 6.8743926325200002)

        parser = CifParser(os.path.join(test_dir, 'hex_1170.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        conv = s.get_conventional_standard_structure()
        self.assertAlmostEqual(conv.lattice.alpha, 90)
        self.assertAlmostEqual(conv.lattice.beta, 90)
        self.assertAlmostEqual(conv.lattice.gamma, 120)
        self.assertAlmostEqual(conv.lattice.a, 3.699919902005897)
        self.assertAlmostEqual(conv.lattice.b, 3.699919902005897)
        self.assertAlmostEqual(conv.lattice.c, 6.9779585500000003)

    def test_get_primitive_standard_structure(self):
        parser = CifParser(os.path.join(test_dir, 'bcc_1927.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 109.47122063400001)
        self.assertAlmostEqual(prim.lattice.beta, 109.47122063400001)
        self.assertAlmostEqual(prim.lattice.gamma, 109.47122063400001)
        self.assertAlmostEqual(prim.lattice.a, 7.9657251015812145)
        self.assertAlmostEqual(prim.lattice.b, 7.9657251015812145)
        self.assertAlmostEqual(prim.lattice.c, 7.9657251015812145)

        parser = CifParser(os.path.join(test_dir, 'btet_1915.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 105.015053349)
        self.assertAlmostEqual(prim.lattice.beta, 105.015053349)
        self.assertAlmostEqual(prim.lattice.gamma, 118.80658411899999)
        self.assertAlmostEqual(prim.lattice.a, 4.1579321075608791)
        self.assertAlmostEqual(prim.lattice.b, 4.1579321075608791)
        self.assertAlmostEqual(prim.lattice.c, 4.1579321075608791)

        parser = CifParser(os.path.join(test_dir, 'orci_1010.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 134.78923546600001)
        self.assertAlmostEqual(prim.lattice.beta, 105.856239333)
        self.assertAlmostEqual(prim.lattice.gamma, 91.276341676000001)
        self.assertAlmostEqual(prim.lattice.a, 3.8428217771014852)
        self.assertAlmostEqual(prim.lattice.b, 3.8428217771014852)
        self.assertAlmostEqual(prim.lattice.c, 3.8428217771014852)

        parser = CifParser(os.path.join(test_dir, 'orcc_1003.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 90)
        self.assertAlmostEqual(prim.lattice.beta, 90)
        self.assertAlmostEqual(prim.lattice.gamma, 164.985257335)
        self.assertAlmostEqual(prim.lattice.a, 15.854897098324196)
        self.assertAlmostEqual(prim.lattice.b, 15.854897098324196)
        self.assertAlmostEqual(prim.lattice.c, 3.99648651)

        parser = CifParser(os.path.join(test_dir, 'orac_632475.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 90)
        self.assertAlmostEqual(prim.lattice.beta, 90)
        self.assertAlmostEqual(prim.lattice.gamma, 144.40557588533386)
        self.assertAlmostEqual(prim.lattice.a, 5.2005185662155391)
        self.assertAlmostEqual(prim.lattice.b, 5.2005185662155391)
        self.assertAlmostEqual(prim.lattice.c, 3.5372412099999999)

        parser = CifParser(os.path.join(test_dir, 'monoc_1028.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 63.579155761999999)
        self.assertAlmostEqual(prim.lattice.beta, 116.42084423747779)
        self.assertAlmostEqual(prim.lattice.gamma, 148.47965136208569)
        self.assertAlmostEqual(prim.lattice.a, 7.2908007159612325)
        self.assertAlmostEqual(prim.lattice.b, 7.2908007159612325)
        self.assertAlmostEqual(prim.lattice.c, 6.8743926325200002)

        parser = CifParser(os.path.join(test_dir, 'hex_1170.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 90)
        self.assertAlmostEqual(prim.lattice.beta, 90)
        self.assertAlmostEqual(prim.lattice.gamma, 120)
        self.assertAlmostEqual(prim.lattice.a, 3.699919902005897)
        self.assertAlmostEqual(prim.lattice.b, 3.699919902005897)
        self.assertAlmostEqual(prim.lattice.c, 6.9779585500000003)

        parser = CifParser(os.path.join(test_dir, 'rhomb_3478_conv.cif'))
        structure = parser.get_structures(False)[0]
        s = SpacegroupAnalyzer(structure, symprec=1e-2)
        prim = s.get_primitive_standard_structure()
        self.assertAlmostEqual(prim.lattice.alpha, 28.049186140546812)
        self.assertAlmostEqual(prim.lattice.beta, 28.049186140546812)
        self.assertAlmostEqual(prim.lattice.gamma, 28.049186140546812)
        self.assertAlmostEqual(prim.lattice.a, 5.9352627428399982)
        self.assertAlmostEqual(prim.lattice.b, 5.9352627428399982)
        self.assertAlmostEqual(prim.lattice.c, 5.9352627428399982)
Example #54
0
def download_xanes_from_MP_old(mpr, mpid, absorption_specie, download_to=None):
    import numpy as np

    here = os.getcwd()

    if download_to is None:
        download_to = t4iss_defaults['t4iss_xanes_data']

    data = mpr.get_data(mpid, data_type="feff", prop="xas")

    # check if data from MP is available
    if data[0]['xas']:
        species_in_data = []
        for xas_doc in data[0]['xas']:
            data_abs_specie = \
                xas_doc['structure'].species[xas_doc['absorbing_atom']].name
            species_in_data.append(data_abs_specie)
        if absorption_specie in species_in_data:
            available = True
        else:
            available = False
    else:
        available = False

    if available:
        data_structure0 = data[0]['xas'][0]["structure"]
        sites = []
        for i in data_structure0:
            sites.append(i.species_string)
        if absorption_specie not in sites:
            print('ERROR:' + absorption_specie + 'is NOT found in this'
                  ' structure. Please check....')
        else:
            os.chdir(download_to)
            os.makedirs(mpid, exist_ok=True)
            os.chdir(mpid)
            spectra = []
            for xas_doc in data[0]['xas']:
                data_abs_specie = \
                    xas_doc['structure'].\
                    species[xas_doc['absorbing_atom']].name
                data_structure = xas_doc["structure"]
                data_absorption_atom = xas_doc['absorbing_atom']
                data_edge = xas_doc["edge"]

                # x and y are the energy and intensity in two lists
                x, y = xas_doc['spectrum']

                if data_abs_specie == absorption_specie:
                    finder = SpacegroupAnalyzer(data_structure)
                    structure = finder.get_symmetrized_structure()
                    [sites, indices] = structure.equivalent_sites, \
                        structure.equivalent_indices
                    """
                    If one needs to pull structural data about the sites and
                    equilvalent indices from a CONTCAR file, this is possible.

                    This code produces a structure (structure2) that is
                    slightly different from the above data_structure/structure
                    (missing the last two columns) but appears to produce the
                    same output as when using data_structure from the MP.

                    file_name = '/path/to/CONTCAR'
                    import pymatgen as mg
                    structure2 = mg.Structure.from_file(file_name)
                    finder = SpacegroupAnalyzer(structure2)
                    structure = finder.get_symmetrized_structure()
                    [sites, indices] = structure.equivalent_sites, \
                        structure.equivalent_indices
                    """

                    for m in indices:
                        if m[0] == data_absorption_atom:
                            multiplicity = len(m)
                            ind = m[0]

                    try:
                        f = 'feff_{:03d}_{}-{}'.format(ind + 1,
                                                       absorption_specie,
                                                       data_edge)
                    except:  # noqa
                        print('XANES data for' + mpid +
                              ' in Materials Project is corrupt.')
                        path = os.getcwd() + '/WARNING_CORRUPT'
                        f = open(path, 'w+')
                        f.write('data is corrupt')
                        f.close()
                        return []

                    os.makedirs(f, exist_ok=True)
                    os.chdir(f)
                    xanes = mXANES(
                        data=[x, y],
                        structure=[data_structure, data_absorption_atom],
                        xanesid=mpid,
                        source='from_MP',
                        edge=data_edge,
                        multiplicity=multiplicity)
                    pickle.dump(xanes, open('xanes.pkl', 'wb'))
                    out = np.column_stack((xanes.E0, xanes.I0))
                    np.savetxt('xanes.dat', out)
                    spectra.append(xanes)
                    os.chdir('..')

            data_structure.to(fmt='poscar', filename='CONTCAR')
            os.chdir(here)
            print('XANES data was downloaded to ' + download_to + '/' + mpid)
            return [data_structure, spectra]
    else:
        print('XANES data for' + mpid +
              ' is not available in Materials Project database...')
        path = t4iss_defaults['t4iss_xanes_data'] + '/' + str(mpid)
        try:
            os.makedirs(path)
        except:  # noqa
            # file exists already, that's ok
            pass  # mp-1071163
        f = open(path + '/WARNING_MISSING', 'w+')
        f.write('data is missing')
        f.close()
        return []
Example #55
0
def vac_antisite_def_struct_gen(mpid, mapi_key, cellmax, struct_file=None):
    if not mpid and not struct_file:
        print ("============\nERROR: Provide an mpid\n============")
        return

    # Get primitive structure from the Materials Project DB
    if not struct_file:
        if not mapi_key:
            with MPRester() as mp:
                struct = mp.get_structure_by_material_id(mpid)
        else:
            with MPRester(mapi_key) as mp:
                struct = mp.get_structure_by_material_id(mpid)
    else:
        struct = Structure.from_file(struct_file)

    sga = SpacegroupAnalyzer(struct)
    prim_struct = sga.find_primitive()
    #prim_struct_sites = len(prim_struct.sites)
    #conv_struct = sga.get_conventional_standard_structure()
    #conv_struct_sites = len(conv_struct.sites)
    #conv_prim_ratio = int(conv_struct_sites / prim_struct_sites)

    # Default VASP settings
    def_vasp_incar_param = {'ISIF':2, 'EDIFF':1e-6, 'EDIFFG':0.001,}
    kpoint_den = 15000

    # Create bulk structure and associated VASP files
    sc_scale = get_sc_scale(inp_struct=prim_struct, final_site_no=cellmax)
    blk_sc = prim_struct.copy()
    blk_sc.make_supercell(scaling_matrix=sc_scale)
    site_no = blk_sc.num_sites

    # Rescale if needed
    while site_no > cellmax:
        max_sc_dim = max(sc_scale)
        i = sc_scale.index(max_sc_dim)
        sc_scale[i] -= 1
        blk_sc = prim_struct.copy()
        blk_sc.make_supercell(scaling_matrix=sc_scale)
        site_no = blk_sc.num_sites
    
    blk_str_sites = set(blk_sc.sites)
    custom_kpoints = Kpoints.automatic_density(blk_sc, kppa=kpoint_den)
    mpvis = MPMetalRelaxSet(blk_sc, user_incar_settings=def_vasp_incar_param,
                            user_kpoints_settings=custom_kpoints)

    if mpid:
        root_fldr = mpid
    else:
        root_fldr = struct.composition.reduced_formula

    fin_dir = os.path.join(root_fldr, 'bulk')
    mpvis.write_input(fin_dir)
    if not mpid:    # write the input structure if mpid is not used
        struct.to(fmt='poscar', filename=os.path.join(fin_dir, 'POSCAR.uc'))

    # Create each defect structure and associated VASP files
    # First find all unique defect sites
    periodic_struct = sga.get_symmetrized_structure()
    unique_sites = list(set([periodic_struct.find_equivalent_sites(site)[0] \
                             for site in periodic_struct.sites]))
    temp_struct = Structure.from_sites(sorted(unique_sites))
    prim_struct2 = SpacegroupAnalyzer(temp_struct).find_primitive()
    prim_struct2.lattice = prim_struct.lattice  # a little hacky
    for i, site in enumerate(prim_struct2.sites):
        vac = Vacancy(structure=prim_struct, defect_site=site)
        vac_sc = vac.generate_defect_structure(supercell=sc_scale)

        # Get vacancy site information
        vac_str_sites = set(vac_sc.sites)
        vac_sites = blk_str_sites - vac_str_sites
        vac_site = next(iter(vac_sites))
        site_mult = vac.get_multiplicity()
        vac_site_specie = vac_site.specie
        vac_symbol = vac_site_specie.symbol

        custom_kpoints = Kpoints.automatic_density(vac_sc, kppa=kpoint_den)
        mpvis = MPMetalRelaxSet(vac_sc,
                                user_incar_settings=def_vasp_incar_param,
                                user_kpoints_settings=custom_kpoints)
        vac_dir = 'vacancy_{}_mult-{}_sitespecie-{}'.format(
                    str(i+1), site_mult, vac_symbol)
        fin_dir = os.path.join(root_fldr, vac_dir)
        mpvis.write_input(fin_dir)

        # Antisites generation at the vacancy site
        struct_species = blk_sc.species
        for specie in set(struct_species) - set([vac_site_specie]):
            specie_symbol = specie.symbol
            anti_sc = vac_sc.copy()
            anti_sc.append(specie, vac_site.frac_coords)
            mpvis = MPMetalRelaxSet(anti_sc,
                                    user_incar_settings=def_vasp_incar_param,
                                    user_kpoints_settings=custom_kpoints)
            anti_dir = 'antisite_{}_mult-{}_sitespecie-{}_subspecie-{}'.format(
                        str(i+1), site_mult, vac_symbol, specie_symbol)
            fin_dir = os.path.join(root_fldr, anti_dir)
            mpvis.write_input(fin_dir)
Example #56
0
    def get_tasker2_slabs(self, tol=0.01, same_species_only=True):
        """
        Get a list of slabs that have been Tasker 2 corrected.

        Args:
            tol (float): Tolerance to determine if atoms are within same plane.
                This is a fractional tolerance, not an absolute one.
            same_species_only (bool): If True, only that are of the exact same
                species as the atom at the outermost surface are considered for
                moving. Otherwise, all atoms regardless of species that is
                within tol are considered for moving. Default is True (usually
                the desired behavior).

        Returns:
            ([Slab]) List of tasker 2 corrected slabs.
        """
        sites = list(self.sites)
        slabs = []

        sortedcsites = sorted(sites, key=lambda site: site.c)

        # Determine what fraction the slab is of the total cell size in the
        # c direction. Round to nearest rational number.
        nlayers_total = int(round(self.lattice.c /
                                  self.oriented_unit_cell.lattice.c))
        nlayers_slab = int(round((sortedcsites[-1].c - sortedcsites[0].c)
                                 * nlayers_total))
        slab_ratio = nlayers_slab / nlayers_total

        a = SpacegroupAnalyzer(self)
        symm_structure = a.get_symmetrized_structure()

        def equi_index(site):
            for i, equi_sites in enumerate(symm_structure.equivalent_sites):
                if site in equi_sites:
                    return i
            raise ValueError("Cannot determine equi index!")

        for surface_site, shift in [(sortedcsites[0], slab_ratio),
                                    (sortedcsites[-1], -slab_ratio)]:
            tomove = []
            fixed = []
            for site in sites:
                if abs(site.c - surface_site.c) < tol and (
                        (not same_species_only) or
                        site.species_and_occu == surface_site.species_and_occu):
                    tomove.append(site)
                else:
                    fixed.append(site)

            # Sort and group the sites by the species and symmetry equivalence
            tomove = sorted(tomove, key=lambda s: equi_index(s))

            grouped = [list(sites) for k, sites in itertools.groupby(
                tomove, key=lambda s: equi_index(s))]

            if len(tomove) == 0 or any([len(g) % 2 != 0 for g in grouped]):
                warnings.warn("Odd number of sites to divide! Try changing "
                              "the tolerance to ensure even division of "
                              "sites or create supercells in a or b directions "
                              "to allow for atoms to be moved!")
                continue
            combinations = []
            for g in grouped:
                combinations.append(
                    [c for c in itertools.combinations(g, int(len(g) / 2))])

            for selection in itertools.product(*combinations):
                species = [site.species_and_occu for site in fixed]
                fcoords = [site.frac_coords for site in fixed]

                for s in tomove:
                    species.append(s.species_and_occu)
                    for group in selection:
                        if s in group:
                            fcoords.append(s.frac_coords)
                            break
                    else:
                        # Move unselected atom to the opposite surface.
                        fcoords.append(s.frac_coords + [0, 0, shift])

                # sort by species to put all similar species together.
                sp_fcoord = sorted(zip(species, fcoords), key=lambda x: x[0])
                species = [x[0] for x in sp_fcoord]
                fcoords = [x[1] for x in sp_fcoord]
                slab = Slab(self.lattice, species, fcoords, self.miller_index,
                            self.oriented_unit_cell, self.shift,
                            self.scale_factor, energy=self.energy)
                slabs.append(slab)
        s = StructureMatcher()
        unique = [ss[0] for ss in s.group_structures(slabs)]
        return unique