예제 #1
0
def _filter_and_merge(inserted_structure: Structure):
    """
    For each site in a structure, split it into a migration sublattice where all sites contain the "insertion_energy"
    property and a host lattice. For each site in the migration sublattice if there is collision with the host sites,
    remove the migration site. Finally merge all the migration sites.
    """
    migration_sites = []
    base_sites = []
    for i_site in inserted_structure:
        if "insertion_energy" in i_site.properties and isinstance(
                i_site.properties["insertion_energy"], float):
            migration_sites.append(i_site)
        else:
            base_sites.append(i_site)
    migration = Structure.from_sites(migration_sites)
    base = Structure.from_sites(base_sites)

    non_colliding_sites = []
    for i_site in migration.sites:
        col_sites = base.get_sites_in_sphere(i_site.coords, BASE_COLLISION_R)
        if len(col_sites) == 0:
            non_colliding_sites.append(i_site)
    res = Structure.from_sites(non_colliding_sites + base.sites)
    res.merge_sites(tol=SITE_MERGE_R, mode="average")
    return res
예제 #2
0
    def make_site_diff(self):
        removed_sites = [
            self._perfect_structure[x] for x in self.removed_indices
        ]
        inserted_sites = [
            self._defect_structure[x] for x in self.inserted_indices
        ]

        try:
            removed_str = Structure.from_sites(removed_sites)
        except ValueError:
            removed_str = None

        try:
            inserted_str = Structure.from_sites(inserted_sites)
        except ValueError:
            inserted_str = None

        if inserted_str and removed_str:
            r_to_i = self._atom_projection(removed_str,
                                           inserted_str,
                                           specie=False)
            i_to_r = self._atom_projection(inserted_str,
                                           removed_str,
                                           specie=False)
        else:
            r_to_i = [None] * len(removed_sites)
            i_to_r = [None] * len(inserted_sites)

        mapping, removed_mapping, inserted_mapping = [], [], []
        for x, y in enumerate(r_to_i):
            if y and x == i_to_r[y]:
                mapping.append(
                    (self.removed_indices[x], self.inserted_indices[y]))
                removed_mapping.append(self.removed_indices[x])
                inserted_mapping.append(self.inserted_indices[y])

        removed, removed_by_sub = [], []
        for idx in self.removed_indices:
            site = self._perfect_structure[idx]
            val = idx, site.species_string, tuple(site.frac_coords)
            if idx in removed_mapping:
                removed_by_sub.append(val)
            else:
                removed.append(val)

        inserted, inserted_by_sub = [], []
        for idx in self.inserted_indices:
            site = self._defect_structure[idx]
            val = idx, site.species_string, tuple(site.frac_coords)
            if idx in inserted_mapping:
                inserted_by_sub.append(val)
            else:
                inserted.append(val)

        return SiteDiff(removed=removed,
                        inserted=inserted,
                        removed_by_sub=removed_by_sub,
                        inserted_by_sub=inserted_by_sub)
예제 #3
0
    def get_structures(self, nimages=5, vac_mode=True, idpp=False, **idpp_kwargs):
        r"""
        Generate structures for NEB calculation.

        Args:
            nimages (int): Defaults to 5. Number of NEB images. Total number of
                structures returned in nimages+2.
            vac_mode (bool): Defaults to True. In vac_mode, a vacancy diffusion
                mechanism is assumed. The initial and end sites of the path
                are assumed to be the initial and ending positions of the
                vacancies. If vac_mode is False, an interstitial mechanism is
                assumed. The initial and ending positions are assumed to be
                the initial and ending positions of the interstitial, and all
                other sites of the same specie are removed. E.g., if NEBPaths
                were obtained using a Li4Fe4P4O16 structure, vac_mode=True would
                generate structures with formula Li3Fe4P4O16, while
                vac_mode=False would generate structures with formula
                LiFe4P4O16.
            idpp (bool): Defaults to False. If True, the generated structures
                will be run through the IDPPSolver to generate a better guess
                for the minimum energy path.
            \*\*idpp_kwargs: Passthrough kwargs for the IDPPSolver.run.

        Returns:
            [Structure] Note that the first site of each structure is always
            the migrating ion. This makes it easier to perform subsequent
            analysis.
        """
        migrating_specie_sites = []
        other_sites = []
        isite = self.isite
        esite = self.esite

        for site in self.symm_structure.sites:
            if site.specie != isite.specie:
                other_sites.append(site)
            else:
                if vac_mode and (
                    isite.distance(site) > 1e-8 and esite.distance(site) > 1e-8
                ):
                    migrating_specie_sites.append(site)

        start_structure = Structure.from_sites(
            [self.isite] + migrating_specie_sites + other_sites
        )
        end_structure = Structure.from_sites(
            [self.esite] + migrating_specie_sites + other_sites
        )

        structures = start_structure.interpolate(
            end_structure, nimages=nimages + 1, pbc=False
        )

        if idpp:
            solver = IDPPSolver(structures)
            return solver.run(**idpp_kwargs)

        return structures
예제 #4
0
 def remove_site_at_pos(structure: Structure, site: PeriodicSite):
     new_struct_sites = []
     for isite in structure:
         if not vac_mode or (isite.distance(site) <= 1e-8):
             continue
         new_struct_sites.append(isite)
     return Structure.from_sites(new_struct_sites)
예제 #5
0
    def test_get_mapping(self):
        sm = StructureMatcher(ltol=0.2,
                              stol=0.3,
                              angle_tol=5,
                              primitive_cell=False,
                              scale=True,
                              attempt_supercell=False,
                              allow_subset=True)
        l = Lattice.orthorhombic(1, 2, 3)
        s1 = Structure(l, ['Ag', 'Si', 'Si'],
                       [[.7, .4, .5], [0, 0, 0.1], [0, 0, 0.2]])
        s1.make_supercell([2, 1, 1])
        s2 = Structure(l, ['Si', 'Si', 'Ag'],
                       [[0, 0.1, -0.95], [0, 0.1, 0], [-.7, .5, .375]])

        shuffle = [2, 0, 1, 3, 5, 4]
        s1 = Structure.from_sites([s1[i] for i in shuffle])
        #test the mapping
        s2.make_supercell([2, 1, 1])
        #equal sizes
        for i, x in enumerate(sm.get_mapping(s1, s2)):
            self.assertEqual(s1[x].species_and_occu, s2[i].species_and_occu)

        del s1[0]
        #s1 is subset of s2
        for i, x in enumerate(sm.get_mapping(s2, s1)):
            self.assertEqual(s1[i].species_and_occu, s2[x].species_and_occu)
        #s2 is smaller than s1
        del s2[0]
        del s2[1]
        self.assertRaises(ValueError, sm.get_mapping, s2, s1)
예제 #6
0
    def get_full_sites(self):
        """
        Get each group of symmetry inequivalent sites and combine them

        Args:

        Returns: a Structure with all possible Li sites, the enregy of the structure is stored as a site property

        """
        res = []
        for itr in self.translated_single_cat_entries:
            sub_site_list = get_all_sym_sites(
                itr,
                self.base_struct_entry,
                self.migrating_specie,
                symprec=self.symprec,
                angle_tol=self.angle_tol,
            )
            # ic(sub_site_list._sites)
            res.extend(sub_site_list._sites)
        # check to see if the sites collide with the base struture
        filtered_res = []
        for itr in res:
            col_sites = self.base_struct_entry.structure.get_sites_in_sphere(
                itr.coords, BASE_COLLISION_R)
            if len(col_sites) == 0:
                filtered_res.append(itr)
        res = Structure.from_sites(filtered_res)
        if len(res) > 1:
            res.merge_sites(tol=SITE_MERGE_R, mode="average")
        return res
예제 #7
0
    def test_get_mapping(self):
        sm = StructureMatcher(ltol=0.2, stol=0.3, angle_tol=5,
                              primitive_cell=False, scale=True,
                              attempt_supercell=False,
                              allow_subset = True)
        l = Lattice.orthorhombic(1, 2, 3)
        s1 = Structure(l, ['Ag', 'Si', 'Si'],
                       [[.7,.4,.5],[0,0,0.1],[0,0,0.2]])
        s1.make_supercell([2,1,1])
        s2 = Structure(l, ['Si', 'Si', 'Ag'],
                       [[0,0.1,-0.95],[0,0.1,0],[-.7,.5,.375]])

        shuffle = [2,0,1,3,5,4]
        s1 = Structure.from_sites([s1[i] for i in shuffle])
        #test the mapping
        s2.make_supercell([2,1,1])
        #equal sizes
        for i, x in enumerate(sm.get_mapping(s1, s2)):
            self.assertEqual(s1[x].species_and_occu,
                             s2[i].species_and_occu)

        del s1[0]
        #s1 is subset of s2
        for i, x in enumerate(sm.get_mapping(s2, s1)):
            self.assertEqual(s1[i].species_and_occu,
                             s2[x].species_and_occu)
        #s2 is smaller than s1
        del s2[0]
        del s2[1]
        self.assertRaises(ValueError, sm.get_mapping, s2, s1)
예제 #8
0
def slab_from_file(structure, hkl):
    """
    Reads in structure from the file and returns slab object.

    Args:
         structure (str): Structure file in any format supported by pymatgen.
            Will accept a pymatgen.Structure object directly.
         hkl (tuple): Miller index of the slab in the input file.

    Returns:
         Slab object
    """
    if type(structure) == str:
        slab_input = Structure.from_file(structure)
    else:
        slab_input = structure
    return Slab(
        slab_input.lattice,
        slab_input.species_and_occu,
        slab_input.frac_coords,
        hkl,
        Structure.from_sites(slab_input, to_unit_cell=True),
        # this OUC is not correct, need to get it from slabgenerator
        shift=0,
        scale_factor=np.eye(3, dtype=np.int),
        site_properties=slab_input.site_properties)
예제 #9
0
 def test_site_index_mapping_one(self):
     a = 6.19399
     lattice = Lattice.from_parameters(a, a, a, 90, 90, 90)
     coords1 = np.array([[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]])
     coords2 = np.array([[0.1, 0.1, 0.1], [0.4, 0.6, 0.4]])
     sites1 = [
         PeriodicSite(species='Na', coords=c, lattice=lattice)
         for c in coords1
     ]
     sites2 = [
         PeriodicSite(species='Na', coords=c, lattice=lattice)
         for c in coords2
     ]
     structure1 = Structure.from_sites(sites1)
     structure2 = Structure.from_sites(sites2)
     mapping = site_index_mapping(structure1, structure2)
     np.testing.assert_array_equal(mapping, np.array([0, 1]))
예제 #10
0
 def test_filter_and_merge(self):
     combined_struct = Structure.from_sites(
         self.struct_inserted_1Li1.sites + self.struct_inserted_1Li2.sites +
         self.struct_inserted_2Li.sites)
     filtered_struct = _filter_and_merge(combined_struct)
     for i_insert_site in filtered_struct:
         if i_insert_site.species_string == "Li":
             self.assertIn(i_insert_site.properties["insertion_energy"],
                           {4.5, 5.5})
예제 #11
0
 def test_site_index_mapping_with_species_1_as_string(self):
     a = 6.19399
     lattice = Lattice.from_parameters(a, a, a, 90, 90, 90)
     coords1 = np.array([[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]])
     coords2 = np.array([[0.4, 0.6, 0.4], [0.1, 0.1, 0.1]])
     species1 = ['Na', 'Cl']
     sites1 = [
         PeriodicSite(species=s, coords=c, lattice=lattice)
         for s, c in zip(species1, coords1)
     ]
     sites2 = [
         PeriodicSite(species='Na', coords=c, lattice=lattice)
         for c in coords2
     ]
     structure1 = Structure.from_sites(sites1)
     structure2 = Structure.from_sites(sites2)
     mapping = site_index_mapping(structure1, structure2, species1='Na')
     np.testing.assert_array_equal(mapping, np.array([1]))
예제 #12
0
    def get_sc_structures(
        self,
        vac_mode: bool,
        min_atoms: int = 80,
        max_atoms: int = 240,
        min_length: float = 10.0,
    ) -> Tuple[Structure, Structure, Structure]:
        """
        Construct supercells that represents the start and end positions for migration analysis.

        Args:
            vac_mode: If true simulate vacancy diffusion.
            max_atoms: Maximum number of atoms allowed in the supercell.
            min_atoms: Minimum number of atoms allowed in the supercell.
            min_length: Minimum length of the smallest supercell lattice vector.

        Returns:
            Start, End, Base Structures.

            If not vacancy mode, the base structure is just the host lattice.
            If in vacancy mode, the base structure is the fully intercalated structure

        """
        migrating_specie_sites, other_sites = self._split_migrating_and_other_sites(
            vac_mode)
        if vac_mode:
            base_struct = Structure.from_sites(other_sites +
                                               migrating_specie_sites)
        else:
            base_struct = Structure.from_sites(other_sites)
        sc_mat = get_sc_fromstruct(
            base_struct=base_struct,
            min_atoms=min_atoms,
            max_atoms=max_atoms,
            min_length=min_length,
        )
        start_struct, end_struct, base_sc = get_start_end_structures(
            self.isite,
            self.esite,
            base_struct,
            sc_mat,
            vac_mode=vac_mode  # type: ignore
        )
        return start_struct, end_struct, base_sc
예제 #13
0
 def setUp(self):
     c1 = [[0.5] * 3, [0.9] * 3]
     c2 = [[0.5] * 3, [0.9, 0.1, 0.1]]
     s1 = Structure(Lattice.cubic(5), ['Si', 'Si'], c1)
     s2 = Structure(Lattice.cubic(5), ['Si', 'Si'], c2)
     structs = []
     for s in s1.interpolate(s2, 3, pbc=True):
         structs.append(Structure.from_sites(s.sites, to_unit_cell=True))
     self.structures = structs
     self.vis = MITNEBSet(self.structures)
예제 #14
0
 def test_site_index_mapping_with_one_to_one_mapping_raises_ValueError_one(
         self):
     a = 6.19399
     lattice = Lattice.from_parameters(a, a, a, 90, 90, 90)
     coords1 = np.array([[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]])
     coords2 = np.array([[0.4, 0.6, 0.4], [0.1, 0.1, 0.1]])
     species2 = ['Na', 'Cl']
     sites1 = [
         PeriodicSite(species='Na', coords=c, lattice=lattice)
         for c in coords1
     ]
     sites2 = [
         PeriodicSite(species=s, coords=c, lattice=lattice)
         for s, c in zip(species2, coords2)
     ]
     structure1 = Structure.from_sites(sites1)
     structure2 = Structure.from_sites(sites2)
     with self.assertRaises(ValueError):
         site_index_mapping(structure1, structure2, species2='Na')
예제 #15
0
 def setUp(self):
     c1 = [[0.5] * 3, [0.9] * 3]
     c2 = [[0.5] * 3, [0.9, 0.1, 0.1]]
     s1 = Structure(Lattice.cubic(5), ['Si', 'Si'], c1)
     s2 = Structure(Lattice.cubic(5), ['Si', 'Si'], c2)
     structs = []
     for s in s1.interpolate(s2, 3, pbc=True):
         structs.append(Structure.from_sites(s.sites, to_unit_cell=True))
     self.structures = structs
     self.vis = MITNEBSet(self.structures)
예제 #16
0
파일: sets.py 프로젝트: Rubenpybat/pybat
    def write_input(self, output_dir, make_dir_if_not_present=True,
                    write_cif=False, write_path_cif=False,
                    write_endpoint_inputs=False):
        """
        NEB inputs has a special directory structure where inputs are in 00,
        01, 02, ....

        Args:
            output_dir (str): Directory to output the VASP input files
            make_dir_if_not_present (bool): Set to True if you want the
                directory (and the whole path) to be created if it is not
                present.
            write_cif (bool): If true, writes a cif along with each POSCAR.
            write_path_cif (bool): If true, writes a cif for each image.
            write_endpoint_inputs (bool): If true, writes input files for
                running endpoint calculations.

        """

        if make_dir_if_not_present and not os.path.exists(output_dir):
            os.makedirs(output_dir)
        self.incar.write_file(os.path.join(output_dir, 'INCAR'))
        self.kpoints.write_file(os.path.join(output_dir, 'KPOINTS'))
        self.potcar.write_file(os.path.join(output_dir, 'POTCAR'))

        for i, p in enumerate(self.poscars):
            d = os.path.join(output_dir, str(i).zfill(2))
            if not os.path.exists(d):
                os.makedirs(d)
            p.write_file(os.path.join(d, 'POSCAR'))
            if write_cif:
                p.structure.to(filename=os.path.join(d, '{}.cif'.format(i)))
        if write_endpoint_inputs:
            end_point_param = BulkRelaxSet(
                self.structures[0],
                user_incar_settings=self.user_incar_settings)

            for image in ['00', str(len(self.structures) - 1).zfill(2)]:
                end_point_param.incar.write_file(
                    os.path.join(output_dir, image, 'INCAR')
                )
                end_point_param.kpoints.write_file(
                    os.path.join(output_dir, image, 'KPOINTS')
                )
                end_point_param.potcar.write_file(
                    os.path.join(output_dir, image, 'POTCAR')
                )
        if write_path_cif:
            sites = set()
            lattice = self.structures[0].lattice
            for site in chain(*(s.sites for s in self.structures)):
                sites.add(PeriodicSite(site.species_and_occu, site.frac_coords,
                                       lattice))
            nebpath = Structure.from_sites(sorted(sites))
            nebpath.to(filename=os.path.join(output_dir, 'path.cif'))
예제 #17
0
    def convert_to_structure(self, coloring) -> Structure:
        list_psites = [
            self.precomputed_psites[i][coloring[i]]
            for i in range(self.dshash.num_sites)
        ]
        if self.additional_psites is not None:
            list_psites.extend(self.additional_psites)

        dstruct = Structure.from_sites(list_psites)

        return dstruct
예제 #18
0
def get_endpoints_from_index(structure, site_indices):
    """
    This class reads in one perfect structure and the two endpoint structures
    are generated using site_indices.

    Args:
        structure (Structure): A perfect structure.
        site_indices (list of int): a two-element list indicating site indices.

    Returns:
        endpoints (list of Structure): a two-element list of two endpoints
                                        Structure object.
    """

    if len(site_indices) != 2 or len(set(site_indices)) != 2:
        raise ValueError("Invalid indices!")
    if structure[site_indices[0]].specie != structure[site_indices[1]].specie:
        raise ValueError("The site indices must be "
                         "associated with identical species!")

    s = structure.copy()
    sites = s.sites

    # Move hopping atoms to the beginning of species index.
    init_site = sites[site_indices[0]]
    final_site = sites[site_indices[1]]
    sites.remove(init_site)
    sites.remove(final_site)

    init_sites = copy.deepcopy(sites)
    final_sites = copy.deepcopy(sites)

    init_sites.insert(0, final_site)
    final_sites.insert(0, init_site)

    s_0 = Structure.from_sites(init_sites)
    s_1 = Structure.from_sites(final_sites)

    endpoints = [s_0, s_1]

    return endpoints
예제 #19
0
def get_sym_migration_ion_sites(
    base_struct: Structure,
    inserted_struct: Structure,
    migrating_ion: str,
    symprec: float = 0.01,
    angle_tol: float = 5.0,
) -> Structure:
    """
    Take one inserted entry then map out all symmetry equivalent copies of the cation sites in base entry.
    Each site is decorated with the insertion energy calculated from the base and inserted entries.

    Args:
        inserted_entry: entry that contains cation
        base_struct_entry: the entry containing the base structure
        migrating_ion_entry: the name of the migrating species
        symprec: the symprec tolerance for the space group analysis
        angle_tol: the angle tolerance for the space group analysis

    Returns:
        Structure with only the migrating ion sites decorated with insertion energies.
    """
    wi_ = migrating_ion

    sa = SpacegroupAnalyzer(base_struct,
                            symprec=symprec,
                            angle_tolerance=angle_tol)
    # start with the base structure but empty
    sym_migration_ion_sites = list(
        filter(
            lambda isite: isite.species_string == wi_,
            inserted_struct.sites,
        ))

    sym_migration_struct = Structure.from_sites(sym_migration_ion_sites)
    for op in sa.get_space_group_operations():
        struct_tmp = sym_migration_struct.copy()
        struct_tmp.apply_operation(symmop=op, fractional=True)
        for isite in struct_tmp.sites:
            if isite.species_string == wi_:
                sym_migration_struct.insert(
                    0,
                    wi_,
                    coords=np.mod(isite.frac_coords, 1.0),
                    properties=isite.properties,
                )

            # must clean up as you go or the number of sites explodes
            if len(sym_migration_struct) > 1:
                sym_migration_struct.merge_sites(
                    tol=SITE_MERGE_R,
                    mode="average")  # keeps removing duplicates
    return sym_migration_struct
예제 #20
0
 def with_base_structure(cls, base_structure: Structure,
                         m_graph: StructureGraph,
                         **kwargs) -> "MigrationGraph":
     """
     Args:
         base_structure: base framework structure that does not contain any
          migrating sites.
     Returns:
         A constructed MigrationGraph object
     """
     sites = m_graph.structure.sites + base_structure.sites
     structure = Structure.from_sites(sites)
     return cls(structure=structure, m_graph=m_graph, **kwargs)
예제 #21
0
 def test_site_index_mapping_with_return_mapping_distances(self):
     a = 6.19399
     lattice = Lattice.from_parameters(a, a, a, 90, 90, 90)
     coords1 = np.array([[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]])
     coords2 = np.array([[0.1, 0.1, 0.1], [0.4, 0.6, 0.4]])
     sites1 = [
         PeriodicSite(species='Na', coords=c, lattice=lattice)
         for c in coords1
     ]
     sites2 = [
         PeriodicSite(species='Na', coords=c, lattice=lattice)
         for c in coords2
     ]
     structure1 = Structure.from_sites(sites1)
     structure2 = Structure.from_sites(sites2)
     mapping, distances = site_index_mapping(structure1,
                                             structure2,
                                             return_mapping_distances=True)
     np.testing.assert_array_equal(mapping, np.array([0, 1]))
     expected_distance = np.sqrt(3 * ((0.1 * a)**2))
     np.testing.assert_array_almost_equal(
         distances, np.array([expected_distance, expected_distance]))
예제 #22
0
    def write_path(self, fname, **kwargs):
        r"""
        Write the path to a file for easy viewing.

        Args:
            fname (str): File name.
            \*\*kwargs: Kwargs supported by NEBPath.get_structures.
        """
        sites = []
        for st in self.get_structures(**kwargs):
            sites.extend(st)
        st = Structure.from_sites(sites)
        st.to(filename=fname)
예제 #23
0
    def write_all_paths(self, fname, nimages=5, **kwargs):
        r"""
        Write a file containing all paths, using hydrogen as a placeholder for
        the images. H is chosen as it is the smallest atom. This is extremely
        useful for path visualization in a standard software like VESTA.

        Args:
            fname (str): Filename
            nimages (int): Number of images per path.
            \*\*kwargs: Passthrough kwargs to path.get_structures.
        """
        sites = []
        for p in self.get_paths():
            structures = p.get_structures(
                nimages=nimages, species=[self.migrating_specie], **kwargs
            )
            sites.append(structures[0][0])
            sites.append(structures[-1][0])
            for s in structures[1:-1]:
                sites.append(PeriodicSite("H", s[0].frac_coords, s.lattice))
        sites.extend(structures[0].sites[1:])
        Structure.from_sites(sites).to(filename=fname)
예제 #24
0
    def from_dict(cls, d):
        lattice = Lattice.from_dict(d["lattice"])
        sites = [PeriodicSite.from_dict(sd, lattice) for sd in d["sites"]]
        s = Structure.from_sites(sites)

        return cls(
            lattice=lattice,
            species=s.species_and_occu, coords=s.frac_coords,
            miller_index=d["miller_index"],
            oriented_unit_cell=Structure.from_dict(d["oriented_unit_cell"]),
            shift=d["shift"],
            scale_factor=MontyDecoder().process_decoded(d["scale_factor"]),
            site_properties=s.site_properties, energy=d["energy"]
        )
예제 #25
0
def clusters_from_structure(structure, rcut, elements):
    """
    Take a pymatgen structure, and converts it to a graph object

    Args:

        - structure (Structure): pymatgen structure object to set up graph from
        - rcut (float):   cut-off radii for node-node connections in forming clusters
        - elements ({str,str,.....}): set of element strings to include in setting up graph

    Returns:

        - clusters ({clusters}): set of clusters 

    """

    symbols = set([species for species in structure.symbol_set])

    if elements.issubset(structure.symbol_set):

        all_elements = set([species for species in structure.symbol_set])
        remove_elements = [x for x in all_elements if x not in elements]

        structure.remove_species(remove_elements)
        folded_structure = Structure.from_sites(structure.sites,
                                                to_unit_cell=True)

        nodes = nodes_from_structure(folded_structure, rcut, get_halo=True)
        set_fort_nodes(nodes)

        clusters = set()

        uc_nodes = set(
            [node for node in nodes if node.labels["Halo"] == False])

        while uc_nodes:
            node = uc_nodes.pop()
            if node.labels["Halo"] == False:
                cluster = Cluster({node})
                cluster.grow_cluster()
                uc_nodes.difference_update(cluster.nodes)
                clusters.add(cluster)
                set_cluster_periodic(cluster)

        return clusters
    else:
        raise ValueError(
            "The element set fed to 'clusters_from_file' is not a subset of the elements in the file"
        )
    def get_full_sites(self):
        """
        Get each group of symmetry inequivalent sites and combine them

        Args:

        Returns: a Structure with all possible Li sites, the enregy of the structure is stored as a site property

        """
        res = []
        for itr in self.translated_single_cat_entries:
            res.extend(self.get_all_sym_sites(itr).sites)
        res = Structure.from_sites(res)
        res.merge_sites(tol=1.0, mode='average')
        return res
    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)
예제 #28
0
    def as_ordered_structure(self):
        """
        Return the structure as a pymatgen.core.Structure, removing the
        unoccupied sites. This is because many of the IO methods of pymatgen
        run into issues when empty occupancies are present.

        Returns:
            pymatgen.core.Structure

        """

        return Structure.from_sites(
            [site for site in self.sites
             if site.species != Composition()]
        )
예제 #29
0
def get_only_sites_from_structure(structure: Structure,
                                  migrating_specie: str) -> Structure:
    """
    Get a copy of the structure with only the migrating sites.
    Args:
        structure: The full_structure that contains all the sites
        migrating_specie: The name of migrating species
    Returns:
      Structure: Structure with all possible migrating ion sites
    """
    migrating_ion_sites = list(
        filter(
            lambda site: site.species == Composition({migrating_specie: 1}),
            structure.sites,
        ))
    return Structure.from_sites(migrating_ion_sites)
예제 #30
0
    def get_sorted_structure(self, key=None, reverse=False) -> Structure:
        """
        Get a sorted structure for the interface. The parameters have the same
        meaning as in list.sort. By default, sites are sorted by the
        electronegativity of the species.

        Args:
            key: Specifies a function of one argument that is used to extract
                a comparison key from each list element: key=str.lower. The
                default value is None (compare the elements directly).
            reverse (bool): If set to True, then the list elements are sorted
                as if each comparison were reversed.
        """
        struct_copy = Structure.from_sites(self)
        struct_copy.sort(key=key, reverse=reverse)
        return struct_copy
예제 #31
0
    def __init__(
        self,
        structure: Structure,
        migration_graph: StructureGraph,
        structure_is_base=False,
        symprec=0.1,
        vac_mode=False,
    ):
        """
        Construct the MigrationGraph object using a potential_field will
        all mobile sites occupied. A potential_field graph is generated by
        connecting all sites withing max_path_length distance of each other
        The sites are decorated with Migration graph objects and then grouped
        together based on their equivalence.
        Args:
            structure: Structure with base framework and mobile sites.
             When used with structure_is_base = True, only the base framework
             structure, does not contain any migrating sites.
            migration_graph: The StructureGraph object that defines the
             migration network
            structure_is_base: Flag to indicate if the structure provided
             is only the base framework structure and does not contain any
             migrating sites.
            symprec (float): Symmetry precision to determine equivalence
             of migration events
        """
        if structure_is_base is True:
            sites = migration_graph.structure.sites + structure.sites
            self.structure = Structure.from_sites(sites)
        else:
            self.structure = structure

        self.migration_graph = migration_graph
        self.symprec = symprec
        self.vac_mode = vac_mode
        if self.vac_mode:
            raise NotImplementedError("Vacancy mode is not yet implemented")
        # Generate the graph edges between these all the sites
        self.migration_graph.set_node_attributes(
        )  # popagate the sites properties to the graph nodes
        # For poperies like unique_hops we might be interested in modifying them after creation
        # So let's not convert them into properties for now.  (Awaiting rewrite once the usage becomes more clear.)
        self._populate_edges_with_migration_paths()
        self._group_and_label_hops()
        self.unique_hops = None
        self._populate_unique_hops_dict()
예제 #32
0
def label_termination(slab: Structure) -> str:
    """Labels the slab surface termination"""
    frac_coords = slab.frac_coords
    n = len(frac_coords)

    if n == 1:
        # Clustering does not work when there is only one data point.
        form = slab.composition.reduced_formula
        sp_symbol = SpacegroupAnalyzer(slab,
                                       symprec=0.1).get_space_group_symbol()
        return f"{form}_{sp_symbol}_{len(slab)}"

    dist_matrix = np.zeros((n, n))
    h = slab.lattice.c
    # Projection of c lattice vector in
    # direction of surface normal.
    for i, j in combinations(list(range(n)), 2):
        if i != j:
            cdist = frac_coords[i][2] - frac_coords[j][2]
            cdist = abs(cdist - round(cdist)) * h
            dist_matrix[i, j] = cdist
            dist_matrix[j, i] = cdist

    condensed_m = squareform(dist_matrix)
    z = linkage(condensed_m)
    clusters = fcluster(z, 0.25, criterion="distance")

    clustered_sites: Dict[int, List[Site]] = {c: [] for c in clusters}
    for i, c in enumerate(clusters):
        clustered_sites[c].append(slab[i])

    plane_heights = {
        np.average(np.mod([s.frac_coords[2] for s in sites], 1)): c
        for c, sites in clustered_sites.items()
    }
    top_plane_cluster = sorted(plane_heights.items(),
                               key=lambda x: x[0])[-1][1]
    top_plane_sites = clustered_sites[top_plane_cluster]
    top_plane = Structure.from_sites(top_plane_sites)

    sp_symbol = SpacegroupAnalyzer(top_plane,
                                   symprec=0.1).get_space_group_symbol()
    form = top_plane.composition.reduced_formula
    return f"{form}_{sp_symbol}_{len(top_plane)}"
예제 #33
0
    def predict(self, structure, icsd_vol=False):
        """
        Given a structure, returns the predicted volume.

        Args:
            structure (Structure) : a crystal structure with an unknown volume.
            icsd_vol (bool) : True if the input structure's volume comes from
                ICSD.

        Returns:
            a float value of the predicted volume.
        """

        # Get standard deviation of electronnegativity in the structure.
        std_x = np.std([site.specie.X for site in structure])
        # Sites that have atomic radii
        sub_sites = []
        # Record the "DLS estimated radius" from bond_params.
        bp_dict = {}

        for sp in list(structure.composition.keys()):
            if sp.atomic_radius:
                sub_sites.extend([site for site in structure
                                  if site.specie == sp])
            else:
                warnings.warn("VolumePredictor: no atomic radius data for "
                              "{}".format(sp))

            if sp.symbol not in bond_params:
                warnings.warn("VolumePredictor: bond parameters not found, "
                              "used atomic radii for {}".format(sp))
            else:
                r, k = bond_params[sp.symbol]["r"], bond_params[sp.symbol]["k"]
                bp_dict[sp] = float(r) + float(k) * std_x

        # Structure object that include only sites with known atomic radii.
        reduced_structure = Structure.from_sites(sub_sites)
        smallest_ratio = None

        for site1 in reduced_structure:
            sp1 = site1.specie
            neighbors = reduced_structure.get_neighbors(site1,
                                                        sp1.atomic_radius +
                                                        self.cutoff)

            for site2, dist in neighbors:
                sp2 = site2.specie

                if sp1 in bp_dict and sp2 in bp_dict:
                    expected_dist = bp_dict[sp1] + bp_dict[sp2]
                else:
                    expected_dist = sp1.atomic_radius + sp2.atomic_radius

                if not smallest_ratio or dist / expected_dist < smallest_ratio:
                    smallest_ratio = dist / expected_dist

        if not smallest_ratio:
            raise ValueError("Could not find any bonds within the given cutoff "
                             "in this structure.")

        volume_factor = (1 / smallest_ratio) ** 3

        # icsd volume fudge factor
        if icsd_vol:
            volume_factor *= 1.05

        return structure.volume * volume_factor
예제 #34
0
    def test_supercell_subsets(self):
        sm = StructureMatcher(ltol=0.2, stol=0.3, angle_tol=5,
                              primitive_cell=False, scale=True,
                              attempt_supercell=True, allow_subset=True,
                              supercell_size='volume')
        sm_no_s = StructureMatcher(ltol=0.2, stol=0.3, angle_tol=5,
                              primitive_cell=False, scale=True,
                              attempt_supercell=True, allow_subset=False,
                              supercell_size='volume')
        l = Lattice.orthorhombic(1, 2, 3)
        s1 = Structure(l, ['Ag', 'Si', 'Si'],
                       [[.7,.4,.5],[0,0,0.1],[0,0,0.2]])
        s1.make_supercell([2,1,1])
        s2 = Structure(l, ['Si', 'Si', 'Ag'],
                       [[0,0.1,-0.95],[0,0.1,0],[-.7,.5,.375]])

        shuffle = [0,2,1,3,4,5]
        s1 = Structure.from_sites([s1[i] for i in shuffle])

        #test when s1 is exact supercell of s2
        result = sm.get_s2_like_s1(s1, s2)
        for a, b in zip(s1, result):
            self.assertTrue(a.distance(b) < 0.08)
            self.assertEqual(a.species_and_occu, b.species_and_occu)

        self.assertTrue(sm.fit(s1, s2))
        self.assertTrue(sm.fit(s2, s1))
        self.assertTrue(sm_no_s.fit(s1, s2))
        self.assertTrue(sm_no_s.fit(s2, s1))

        rms = (0.048604032430991401, 0.059527539448807391)
        self.assertTrue(np.allclose(sm.get_rms_dist(s1, s2), rms))
        self.assertTrue(np.allclose(sm.get_rms_dist(s2, s1), rms))

        #test when the supercell is a subset of s2
        subset_supercell = s1.copy()
        del subset_supercell[0]
        result = sm.get_s2_like_s1(subset_supercell, s2)
        self.assertEqual(len(result), 6)
        for a, b in zip(subset_supercell, result):
            self.assertTrue(a.distance(b) < 0.08)
            self.assertEqual(a.species_and_occu, b.species_and_occu)

        self.assertTrue(sm.fit(subset_supercell, s2))
        self.assertTrue(sm.fit(s2, subset_supercell))
        self.assertFalse(sm_no_s.fit(subset_supercell, s2))
        self.assertFalse(sm_no_s.fit(s2, subset_supercell))

        rms = (0.053243049896333279, 0.059527539448807336)
        self.assertTrue(np.allclose(sm.get_rms_dist(subset_supercell, s2), rms))
        self.assertTrue(np.allclose(sm.get_rms_dist(s2, subset_supercell), rms))

        #test when s2 (once made a supercell) is a subset of s1
        s2_missing_site = s2.copy()
        del s2_missing_site[1]
        result = sm.get_s2_like_s1(s1, s2_missing_site)
        for a, b in zip((s1[i] for i in (0, 2, 4, 5)), result):
            self.assertTrue(a.distance(b) < 0.08)
            self.assertEqual(a.species_and_occu, b.species_and_occu)

        self.assertTrue(sm.fit(s1, s2_missing_site))
        self.assertTrue(sm.fit(s2_missing_site, s1))
        self.assertFalse(sm_no_s.fit(s1, s2_missing_site))
        self.assertFalse(sm_no_s.fit(s2_missing_site, s1))

        rms = (0.029763769724403633, 0.029763769724403987)
        self.assertTrue(np.allclose(sm.get_rms_dist(s1, s2_missing_site), rms))
        self.assertTrue(np.allclose(sm.get_rms_dist(s2_missing_site, s1), rms))
예제 #35
0
    def __mul__(self, scaling_matrix):
        """
        Replicates the graph, creating a supercell,
        intelligently joining together
        edges that lie on periodic boundaries.
        In principle, any operations on the expanded
        graph could also be done on the original
        graph, but a larger graph can be easier to
        visualize and reason about.
        :param scaling_matrix: same as Structure.__mul__
        :return:
        """

        # Developer note: a different approach was also trialed, using
        # a simple Graph (instead of MultiDiGraph), with node indices
        # representing both site index and periodic image. Here, the
        # number of nodes != number of sites in the Structure. This
        # approach has many benefits, but made it more difficult to
        # keep the graph in sync with its corresponding Structure.

        # Broadly, it would be easier to multiply the Structure
        # *before* generating the StructureGraph, but this isn't
        # possible when generating the graph using critic2 from
        # charge density.

        # Multiplication works by looking for the expected position
        # of an image node, and seeing if that node exists in the
        # supercell. If it does, the edge is updated. This is more
        # computationally expensive than just keeping track of the
        # which new lattice images present, but should hopefully be
        # easier to extend to a general 3x3 scaling matrix.

        # code adapted from Structure.__mul__
        scale_matrix = np.array(scaling_matrix, np.int16)
        if scale_matrix.shape != (3, 3):
            scale_matrix = np.array(scale_matrix * np.eye(3), np.int16)
        else:
            # TODO: test __mul__ with full 3x3 scaling matrices
            raise NotImplementedError('Not tested with 3x3 scaling matrices yet.')
        new_lattice = Lattice(np.dot(scale_matrix, self.structure.lattice.matrix))

        f_lat = lattice_points_in_supercell(scale_matrix)
        c_lat = new_lattice.get_cartesian_coords(f_lat)

        new_sites = []
        new_graphs = []

        for v in c_lat:

            # create a map of nodes from original graph to its image
            mapping = {n: n + len(new_sites) for n in range(len(self.structure))}

            for idx, site in enumerate(self.structure):

                s = PeriodicSite(site.species_and_occu, site.coords + v,
                                 new_lattice, properties=site.properties,
                                 coords_are_cartesian=True, to_unit_cell=False)

                new_sites.append(s)

            new_graphs.append(nx.relabel_nodes(self.graph, mapping, copy=True))

        new_structure = Structure.from_sites(new_sites)

        # merge all graphs into one big graph
        new_g = nx.MultiDiGraph()
        for new_graph in new_graphs:
            new_g = nx.union(new_g, new_graph)

        edges_to_remove = []  # tuple of (u, v, k)
        edges_to_add = []  # tuple of (u, v, attr_dict)

        # list of new edges inside supercell
        # for duplicate checking
        edges_inside_supercell = [{u, v} for u, v, d in new_g.edges(data=True)
                                  if d['to_jimage'] == (0, 0, 0)]
        new_periodic_images = []

        orig_lattice = self.structure.lattice

        # use k-d tree to match given position to an
        # existing Site in Structure
        kd_tree = KDTree(new_structure.cart_coords)

        # tolerance in Å for sites to be considered equal
        # this could probably be a lot smaller
        tol = 0.05

        for u, v, k, d in new_g.edges(keys=True, data=True):

            to_jimage = d['to_jimage']  # for node v

            # reduce unnecessary checking
            if to_jimage != (0, 0, 0):

                # get index in original site
                n_u = u % len(self.structure)
                n_v = v % len(self.structure)

                # get fractional co-ordinates of where atoms defined
                # by edge are expected to be, relative to original
                # lattice (keeping original lattice has
                # significant benefits)
                v_image_frac = np.add(self.structure[n_v].frac_coords, to_jimage)
                u_frac = self.structure[n_u].frac_coords

                # using the position of node u as a reference,
                # get relative Cartesian co-ordinates of where
                # atoms defined by edge are expected to be
                v_image_cart = orig_lattice.get_cartesian_coords(v_image_frac)
                u_cart = orig_lattice.get_cartesian_coords(u_frac)
                v_rel = np.subtract(v_image_cart, u_cart)

                # now retrieve position of node v in
                # new supercell, and get absolute Cartesian
                # co-ordinates of where atoms defined by edge
                # are expected to be
                v_expec = new_structure[u].coords + v_rel

                # now search in new structure for these atoms
                # query returns (distance, index)
                v_present = kd_tree.query(v_expec)
                v_present = v_present[1] if v_present[0] <= tol else None

                # check if image sites now present in supercell
                # and if so, delete old edge that went through
                # periodic boundary
                if v_present is not None:

                    new_u = u
                    new_v = v_present
                    new_d = d.copy()

                    # node now inside supercell
                    new_d['to_jimage'] = (0, 0, 0)

                    edges_to_remove.append((u, v, k))

                    # make sure we don't try to add duplicate edges
                    # will remove two edges for everyone one we add
                    if {new_u, new_v} not in edges_inside_supercell:

                        # normalize direction
                        if new_v < new_u:
                            new_u, new_v = new_v, new_u

                        edges_inside_supercell.append({new_u, new_v})
                        edges_to_add.append((new_u, new_v, new_d))

                else:

                    # want to find new_v such that we have
                    # full periodic boundary conditions
                    # so that nodes on one side of supercell
                    # are connected to nodes on opposite side

                    v_expec_frac = new_structure.lattice.get_fractional_coords(v_expec)

                    # find new to_jimage
                    # use np.around to fix issues with finite precision leading to incorrect image
                    v_expec_image = np.around(v_expec_frac, decimals=3)
                    v_expec_image = v_expec_image - v_expec_image%1

                    v_expec_frac = np.subtract(v_expec_frac, v_expec_image)
                    v_expec = new_structure.lattice.get_cartesian_coords(v_expec_frac)
                    v_present = kd_tree.query(v_expec)
                    v_present = v_present[1] if v_present[0] <= tol else None

                    if v_present is not None:

                        new_u = u
                        new_v = v_present
                        new_d = d.copy()
                        new_to_jimage = tuple(map(int, v_expec_image))

                        # normalize direction
                        if new_v < new_u:
                            new_u, new_v = new_v, new_u
                            new_to_jimage = tuple(np.multiply(-1, d['to_jimage']).astype(int))

                        new_d['to_jimage'] = new_to_jimage

                        edges_to_remove.append((u, v, k))

                        if (new_u, new_v, new_to_jimage) not in new_periodic_images:
                            edges_to_add.append((new_u, new_v, new_d))
                            new_periodic_images.append((new_u, new_v, new_to_jimage))

        logger.debug("Removing {} edges, adding {} new edges.".format(len(edges_to_remove),
                                                                      len(edges_to_add)))

        # add/delete marked edges
        for edges_to_remove in edges_to_remove:
            new_g.remove_edge(*edges_to_remove)
        for (u, v, d) in edges_to_add:
            new_g.add_edge(u, v, **d)

        # return new instance of StructureGraph with supercell
        d = {"@module": self.__class__.__module__,
             "@class": self.__class__.__name__,
             "structure": new_structure.as_dict(),
             "graphs": json_graph.adjacency_data(new_g)}

        sg = StructureGraph.from_dict(d)

        return sg