示例#1
0
    def test_integration(self):
        """
        Sanity check: for a long enough diagonaly hop, if we turn the radius of the tube way up, it should cover the entire unit cell
        """
        total_chg_per_vol = (self.cbg.potential_field.data["total"].sum() /
                             self.cbg.potential_field.ngridpts /
                             self.cbg.potential_field.structure.volume)
        self.assertAlmostEqual(
            self.cbg._get_chg_between_sites_tube(
                self.cbg.unique_hops[2]["hop"]),
            total_chg_per_vol,
        )

        self.cbg._tube_radius = 2
        # self.cbg.populate_edges_with_chg_density_info()

        # find this particular hop
        ipos = [0.33079153, 0.18064031, 0.67945924]
        epos = [0.33587514, -0.3461259, 1.15269302]
        isite = PeriodicSite("Li", ipos, self.cbg.structure.lattice)
        esite = PeriodicSite("Li", epos, self.cbg.structure.lattice)
        ref_hop = MigrationHop(isite=isite,
                               esite=esite,
                               symm_structure=self.cbg.symm_structure)
        hop_idx = -1
        for k, d in self.cbg.unique_hops.items():
            if d["hop"] == ref_hop:
                hop_idx = k

        self.assertAlmostEqual(
            self.cbg._get_chg_between_sites_tube(
                self.cbg.unique_hops[hop_idx]["hop"]),
            0.188952739835188,
            3,
        )
示例#2
0
    def are_equal(self, d1, d2):
        """
        Args:
            d1: First defect. A pymatgen Defect object.
            d2: Second defect. A pymatgen Defect object.

        Returns:
            True if defects are identical in type and sublattice.
        """
        possible_defect_types = (Defect, Vacancy, Substitution, Interstitial)

        if not isinstance(d1, possible_defect_types) or not isinstance(d2, possible_defect_types):
            raise ValueError("Cannot use PointDefectComparator to" " compare non-defect objects...")

        if not isinstance(d1, d2.__class__):
            return False
        if d1.site.specie != d2.site.specie:
            return False
        if self.check_charge and (d1.charge != d2.charge):
            return False

        sm = StructureMatcher(
            ltol=0.01,
            primitive_cell=self.check_primitive_cell,
            scale=self.check_lattice_scale,
        )

        if not sm.fit(d1.bulk_structure, d2.bulk_structure):
            return False

        d1 = d1.copy()
        d2 = d2.copy()
        if self.check_primitive_cell or self.check_lattice_scale:
            # if allowing for base structure volume or supercell modifications,
            # then need to preprocess defect objects to allow for matching
            d1_mod_bulk_structure, d2_mod_bulk_structure, _, _ = sm._preprocess(d1.bulk_structure, d2.bulk_structure)
            d1_defect_site = PeriodicSite(
                d1.site.specie,
                d1.site.coords,
                d1_mod_bulk_structure.lattice,
                to_unit_cell=True,
                coords_are_cartesian=True,
            )
            d2_defect_site = PeriodicSite(
                d2.site.specie,
                d2.site.coords,
                d2_mod_bulk_structure.lattice,
                to_unit_cell=True,
                coords_are_cartesian=True,
            )

            d1._structure = d1_mod_bulk_structure
            d2._structure = d2_mod_bulk_structure
            d1._defect_site = d1_defect_site
            d2._defect_site = d2_defect_site

        return sm.fit(d1.generate_defect_structure(), d2.generate_defect_structure())
def convert_cd_to_de( cd, b_cse):
    """
    As of pymatgen v2.0, ComputedDefect objects were deprecated in favor
    of DefectEntry objects in pymatgen.analysis.defects.core
    This function takes a ComputedDefect (either as a dict or object) and
    converts it into a DefectEntry object in order to handle legacy
    PyCDT creation within the current paradigm of PyCDT.

    :param cd (dict or ComputedDefect object): ComputedDefect as an object or as a dictionary
    :params b_cse (dict or ComputedStructureEntry object): ComputedStructureEntry of bulk entry
        associated with the ComputedDefect.
    :return: de (DefectEntry): Resulting DefectEntry object
    """
    if type(cd) != dict:
        cd = cd.as_dict()
    if type(b_cse) != dict:
        b_cse = b_cse.as_dict()

    bulk_sc_structure = Structure.from_dict( b_cse["structure"])

    #modify defect_site as required for Defect object, confirming site exists in bulk structure
    site_cls = cd["site"]
    defect_site = PeriodicSite.from_dict( site_cls)
    def_nom = cd["name"].lower()
    if "sub_" in def_nom or "as_" in def_nom:
        #modify site object for substitution site of Defect object
        site_cls["species"][0]["element"] = cd["name"].split("_")[2]
        defect_site = PeriodicSite.from_dict( site_cls)

    poss_deflist = sorted(
        bulk_sc_structure.get_sites_in_sphere(defect_site.coords, 0.1, include_index=True), key=lambda x: x[1])
    if len(poss_deflist) != 1:
        raise ValueError("ComputedDefect to DefectEntry conversion failed. "
                         "Could not determine periodic site position in bulk supercell.")

    # create defect object
    if "vac_" in def_nom:
        defect_obj = Vacancy(bulk_sc_structure, defect_site, charge=cd["charge"])
    elif "as_" in def_nom or "sub_" in def_nom:
        defect_obj = Substitution(bulk_sc_structure, defect_site, charge=cd["charge"])
    elif "int_" in def_nom:
        defect_obj = Interstitial(bulk_sc_structure, defect_site, charge=cd["charge"])
    else:
        raise ValueError("Could not recognize defect type for {}".format( cd["name"]))


    # assign proper energy and parameter metadata
    uncorrected_energy = cd["entry"]["energy"] - b_cse["energy"]
    def_path = os.path.split( cd["entry"]["data"]["locpot_path"])[0]
    bulk_path = os.path.split( b_cse["data"]["locpot_path"])[0]
    p = {"defect_path": def_path,
         "bulk_path": bulk_path,
         "encut": cd["entry"]["data"]["encut"]}

    de = DefectEntry( defect_obj, uncorrected_energy, parameters = p)

    return de
示例#4
0
    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.migration_graph.graph[u][v][w]
        i_site = self.only_sites.sites[u]
        e_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"] = e_site.frac_coords
        edge["ipos_cart"] = np.dot(i_site.frac_coords,
                                   self.only_sites.lattice.matrix)
        edge["epos_cart"] = np.dot(e_site.frac_coords,
                                   self.only_sites.lattice.matrix)

        edge["hop"] = MigrationPath(i_site, e_site, self.symm_structure)
        edge["properties"] = {}
示例#5
0
def set_up_brillouin_facets(lattice):
    """
    Set up the facets of the 1st Brillouin zone as a list of cage.core.Facets.

    Args:
        lattice (pymatgen.core.lattice.Lattice): Lattice of the structure.

    Returns:
        (list) List of cage.core.Facet objects.

    """
    rec_lattice = lattice.reciprocal_lattice
    bz_facet_coords = lattice.get_brillouin_zone()
    bz_facet_sites = []

    for facet in bz_facet_coords:
        # The coordinates are transferred into sites to be able to use the
        # Facet object more easily.
        bz_facet_sites.append(
            [PeriodicSite("H", coords, rec_lattice, coords_are_cartesian=True)
             for coords in facet]
        )

    bz_facets = [Facet(facet_sites) for facet_sites in bz_facet_sites]

    return bz_facets
示例#6
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)
 def setUp(self):
     self.lifepo = self.get_structure("LiFePO4")
     migration_graph = MigrationGraph.with_distance(self.lifepo,
                                                    max_distance=4.0,
                                                    migrating_specie="Li")
     gen = iter(migration_graph.migration_graph.graph.edges(data=True))
     u, v, d = next(gen)
     i_site = PeriodicSite("Li",
                           coords=d["ipos"],
                           lattice=self.lifepo.lattice)
     e_site = PeriodicSite("Li",
                           coords=d["epos"],
                           lattice=self.lifepo.lattice)
     a = SpacegroupAnalyzer(self.lifepo)
     symm_structure = a.get_symmetrized_structure()
     self.m_hop = MigrationHop(i_site, e_site, symm_structure)
示例#8
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]))
示例#9
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]))
示例#10
0
    def __init__(
        self,
        isite: Site,
        esite: Site,
        symm_structure: SymmetrizedStructure,
        symprec: float = 0.001,
    ):
        """
        Args:
            isite: Initial site
            esite: End site
            symm_structure: SymmetrizedStructure
            symprec: used to determine equivalence
        """
        self.isite = isite
        self.esite = esite
        self.iindex = None
        self.eindex = None
        self.symm_structure = symm_structure
        self.symprec = symprec
        self.msite = PeriodicSite(esite.specie,
                                  (isite.frac_coords + esite.frac_coords) / 2,
                                  esite.lattice)
        sg = self.symm_structure.spacegroup
        for i, sites in enumerate(self.symm_structure.equivalent_sites):
            if sg.are_symmetrically_equivalent([isite], [sites[0]]):
                self.iindex = i
            if sg.are_symmetrically_equivalent([esite], [sites[0]]):
                self.eindex = i

        # if no index was identified then loop over each site until something is found
        if self.iindex is None:
            for i, sites in enumerate(self.symm_structure.equivalent_sites):
                for itr_site in sites:
                    if sg.are_symmetrically_equivalent([isite], [itr_site]):
                        self.iindex = i
                        break
                else:
                    continue
                break
        if self.eindex is None:
            for i, sites in enumerate(self.symm_structure.equivalent_sites):
                for itr_site in sites:
                    if sg.are_symmetrically_equivalent([esite], [itr_site]):
                        self.eindex = i
                        break
                else:
                    continue
                break

        if self.iindex is None:
            raise RuntimeError(
                f"No symmetrically equivalent site was found for {isite}")
        if self.eindex is None:
            raise RuntimeError(
                f"No symmetrically equivalent site was found for {esite}")
示例#11
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')
示例#12
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'))
示例#13
0
    def test_subs_and_interstits(self):
        # test manual subtitution specification
        CDS = ChargedDefectsStructures(self.gaas_struct, antisites_flag=False,
                                       substitutions={'Ga':['Si','In'], 'As':['Sb']})
        check_subs = {sub['name']: sub['unique_site'] for sub in CDS.defects['substitutions']}
        self.assertEqual(3, len(check_subs))
        self.assertEqual(self.ga_site, check_subs['sub_1_Si_on_Ga'])
        self.assertEqual(self.ga_site, check_subs['sub_1_In_on_Ga'])
        self.assertEqual(self.as_site, check_subs['sub_2_Sb_on_As'])

        # Test automatic interstitial finding.
        CDS = ChargedDefectsStructures(self.gaas_struct,
                                       include_interstitials=True,
                                       interstitial_elements=['Mn'])
        self.assertEqual(CDS.get_n_defects_of_type('interstitials'), 2)
        fnames = [i['name'][i['name'].index('M'):] for i in CDS.defects['interstitials']]
        self.assertEqual(sorted(fnames), sorted(['Mn_InFiT1_mult6', 'Mn_InFiT2_mult6']))
        nsites = len(CDS.defects['interstitials'][0]['supercell']['structure'].sites)
        self.assertEqual(len(CDS.get_ith_supercell_of_defect_type(
                0, 'interstitials').sites), nsites)
        self.assertEqual(CDS.defects['interstitials'][0]['charges'], \
                [0, 1, 2, 3, 4, 5, 6, 7])
        self.assertEqual(CDS.defects['interstitials'][1]['charges'], \
                [0, 1, 2, 3, 4, 5, 6, 7])

        # Test manual interstitial specification.
        isite = PeriodicSite('Mn',
                CDS.defects['interstitials'][0]['supercell']['structure'][nsites-1].coords,
                             self.gaas_struct.lattice)
        cds2 = ChargedDefectsStructures(self.gaas_struct,
                                        include_interstitials=True,
                                        standardized=False,
                                        # standardized = False is required when manually specifying interstitial,
                                        # because dont want base structure lattice to change
                                        intersites=(isite,))
        self.assertEqual(cds2.get_n_defects_of_type('interstitials'), 2)
        fnames = [i['name'][i['name'].index('_')+3:] for i in cds2.defects['interstitials']]
        self.assertEqual(sorted(fnames), sorted(['As', 'Ga']))
        nsites = len(cds2.defects['interstitials'][0]['supercell']['structure'].sites)
        self.assertEqual(len(cds2.get_ith_supercell_of_defect_type(
                0, 'interstitials').sites), nsites)
        cds3 = ChargedDefectsStructures(self.gaas_struct,
                                        include_interstitials=True,
                                        standardized=False,
                                        # standardized = False is required when manually specifying interstitial,
                                        # because dont want base structure lattice to change
                                        interstitial_elements=['Mn'],
                                        intersites=(isite,))
        self.assertEqual(cds3.get_n_defects_of_type('interstitials'), 1)
        fnames = [i['name'][i['name'].index('_')+3:] for i in cds3.defects['interstitials']]
        self.assertEqual(sorted(fnames), sorted(['Mn']))
        nsites = len(cds3.defects['interstitials'][0]['supercell']['structure'].sites)
        self.assertEqual(len(cds3.get_ith_supercell_of_defect_type(
                0, 'interstitials').sites), nsites)
示例#14
0
    def get_connected_sites(self, n, jimage=(0, 0, 0)):
        """
        Returns a named tuple of neighbors of site n:
        periodic_site, jimage, index, weight.
        Index is the index of the corresponding site
        in the original structure, weight can be
        None if not defined.
        :param n: index of Site in Structure
        :param jimage: lattice vector of site
        :return: list of ConnectedSite tuples,
        sorted by closest first
        """

        connected_sites = set()

        out_edges = [(u, v, d, 'out')
                     for u, v, d in self.graph.out_edges(n, data=True)]
        in_edges = [(u, v, d, 'in')
                    for u, v, d in self.graph.in_edges(n, data=True)]

        for u, v, d, dir in out_edges + in_edges:

            to_jimage = d['to_jimage']

            if dir == 'in':
                u, v = v, u
                to_jimage = np.multiply(-1, to_jimage)

            site_d = self.structure[v].as_dict()
            site_d['abc'] = np.add(site_d['abc'], to_jimage).tolist()
            to_jimage = tuple(map(int, np.add(to_jimage, jimage)))
            periodic_site = PeriodicSite.from_dict(site_d)

            weight = d.get('weight', None)

            # from_site if jimage arg != (0, 0, 0)
            relative_jimage = np.subtract(to_jimage, jimage)
            dist = self.structure[u].distance(self.structure[v],
                                              jimage=relative_jimage)

            connected_site = ConnectedSite(periodic_site=periodic_site,
                                           jimage=to_jimage,
                                           index=v,
                                           weight=weight,
                                           dist=dist)

            connected_sites.add(connected_site)

        # return list sorted by closest sites first
        connected_sites = list(connected_sites)
        connected_sites.sort(key=lambda x: x.dist)

        return connected_sites
示例#15
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]))
示例#16
0
    def get_connected_sites(self, n, jimage=(0, 0, 0)):
        """
        Returns a named tuple of neighbors of site n:
        periodic_site, jimage, index, weight.
        Index is the index of the corresponding site
        in the original structure, weight can be
        None if not defined.
        :param n: index of Site in Structure
        :param jimage: lattice vector of site
        :return: list of ConnectedSite tuples,
        sorted by closest first
        """

        connected_sites = set()

        out_edges = [(u, v, d, 'out') for u, v, d in self.graph.out_edges(n, data=True)]
        in_edges = [(u, v, d, 'in') for u, v, d in self.graph.in_edges(n, data=True)]

        for u, v, d, dir in out_edges + in_edges:

            to_jimage = d['to_jimage']

            if dir == 'in':
                u, v = v, u
                to_jimage = np.multiply(-1, to_jimage)

            site_d = self.structure[v].as_dict()
            site_d['abc'] = np.add(site_d['abc'], to_jimage).tolist()
            to_jimage = tuple(map(int, np.add(to_jimage, jimage)))
            periodic_site = PeriodicSite.from_dict(site_d)

            weight = d.get('weight', None)

            # from_site if jimage arg != (0, 0, 0)
            relative_jimage = np.subtract(to_jimage, jimage)
            dist = self.structure[u].distance(self.structure[v], jimage=relative_jimage)

            connected_site = ConnectedSite(periodic_site=periodic_site,
                                           jimage=to_jimage,
                                           index=v,
                                           weight=weight,
                                           dist=dist)

            connected_sites.add(connected_site)

        # return list sorted by closest sites first
        connected_sites = list(connected_sites)
        connected_sites.sort(key=lambda x: x.dist)

        return connected_sites
示例#17
0
    def test_convert_cd_to_de(self):
        #create a ComputedDefect object similar to legacy format
        # Vacancy type first
        struc = PymatgenTest.get_structure("VO2")
        struc.make_supercell(3)
        vac = Vacancy(struc, struc.sites[0], charge=-3)
        ids = vac.generate_defect_structure(1)
        defect_data = {
            "locpot_path": "defect/path/to/files/LOCPOT",
            "encut": 520
        }
        bulk_data = {"locpot_path": "bulk/path/to/files/LOCPOT"}

        cse_defect = ComputedStructureEntry(ids, 100., data=defect_data)
        cd = ComputedDefect(cse_defect,
                            struc.sites[0],
                            charge=-3,
                            name="Vac_1_O")
        b_cse = ComputedStructureEntry(struc, 10., data=bulk_data)

        de = convert_cd_to_de(cd, b_cse)
        self.assertIsInstance(de.defect, Vacancy)
        self.assertIsInstance(de, DefectEntry)
        self.assertEqual(de.parameters["defect_path"], "defect/path/to/files")
        self.assertEqual(de.parameters["bulk_path"], "bulk/path/to/files")
        self.assertEqual(de.parameters["encut"], 520)
        self.assertEqual(de.site.specie.symbol, "O")

        # try again for substitution type
        # (site object had bulk specie for ComputedDefects,
        # while it should have substituional site specie for DefectEntrys...)
        de_site_type = PeriodicSite("Sb", vac.site.frac_coords, struc.lattice)
        sub = Substitution(struc, de_site_type, charge=1)
        ids = sub.generate_defect_structure(1)

        cse_defect = ComputedStructureEntry(ids, 100., data=defect_data)
        cd = ComputedDefect(cse_defect,
                            struc.sites[0],
                            charge=1,
                            name="Sub_1_Sb_on_O")

        de = convert_cd_to_de(cd, b_cse)

        self.assertIsInstance(de.defect, Substitution)
        self.assertIsInstance(de, DefectEntry)
        self.assertEqual(de.site.specie.symbol, "Sb")
 def __init__(self, isite, esite, symm_structure):
     """
     Args:
         isite: Initial site
         esite: End site
         symm_structure: SymmetrizedStructure
     """
     self.isite = isite
     self.esite = esite
     self.symm_structure = symm_structure
     self.msite = PeriodicSite(esite.specie,
                               (isite.frac_coords + esite.frac_coords) / 2,
                               esite.lattice)
     sg = self.symm_structure.spacegroup
     for i, sites in enumerate(self.symm_structure.equivalent_sites):
         if sg.are_symmetrically_equivalent([isite], [sites[0]]):
             self.iindex = i
         if sg.are_symmetrically_equivalent([esite], [sites[0]]):
             self.eindex = i
 def test_symmetrized_structure(self):
     t = OrderDisorderedStructureTransformation(symmetrized_structures=True)
     c = []
     sp = []
     c.append([0.5, 0.5, 0.5])
     sp.append('Si4+')
     c.append([0.45, 0.45, 0.45])
     sp.append({"Si4+": 0.5})
     c.append([0.56, 0.56, 0.56])
     sp.append({"Si4+": 0.5})
     c.append([0.25, 0.75, 0.75])
     sp.append({"Si4+": 0.5})
     c.append([0.75, 0.25, 0.25])
     sp.append({"Si4+": 0.5})
     l = Lattice.cubic(5)
     s = Structure(l, sp, c)
     test_site = PeriodicSite("Si4+", c[2], l)
     s = SymmetrizedStructure(s, 'not_real', [0,1,1,2,2])
     output = t.apply_transformation(s)
     self.assertTrue(test_site in output.sites)
    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)
示例#21
0
    def test_defect_matching(self):
        # SETUP DEFECTS FOR TESTING
        # symmorphic defect test set
        s_struc = Structure.from_file(os.path.join(
            test_dir, "CsSnI3.cif"))  # tetragonal CsSnI3
        identical_Cs_vacs = [
            Vacancy(s_struc, s_struc[0]),
            Vacancy(s_struc, s_struc[1])
        ]
        identical_I_vacs_sublattice1 = [
            Vacancy(s_struc, s_struc[4]),
            Vacancy(s_struc, s_struc[5]),
            Vacancy(s_struc, s_struc[8]),
            Vacancy(s_struc, s_struc[9])
        ]  # in plane halides
        identical_I_vacs_sublattice2 = [
            Vacancy(s_struc, s_struc[6]),
            Vacancy(s_struc, s_struc[7])
        ]  # out of plane halides
        pdc = PointDefectComparator()

        # NOW TEST DEFECTS
        # test vacancy matching
        self.assertTrue(
            pdc.are_equal(identical_Cs_vacs[0],
                          identical_Cs_vacs[0]))  # trivial vacancy test
        self.assertTrue(
            pdc.are_equal(
                identical_Cs_vacs[0],
                identical_Cs_vacs[1]))  # vacancies on same sublattice
        for i, j in itertools.combinations(range(4), 2):
            self.assertTrue(
                pdc.are_equal(identical_I_vacs_sublattice1[i],
                              identical_I_vacs_sublattice1[j]))
        self.assertTrue(
            pdc.are_equal(identical_I_vacs_sublattice2[0],
                          identical_I_vacs_sublattice2[1]))
        self.assertFalse(
            pdc.are_equal(
                identical_Cs_vacs[
                    0],  # both vacancies, but different specie types
                identical_I_vacs_sublattice1[0]))
        self.assertFalse(
            pdc.are_equal(
                identical_I_vacs_sublattice1[
                    0],  # same specie type, different sublattice
                identical_I_vacs_sublattice2[0]))

        # test substitutional matching
        sub_Cs_on_I_sublattice1_set1 = PeriodicSite(
            'Cs', identical_I_vacs_sublattice1[0].site.frac_coords,
            s_struc.lattice)
        sub_Cs_on_I_sublattice1_set2 = PeriodicSite(
            'Cs', identical_I_vacs_sublattice1[1].site.frac_coords,
            s_struc.lattice)
        sub_Cs_on_I_sublattice2 = PeriodicSite(
            'Cs', identical_I_vacs_sublattice2[0].site.frac_coords,
            s_struc.lattice)
        sub_Rb_on_I_sublattice2 = PeriodicSite(
            'Rb', identical_I_vacs_sublattice2[0].site.frac_coords,
            s_struc.lattice)

        self.assertTrue(
            pdc.are_equal(  # trivial substitution test
                Substitution(s_struc, sub_Cs_on_I_sublattice1_set1),
                Substitution(s_struc, sub_Cs_on_I_sublattice1_set1)))

        self.assertTrue(
            pdc.are_equal(  # same sublattice, different coords
                Substitution(s_struc, sub_Cs_on_I_sublattice1_set1),
                Substitution(s_struc, sub_Cs_on_I_sublattice1_set2)))
        self.assertFalse(
            pdc.are_equal(  # different subs (wrong specie)
                Substitution(s_struc, sub_Cs_on_I_sublattice2),
                Substitution(s_struc, sub_Rb_on_I_sublattice2)))
        self.assertFalse(
            pdc.are_equal(  # different subs (wrong sublattice)
                Substitution(s_struc, sub_Cs_on_I_sublattice1_set1),
                Substitution(s_struc, sub_Cs_on_I_sublattice2)))

        # test symmorphic interstitial matching
        # (using set generated from Voronoi generator, with same sublattice given by saturatated_interstitial_structure function)
        inter_H_sublattice1_set1 = PeriodicSite('H', [0., 0.75, 0.25],
                                                s_struc.lattice)
        inter_H_sublattice1_set2 = PeriodicSite('H', [0., 0.75, 0.75],
                                                s_struc.lattice)
        inter_H_sublattice2 = PeriodicSite(
            'H', [0.57796112, 0.06923687, 0.56923687], s_struc.lattice)
        inter_H_sublattice3 = PeriodicSite('H', [0.25, 0.25, 0.54018268],
                                           s_struc.lattice)
        inter_He_sublattice3 = PeriodicSite('He', [0.25, 0.25, 0.54018268],
                                            s_struc.lattice)

        self.assertTrue(
            pdc.are_equal(  # trivial interstitial test
                Interstitial(s_struc, inter_H_sublattice1_set1),
                Interstitial(s_struc, inter_H_sublattice1_set1)))

        self.assertTrue(
            pdc.are_equal(  # same sublattice, different coords
                Interstitial(s_struc, inter_H_sublattice1_set1),
                Interstitial(s_struc, inter_H_sublattice1_set2)))
        self.assertFalse(
            pdc.are_equal(  # different interstitials (wrong sublattice)
                Interstitial(s_struc, inter_H_sublattice1_set1),
                Interstitial(s_struc, inter_H_sublattice2)))
        self.assertFalse(
            pdc.are_equal(  # different interstitials (wrong sublattice)
                Interstitial(s_struc, inter_H_sublattice1_set1),
                Interstitial(s_struc, inter_H_sublattice3)))
        self.assertFalse(
            pdc.are_equal(  # different interstitials (wrong specie)
                Interstitial(s_struc, inter_H_sublattice3),
                Interstitial(s_struc, inter_He_sublattice3)))

        # test non-symmorphic interstitial matching
        # (using set generated from Voronoi generator, with same sublattice given by saturatated_interstitial_structure function)
        ns_struc = Structure.from_file(os.path.join(test_dir, "CuCl.cif"))
        ns_inter_H_sublattice1_set1 = PeriodicSite(
            'H', [0.06924513, 0.06308959, 0.86766528], ns_struc.lattice)
        ns_inter_H_sublattice1_set2 = PeriodicSite(
            'H', [0.43691041, 0.36766528, 0.06924513], ns_struc.lattice)
        ns_inter_H_sublattice2 = PeriodicSite(
            'H', [0.06022109, 0.60196031, 0.1621814], ns_struc.lattice)
        ns_inter_He_sublattice2 = PeriodicSite(
            'He', [0.06022109, 0.60196031, 0.1621814], ns_struc.lattice)

        self.assertTrue(
            pdc.are_equal(  # trivial interstitial test
                Interstitial(ns_struc, ns_inter_H_sublattice1_set1),
                Interstitial(ns_struc, ns_inter_H_sublattice1_set1)))
        self.assertTrue(
            pdc.are_equal(  # same sublattice, different coords
                Interstitial(ns_struc, ns_inter_H_sublattice1_set1),
                Interstitial(ns_struc, ns_inter_H_sublattice1_set2)))
        self.assertFalse(
            pdc.are_equal(
                Interstitial(ns_struc, ns_inter_H_sublattice1_set1
                             ),  # different interstitials (wrong sublattice)
                Interstitial(ns_struc, ns_inter_H_sublattice2)))
        self.assertFalse(
            pdc.are_equal(  # different interstitials (wrong specie)
                Interstitial(ns_struc, ns_inter_H_sublattice2),
                Interstitial(ns_struc, ns_inter_He_sublattice2)))

        # test influence of charge on defect matching (default is to be charge agnostic)
        vac_diff_chg = identical_Cs_vacs[0].copy()
        vac_diff_chg.set_charge(3.)
        self.assertTrue(pdc.are_equal(identical_Cs_vacs[0], vac_diff_chg))
        chargecheck_pdc = PointDefectComparator(
            check_charge=True)  # switch to PDC which cares about charge state
        self.assertFalse(
            chargecheck_pdc.are_equal(identical_Cs_vacs[0], vac_diff_chg))

        # test different supercell size
        # (comparing same defect but different supercells - default is to not check for this)
        sc_agnostic_pdc = PointDefectComparator(check_primitive_cell=True)
        sc_scaled_s_struc = s_struc.copy()
        sc_scaled_s_struc.make_supercell([2, 2, 3])
        sc_scaled_I_vac_sublatt1_ps1 = PeriodicSite(
            'I',
            identical_I_vacs_sublattice1[0].site.coords,
            sc_scaled_s_struc.lattice,
            coords_are_cartesian=True)
        sc_scaled_I_vac_sublatt1_ps2 = PeriodicSite(
            'I',
            identical_I_vacs_sublattice1[1].site.coords,
            sc_scaled_s_struc.lattice,
            coords_are_cartesian=True)
        sc_scaled_I_vac_sublatt2_ps = PeriodicSite(
            'I',
            identical_I_vacs_sublattice2[1].site.coords,
            sc_scaled_s_struc.lattice,
            coords_are_cartesian=True)
        sc_scaled_I_vac_sublatt1_defect1 = Vacancy(
            sc_scaled_s_struc, sc_scaled_I_vac_sublatt1_ps1)
        sc_scaled_I_vac_sublatt1_defect2 = Vacancy(
            sc_scaled_s_struc, sc_scaled_I_vac_sublatt1_ps2)
        sc_scaled_I_vac_sublatt2_defect = Vacancy(sc_scaled_s_struc,
                                                  sc_scaled_I_vac_sublatt2_ps)

        self.assertFalse(
            pdc.are_equal(
                identical_I_vacs_sublattice1[
                    0],  # trivially same defect site but between different supercells
                sc_scaled_I_vac_sublatt1_defect1))
        self.assertTrue(
            sc_agnostic_pdc.are_equal(identical_I_vacs_sublattice1[0],
                                      sc_scaled_I_vac_sublatt1_defect1))
        self.assertFalse(
            pdc.are_equal(
                identical_I_vacs_sublattice1[
                    1],  # same coords, different lattice structure
                sc_scaled_I_vac_sublatt1_defect1))
        self.assertTrue(
            sc_agnostic_pdc.are_equal(identical_I_vacs_sublattice1[1],
                                      sc_scaled_I_vac_sublatt1_defect1))
        self.assertFalse(
            pdc.are_equal(
                identical_I_vacs_sublattice1[
                    0],  # same sublattice, different coords
                sc_scaled_I_vac_sublatt1_defect2))
        self.assertTrue(
            sc_agnostic_pdc.are_equal(identical_I_vacs_sublattice1[0],
                                      sc_scaled_I_vac_sublatt1_defect2))
        self.assertFalse(
            sc_agnostic_pdc.are_equal(
                identical_I_vacs_sublattice1[
                    0],  # different defects (wrong sublattice)
                sc_scaled_I_vac_sublatt2_defect))

        # test same structure size, but scaled lattice volume
        # (default is to not allow these to be equal, but check_lattice_scale=True allows for this)
        vol_agnostic_pdc = PointDefectComparator(check_lattice_scale=True)
        vol_scaled_s_struc = s_struc.copy()
        vol_scaled_s_struc.scale_lattice(s_struc.volume * 0.95)
        vol_scaled_I_vac_sublatt1_defect1 = Vacancy(vol_scaled_s_struc,
                                                    vol_scaled_s_struc[4])
        vol_scaled_I_vac_sublatt1_defect2 = Vacancy(vol_scaled_s_struc,
                                                    vol_scaled_s_struc[5])
        vol_scaled_I_vac_sublatt2_defect = Vacancy(vol_scaled_s_struc,
                                                   vol_scaled_s_struc[6])

        self.assertFalse(
            pdc.are_equal(
                identical_I_vacs_sublattice1[
                    0],  # trivially same defect (but vol change)
                vol_scaled_I_vac_sublatt1_defect1))
        self.assertTrue(
            vol_agnostic_pdc.are_equal(identical_I_vacs_sublattice1[0],
                                       vol_scaled_I_vac_sublatt1_defect1))
        self.assertFalse(
            pdc.are_equal(
                identical_I_vacs_sublattice1[
                    0],  # same defect, different sublattice point (and vol change)
                vol_scaled_I_vac_sublatt1_defect2))
        self.assertTrue(
            vol_agnostic_pdc.are_equal(identical_I_vacs_sublattice1[0],
                                       vol_scaled_I_vac_sublatt1_defect2))
        self.assertFalse(
            vol_agnostic_pdc.are_equal(
                identical_I_vacs_sublattice1[
                    0],  # different defect (wrong sublattice)
                vol_scaled_I_vac_sublatt2_defect))

        # test identical defect which has had entire lattice shifted
        shift_s_struc = s_struc.copy()
        shift_s_struc.translate_sites(range(len(s_struc)), [0.2, 0.3, 0.4],
                                      frac_coords=True,
                                      to_unit_cell=True)
        shifted_identical_Cs_vacs = [
            Vacancy(shift_s_struc, shift_s_struc[0]),
            Vacancy(shift_s_struc, shift_s_struc[1])
        ]
        self.assertTrue(
            pdc.are_equal(
                identical_Cs_vacs[0],  # trivially same defect (but shifted)
                shifted_identical_Cs_vacs[0]))
        self.assertTrue(
            pdc.are_equal(
                identical_Cs_vacs[
                    0],  # same defect on different sublattice point (and shifted)
                shifted_identical_Cs_vacs[1]))

        # test uniform lattice shift within non-symmorphic structure
        shift_ns_struc = ns_struc.copy()
        shift_ns_struc.translate_sites(range(len(ns_struc)), [0., 0.6, 0.3],
                                       frac_coords=True,
                                       to_unit_cell=True)

        shift_ns_inter_H_sublattice1_set1 = PeriodicSite(
            'H', ns_inter_H_sublattice1_set1.frac_coords + [0., 0.6, 0.3],
            shift_ns_struc.lattice)
        shift_ns_inter_H_sublattice1_set2 = PeriodicSite(
            'H', ns_inter_H_sublattice1_set2.frac_coords + [0., 0.6, 0.3],
            shift_ns_struc.lattice)
        self.assertTrue(
            pdc.are_equal(
                Interstitial(ns_struc, ns_inter_H_sublattice1_set1
                             ),  # trivially same defect (but shifted)
                Interstitial(shift_ns_struc,
                             shift_ns_inter_H_sublattice1_set1)))
        self.assertTrue(
            pdc.are_equal(
                Interstitial(ns_struc, ns_inter_H_sublattice1_set1),
                # same defect on different sublattice point (and shifted)
                Interstitial(shift_ns_struc,
                             shift_ns_inter_H_sublattice1_set2)))

        # test a rotational + supercell type structure transformation (requires check_primitive_cell=True)
        rotated_s_struc = s_struc.copy()
        rotated_s_struc.make_supercell([[2, 1, 0], [-1, 3, 0], [0, 0, 2]])
        rotated_identical_Cs_vacs = [
            Vacancy(rotated_s_struc, rotated_s_struc[0]),
            Vacancy(rotated_s_struc, rotated_s_struc[1])
        ]
        self.assertFalse(
            pdc.are_equal(
                identical_Cs_vacs[0],  # trivially same defect (but rotated)
                rotated_identical_Cs_vacs[0]))
        self.assertTrue(
            sc_agnostic_pdc.are_equal(identical_Cs_vacs[0],
                                      rotated_identical_Cs_vacs[0]))
        self.assertFalse(
            pdc.are_equal(
                identical_Cs_vacs[
                    0],  # same defect on different sublattice (and rotated)
                rotated_identical_Cs_vacs[1]))
        self.assertTrue(
            sc_agnostic_pdc.are_equal(
                identical_Cs_vacs[
                    0],  # same defect on different sublattice point (and rotated)
                rotated_identical_Cs_vacs[1]))

        # test a rotational + supercell + shift type structure transformation for non-symmorphic structure
        rotANDshift_ns_struc = ns_struc.copy()
        rotANDshift_ns_struc.translate_sites(range(len(ns_struc)),
                                             [0., 0.6, 0.3],
                                             frac_coords=True,
                                             to_unit_cell=True)
        rotANDshift_ns_struc.make_supercell([[2, 1, 0], [-1, 3, 0], [0, 0, 2]])
        ns_vac_Cs_set1 = Vacancy(ns_struc, ns_struc[0])
        rotANDshift_ns_vac_Cs_set1 = Vacancy(rotANDshift_ns_struc,
                                             rotANDshift_ns_struc[0])
        rotANDshift_ns_vac_Cs_set2 = Vacancy(rotANDshift_ns_struc,
                                             rotANDshift_ns_struc[1])

        self.assertTrue(
            sc_agnostic_pdc.are_equal(
                ns_vac_Cs_set1,  # trivially same defect (but rotated and sublattice shifted)
                rotANDshift_ns_vac_Cs_set1))
        self.assertTrue(
            sc_agnostic_pdc.are_equal(
                ns_vac_Cs_set1,  # same defect on different sublattice point (shifted and rotated)
                rotANDshift_ns_vac_Cs_set2))
    def test_check_final_relaxed_structure_delocalized(self):
        # test structure delocalization analysis
        # first test no movement in atoms
        initial_defect_structure = self.vac.generate_defect_structure()
        final_defect_structure = initial_defect_structure.copy()
        sampling_radius = 4.55
        defect_frac_sc_coords = self.vac.site.frac_coords[:]

        params = {
            "initial_defect_structure": initial_defect_structure,
            "final_defect_structure": final_defect_structure,
            "sampling_radius": sampling_radius,
            "defect_frac_sc_coords": defect_frac_sc_coords,
            "is_compatible": True,
        }
        dentry = DefectEntry(self.vac, 0.0, corrections={}, parameters=params, entry_id=None)

        dc = DefectCompatibility(tot_relax_tol=0.1, perc_relax_tol=0.1, defect_tot_relax_tol=0.1)
        dentry = dc.check_final_relaxed_structure_delocalized(dentry)

        struc_delocal = dentry.parameters["delocalization_meta"]["structure_relax"]
        self.assertTrue(dentry.parameters["is_compatible"])
        self.assertTrue(struc_delocal["is_compatible"])
        self.assertTrue(struc_delocal["metadata"]["structure_tot_relax_compatible"])
        self.assertEqual(struc_delocal["metadata"]["tot_relax_outside_rad"], 0.0)
        self.assertTrue(struc_delocal["metadata"]["structure_perc_relax_compatible"])
        self.assertEqual(struc_delocal["metadata"]["perc_relax_outside_rad"], 0.0)
        self.assertEqual(
            len(struc_delocal["metadata"]["full_structure_relax_data"]),
            len(initial_defect_structure),
        )
        self.assertIsNone(struc_delocal["metadata"]["defect_index"])

        defect_delocal = dentry.parameters["delocalization_meta"]["defectsite_relax"]
        self.assertTrue(defect_delocal["is_compatible"])
        self.assertIsNone(defect_delocal["metadata"]["relax_amount"])

        # next test for when structure has delocalized outside of radius from defect
        pert_struct_fin_struct = initial_defect_structure.copy()
        pert_struct_fin_struct.perturb(0.1)
        dentry.parameters.update({"final_defect_structure": pert_struct_fin_struct})
        dentry = dc.check_final_relaxed_structure_delocalized(dentry)

        struc_delocal = dentry.parameters["delocalization_meta"]["structure_relax"]
        self.assertFalse(dentry.parameters["is_compatible"])
        self.assertFalse(struc_delocal["is_compatible"])
        self.assertFalse(struc_delocal["metadata"]["structure_tot_relax_compatible"])
        self.assertAlmostEqual(struc_delocal["metadata"]["tot_relax_outside_rad"], 12.5)
        self.assertFalse(struc_delocal["metadata"]["structure_perc_relax_compatible"])
        self.assertAlmostEqual(struc_delocal["metadata"]["perc_relax_outside_rad"], 77.63975155)

        # now test for when an interstitial defect has migrated too much
        inter_def_site = PeriodicSite(
            "H",
            [7.58857304, 11.70848069, 12.97817518],
            self.vac.bulk_structure.lattice,
            to_unit_cell=True,
            coords_are_cartesian=True,
        )
        inter = Interstitial(self.vac.bulk_structure, inter_def_site, charge=0)

        initial_defect_structure = inter.generate_defect_structure()
        final_defect_structure = initial_defect_structure.copy()
        poss_deflist = sorted(
            final_defect_structure.get_sites_in_sphere(inter.site.coords, 2, include_index=True),
            key=lambda x: x[1],
        )
        def_index = poss_deflist[0][2]
        final_defect_structure.translate_sites(
            indices=[def_index], vector=[0.0, 0.0, 0.008]
        )  # fractional coords translation
        defect_frac_sc_coords = inter_def_site.frac_coords[:]

        params = {
            "initial_defect_structure": initial_defect_structure,
            "final_defect_structure": final_defect_structure,
            "sampling_radius": sampling_radius,
            "defect_frac_sc_coords": defect_frac_sc_coords,
            "is_compatible": True,
        }
        dentry = DefectEntry(inter, 0.0, corrections={}, parameters=params, entry_id=None)

        dentry = dc.check_final_relaxed_structure_delocalized(dentry)

        defect_delocal = dentry.parameters["delocalization_meta"]["defectsite_relax"]
        self.assertFalse(defect_delocal["is_compatible"])
        self.assertAlmostEqual(defect_delocal["metadata"]["relax_amount"], 0.10836054)
示例#23
0
def folded_coords(site: PeriodicSite, center: GenCoords) -> GenCoords:
    _, image = site.distance_and_image_from_frac_coords(center)
    return tuple(site.frac_coords - image)
示例#24
0
    def evaluate_from_coordinates(self,
                                  structure_name,
                                  supercell,
                                  min_distance=2.0,
                                  energy_precision=6,
                                  energy_threshold=0.00,
                                  numpy_array=True):
        """Calculates the average energies/ minimum energy/ boltzmann average energies/ list of energies

        Args:
            structure_name (str): name of the structure as referenced in the cif file in the Raspa directory
            supercell (list): list of ints representing the size of each unicell vector for the supercell
            system of Raspa2 and Zeo++ (different from the one used in pymatgen)
        
        Returns:
        """
        if not os.path.exists("Coordinates"):
            raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT),
                                    "Coordinates")
        coord_path = os.path.join("Coordinates", structure_name + '.csv')
        if not os.path.exists(coord_path):
            return np.nan, np.nan, np.nan

        df_voro = pd.read_csv(coord_path, sep=" ")
        df_voro = df_voro[df_voro["dist_to_nearest"] > min_distance]
        if len(df_voro) == 0:
            return np.nan, np.nan, np.nan

        cif_file = self._load_cif_file(structure_name, self.RASPA_DIR)
        structure = CifParser.from_string(cif_file).get_structures(
            primitive=False)[0]
        structure.make_supercell(supercell)

        lattice_matrix = self._frac_to_cart_matrix(structure)
        inv_lattice_matrix = np.linalg.inv(lattice_matrix)

        df_voro["cartesian_coordinates"] = df_voro.apply(
            lambda row: np.array([float(
                row.x), float(row.y), float(row.z)]),
            axis=1)
        df_voro["fractional_coordinates"] = df_voro[
            "cartesian_coordinates"].to_numpy().dot(inv_lattice_matrix)

        accessible_mean_energy, min_energy, boltz_energy = [], [], []
        for atom_type in self.atoms:
            df_voro["pymat_site_%s" %
                    atom_type] = df_voro["fractional_coordinates"].apply(
                        lambda frac_coord: PeriodicSite(
                            {atom_type: 1}, frac_coord, structure.lattice))
            if numpy_array:
                df_voro["Energy_%s" % atom_type] = df_voro[
                    "pymat_site_%s" % atom_type].apply(
                        lambda site: self.lennard_jones_from_pymatgen_np(
                            site, structure))
            else:
                df_voro["Energy_%s" % atom_type] = df_voro[
                    "pymat_site_%s" % atom_type].apply(
                        lambda site: self.lennard_jones_from_pymatgen(
                            site, structure))
            accessible_mean_energy.append(
                round(
                    df_voro[df_voro["Energy_%s" % atom_type] <
                            energy_threshold]["Energy_%s" % atom_type].mean(),
                    energy_precision))
            min_energy.append(
                round(df_voro["Energy_%s" % atom_type].min(),
                      energy_precision))
            df_voro["exp_energy_%s" %
                    atom_type] = np.exp(-df_voro["Energy_%s" % atom_type] /
                                        (self.R * self.temperature))
            Sum_exp = df_voro["exp_energy_%s" % atom_type].sum()
            df_voro["pond_energy_%s" % atom_type] = df_voro[
                "exp_energy_%s" % atom_type] * df_voro["Energy_%s" %
                                                       atom_type] / Sum_exp
            boltz_energy.append(
                round(df_voro["pond_energy_%s" % atom_type].sum(),
                      energy_precision))
        df_voro.to_csv('Energies/%s.csv' % structure_name, index=False)
        return accessible_mean_energy, min_energy, boltz_energy
    def is_final_relaxed_structure_delocalized(self, defect_entry):
        structure_relax_analyze_meta = {}
        initial_defect_structure = defect_entry.parameters[
            'initial_defect_structure']
        final_defect_structure = defect_entry.parameters[
            'final_defect_structure']
        radius_to_sample = defect_entry.parameters['sampling_radius']

        #determine the defect index within the structure and append fractional_coordinates
        if not isinstance(defect_entry.defect, Vacancy):
            poss_deflist = sorted(initial_defect_structure.get_sites_in_sphere(
                defect_entry.defect.site.coords, 2, include_index=True),
                                  key=lambda x: x[1])
            defindex = poss_deflist[0][2]
            def_frac_coords = poss_deflist[0][0].frac_coords
        else:
            #if vacancy than create periodic site for finding distance from other atoms to defect
            defindex = None
            vac_site = PeriodicSite('H',
                                    defect_entry.defect.site.coords,
                                    initial_defect_structure.lattice,
                                    to_unit_cell=True,
                                    coords_are_cartesian=True)
            def_frac_coords = vac_site.frac_coords

        initsites = [site.frac_coords for site in initial_defect_structure]
        finalsites = [site.frac_coords for site in final_defect_structure]
        distmatrix = initial_defect_structure.lattice.get_all_distances(
            finalsites, initsites)

        #calculate distance moved as a function of the distance from the defect
        distdata = []
        totpert = 0.
        for ind in range(len(initsites)):
            if ind == defindex:
                continue
            else:
                totpert += distmatrix[ind, ind]
                # append [distance to defect, distance traveled, index in structure]
                distance_to_defect = initial_defect_structure.lattice.get_distance_and_image(
                    def_frac_coords, initsites[ind])[0]
                distdata.append(
                    [distance_to_defect, distmatrix[ind, ind], ind])

        distdata.sort()
        tot_relax_outside_rad = 0.
        perc_relax_outside_rad = 0.
        for newind in range(len(distdata)):
            perc_relax = 100 * distdata[newind][1] / totpert if totpert else 0.
            distdata[newind].append(
                perc_relax)  # percentage contribution to total relaxation
            if distdata[newind][0] > radius_to_sample:
                tot_relax_outside_rad += distdata[newind][1]
                perc_relax_outside_rad += distdata[newind][3]

        structure_tot_relax_compatible = True if tot_relax_outside_rad <= self.tot_relax_tol else False
        structure_perc_relax_compatible = True if perc_relax_outside_rad <= self.perc_relax_tol else False
        structure_relax_analyze_meta.update({
            'structure_tot_relax_compatible': structure_tot_relax_compatible,
            'tot_relax_outside_rad': tot_relax_outside_rad,
            'tot_relax_tol': self.tot_relax_tol,
            'structure_perc_relax_compatible': structure_perc_relax_compatible,
            'perc_relax_outside_rad': perc_relax_outside_rad,
            'perc_relax_tol': self.perc_relax_tol,
            'full_structure_relax_data': distdata,
            'defect_index': defindex
        })

        structure_relax_allows_compatible = True if (
            structure_tot_relax_compatible
            and structure_perc_relax_compatible) else False

        #NEXT: do single defect delocalization analysis (requires similar data, so might as well run in tandem
        # with structural delocalization
        defectsite_relax_analyze_meta = {}
        if isinstance(defect_entry.defect, Vacancy):
            defectsite_relax_allows_compatible = True
            defectsite_relax_analyze_meta.update({
                'relax_amount':
                None,
                'defect_tot_relax_tol':
                self.defect_tot_relax_tol
            })
        else:
            defect_relax_amount = distmatrix[defindex, defindex]
            defectsite_relax_allows_compatible = True if defect_relax_amount <= self.defect_tot_relax_tol else False
            defectsite_relax_analyze_meta.update({
                'relax_amount':
                defect_relax_amount,
                'defect_tot_relax_tol':
                self.defect_tot_relax_tol
            })

        if 'delocalization_meta' not in defect_entry.parameters.keys():
            defect_entry.parameters['delocalization_meta'] = {}
        defect_entry.parameters['delocalization_meta'].update({
            'defectsite_relax': {
                'is_compatible': defectsite_relax_allows_compatible,
                'metadata': defectsite_relax_analyze_meta
            }
        })
        defect_entry.parameters['delocalization_meta'].update({
            'structure_relax': {
                'is_compatible': structure_relax_allows_compatible,
                'metadata': structure_relax_analyze_meta
            }
        })

        if (not structure_relax_allows_compatible) or (
                not defectsite_relax_allows_compatible):
            defect_entry.parameters.update({'is_compatible': False})

        return defect_entry
    def parse_defect_calculations(self):
        """
        Parses the defect calculations as DefectEntry objects,
        from a PyCDT root_fldr file structure.
        Charge correction is missing in the first run.
        """
        logger = logging.getLogger(__name__)
        parsed_defects = []
        subfolders = glob.glob(os.path.join(self._root_fldr, "vac_*"))
        subfolders += glob.glob(os.path.join(self._root_fldr, "as_*"))
        subfolders += glob.glob(os.path.join(self._root_fldr, "sub_*"))
        subfolders += glob.glob(os.path.join(self._root_fldr, "inter_*"))

        def get_vr_and_check_locpot(fldr):
            vr_file = os.path.join(fldr,"vasprun.xml")
            if not os.path.exists(vr_file):
                logger.warning("{} doesn't exit".format(vr_file))
                error_msg = ": Failure, vasprun.xml doesn't exist."
                return (None, error_msg) #Further processing is not useful

            try:
                vr = Vasprun(vr_file, parse_potcar_file=False)
            except:
                logger.warning("Couldn't parse {}".format(vr_file))
                error_msg = ": Failure, couldn't parse vaprun.xml file."
                return (None, error_msg)

            if not vr.converged:
                logger.warning(
                    "Vasp calculation at {} not converged".format(fldr))
                error_msg = ": Failure, Vasp calculation not converged."
                return (None, error_msg) # Further processing is not useful

            # Check if locpot exists
            locpot_file = os.path.join(fldr, "LOCPOT")
            if not os.path.exists(locpot_file):
                logger.warning("{} doesn't exit".format(locpot_file))
                error_msg = ": Failure, LOCPOT doesn't exist"
                return (None, error_msg) #Further processing is not useful

            return (vr, None)

        def get_encut_from_potcar(fldr):
            potcar_file = os.path.join(fldr,"POTCAR")
            if not os.path.exists(potcar_file):
                logger.warning("Not POTCAR in {} to parse ENCUT".format(fldr))
                error_msg = ": Failure, No POTCAR file."
                return (None, error_msg) #Further processing is not useful

            try:
                potcar = Potcar.from_file(potcar_file)
            except:
                logger.warning("Couldn't parse {}".format(potcar_file))
                error_msg = ": Failure, couldn't read POTCAR file."
                return (None, error_msg)

            encut = max(ptcr_sngl.enmax for ptcr_sngl in potcar)
            return (encut, None)

        # get bulk entry information first
        fldr = os.path.join(self._root_fldr, "bulk")
        vr, error_msg = get_vr_and_check_locpot(fldr)
        if error_msg:
            logger.error("Abandoning parsing of the calculations")
            return {}
        bulk_energy = vr.final_energy
        bulk_sc_struct = vr.final_structure
        try:
            encut = vr.incar["ENCUT"]
        except:  # ENCUT not specified in INCAR. Read from POTCAR
            encut, error_msg = get_encut_from_potcar(fldr)
            if error_msg:
                logger.error("Abandoning parsing of the calculations")
                return {}

        trans_dict = loadfn(
            os.path.join(fldr, "transformation.json"),
            cls=MontyDecoder)
        supercell_size = trans_dict["supercell"]

        bulk_file_path = fldr
        bulk_entry = ComputedStructureEntry(
            bulk_sc_struct, bulk_energy,
            data={"bulk_path": bulk_file_path,
                  "encut": encut,
                  "supercell_size": supercell_size})

        # get defect entry information
        for fldr in subfolders:
            fldr_name = os.path.split(fldr)[1]
            chrg_fldrs = glob.glob(os.path.join(fldr,"charge*"))
            for chrg_fldr in chrg_fldrs:
                trans_dict = loadfn(
                        os.path.join(chrg_fldr, "transformation.json"),
                        cls=MontyDecoder)
                chrg = trans_dict["charge"]
                vr, error_msg = get_vr_and_check_locpot(chrg_fldr)
                if error_msg:
                    logger.warning("Parsing the rest of the calculations")
                    continue
                if "substitution_specie" in trans_dict and \
                        trans_dict["substitution_specie"] not in bulk_sc_struct.symbol_set:
                    self._substitution_species.add(
                            trans_dict["substitution_specie"])
                elif "inter" in trans_dict["defect_type"] and \
                        trans_dict["defect_site"].specie.symbol not in bulk_sc_struct.symbol_set:
                    # added because extrinsic interstitials don't have
                    # "substitution_specie" character...
                    trans_dict["substitution_specie"] = trans_dict["defect_site"].specie.symbol
                    self._substitution_species.add(
                            trans_dict["defect_site"].specie.symbol)

                defect_type = trans_dict.get("defect_type", None)
                energy = vr.final_energy
                try:
                    encut = vr.incar["ENCUT"]
                except: # ENCUT not specified in INCAR. Read from POTCAR
                    encut, error_msg = get_encut_from_potcar(chrg_fldr)
                    if error_msg:
                        logger.warning("Not able to determine ENCUT "
                                       "in {}".format(fldr_name))
                        logger.warning("Parsing the rest of the "
                                       "calculations")
                        continue

                comp_data = {"bulk_path": bulk_file_path,
                             "defect_path": chrg_fldr, "encut": encut,
                             "fldr_name": fldr_name, "supercell_size": supercell_size}
                if "substitution_specie" in trans_dict:
                    comp_data["substitution_specie"] = \
                            trans_dict["substitution_specie"]

                # create Defect Object as dict, then load to DefectEntry object
                defect_dict = {"structure": bulk_sc_struct, "charge": chrg,
                               "@module": "pymatgen.analysis.defects.core"
                               }
                defect_site = trans_dict["defect_supercell_site"]
                if "vac_" in defect_type:
                    defect_dict["@class"] = "Vacancy"
                elif "as_" in defect_type or "sub_" in defect_type:
                    defect_dict["@class"] = "Substitution"
                    substitution_specie = trans_dict["substitution_specie"]
                    defect_site = PeriodicSite( substitution_specie, defect_site.frac_coords,
                                                defect_site.lattice, coords_are_cartesian=False)
                elif "int_" in defect_type:
                    defect_dict["@class"] = "Interstitial"
                else:
                    raise ValueError("defect type {} not recognized...".format(defect_type))

                defect_dict.update( {"defect_site": defect_site})
                defect = MontyDecoder().process_decoded( defect_dict)
                parsed_defects.append( DefectEntry( defect, energy - bulk_energy,
                                                    parameters=comp_data))

        try:
            parsed_defects_data = {}
            parsed_defects_data["bulk_entry"] = bulk_entry
            parsed_defects_data["defects"] = parsed_defects
            return parsed_defects_data
        except:
            return {} # Return Null dict due to failure
示例#27
0
    def get_primitive_standard_structure(self, international_monoclinic=True):
        """
        Gives a structure with a primitive cell according to certain standards
        the standards are defined in Setyawan, W., & Curtarolo, S. (2010).
        High-throughput electronic band structure calculations:
        Challenges and tools. Computational Materials Science,
        49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010

        Returns:
            The structure in a primitive standardized cell
        """
        if hasattr(self, "_conv"):
            conv = self._conv
        else:
            conv = self.get_conventional_standard_structure(
                international_monoclinic=international_monoclinic)
        lattice = self.lattice_type

        if "P" in self.spg_symbol or lattice == "hexagonal":
            return conv

        transf = self.get_conventional_to_primitive_transformation_matrix(
            international_monoclinic=international_monoclinic)

        new_sites = []
        latt = Lattice(np.dot(transf, conv.lattice.matrix))
        for s in conv:
            new_s = PeriodicSite(s.specie,
                                 s.coords,
                                 latt,
                                 to_unit_cell=True,
                                 coords_are_cartesian=True,
                                 properties=s.properties)
            if not any(map(new_s.is_periodic_image, new_sites)):
                new_sites.append(new_s)

        if lattice == "rhombohedral":
            prim = Structure.from_sites(new_sites)
            lengths = prim.lattice.lengths
            angles = prim.lattice.angles
            a = lengths[0]
            alpha = math.pi * angles[0] / 180
            new_matrix = [[a * cos(alpha / 2), -a * sin(alpha / 2), 0],
                          [a * cos(alpha / 2), a * sin(alpha / 2), 0],
                          [
                              a * cos(alpha) / cos(alpha / 2), 0,
                              a * math.sqrt(1 - (cos(alpha)**2 /
                                                 (cos(alpha / 2)**2)))
                          ]]
            new_sites = []
            latt = Lattice(new_matrix)
            for s in prim:
                new_s = PeriodicSite(s.specie,
                                     s.frac_coords,
                                     latt,
                                     to_unit_cell=True,
                                     properties=s.properties)
                if not any(map(new_s.is_periodic_image, new_sites)):
                    new_sites.append(new_s)
            return Structure.from_sites(new_sites)

        return Structure.from_sites(new_sites)
    def run(
        self,
        maxiter=1000,
        tol=1e-5,
        gtol=1e-3,
        step_size=0.05,
        max_disp=0.05,
        spring_const=5.0,
        species=None,
    ):
        """
        Perform iterative minimization of the set of objective functions in an
        NEB-like manner. In each iteration, the total force matrix for each
        image is constructed, which comprises both the spring forces and true
        forces. For more details about the NEB approach, please see the
        references, e.g. Henkelman et al., J. Chem. Phys. 113, 9901 (2000).

        Args:
            maxiter (int): Maximum number of iterations in the minimization
                process.
            tol (float): Tolerance of the change of objective functions between
                consecutive steps.
            gtol (float): Tolerance of maximum force component (absolute value).
            step_size (float): Step size associated with the displacement of
                the atoms during the minimization process.
            max_disp (float): Maximum allowed atomic displacement in each
                iteration.
            spring_const (float): A virtual spring constant used in the NEB-like
                        relaxation process that yields so-called IDPP path.
            species (list of string): If provided, only those given species are
                allowed to move. The atomic positions of other species are
                obtained via regular linear interpolation approach.

        Returns:
            [Structure] Complete IDPP path (including end-point structures)
        """

        coords = self.init_coords.copy()
        old_funcs = np.zeros((self.nimages,), dtype=np.float64)
        idpp_structures = [self.structures[0]]

        if species is None:
            indices = list(range(len(self.structures[0])))
        else:
            species = [get_el_sp(sp) for sp in species]
            indices = [
                i for i, site in enumerate(self.structures[0]) if site.specie in species
            ]

            if len(indices) == 0:
                raise ValueError("The given species are not in the system!")

        # Iterative minimization
        for n in range(maxiter):
            # Get the sets of objective functions, true and total force
            # matrices.
            funcs, true_forces = self._get_funcs_and_forces(coords)
            tot_forces = self._get_total_forces(
                coords, true_forces, spring_const=spring_const
            )

            # Each atom is allowed to move up to max_disp
            disp_mat = step_size * tot_forces[:, indices, :]
            disp_mat = np.where(
                np.abs(disp_mat) > max_disp, np.sign(disp_mat) * max_disp, disp_mat
            )
            coords[1 : (self.nimages + 1), indices] += disp_mat

            max_force = np.abs(tot_forces[:, indices, :]).max()
            tot_res = np.sum(np.abs(old_funcs - funcs))

            if tot_res < tol and max_force < gtol:
                break

            old_funcs = funcs

        else:
            warnings.warn(
                "Maximum iteration number is reached without convergence!", UserWarning
            )

        for ni in range(self.nimages):
            # generate the improved image structure
            new_sites = []

            for site, cart_coords in zip(self.structures[ni + 1], coords[ni + 1]):
                new_site = PeriodicSite(
                    site.species,
                    coords=cart_coords,
                    lattice=site.lattice,
                    coords_are_cartesian=True,
                    properties=site.properties,
                )
                new_sites.append(new_site)

            idpp_structures.append(Structure.from_sites(new_sites))

        # Also include end-point structure.
        idpp_structures.append(self.structures[-1])

        return idpp_structures
示例#29
0
def get_all_neighbors_and_image(structure, r, include_index=False):
    """
    Modified from `pymatgen 
    <http://pymatgen.org/_modules/pymatgen/core/structure.html#IStructure.get_all_neighbors>`_ 
    to return image (used for mapping to supercell), and to use the f2py wrapped
    OpenMP dist subroutine to get the distances (smaller memory footprint and faster
    than numpy).

    Get neighbours for each atom in the unit cell, out to a distance r
    Returns a list of list of neighbors for each site in structure.
    Use this method if you are planning on looping over all sites in the
    crystal. If you only want neighbors for a particular site, use the
    method get_neighbors as it may not have to build such a large supercell
    However if you are looping over all sites in the crystal, this method
    is more efficient since it only performs one pass over a large enough
    supercell to contain all possible atoms out to a distance r.
    The return type is a [(site, dist) ...] since most of the time,
    subsequent processing requires the distance.

    
    Args:
        - r (float): Radius of sphere.
        - include_index (bool): Whether to include the non-supercell site
        - in the returned data

    Returns:
        - A list of a list of nearest neighbors for each site, i.e., 
        [[(site, dist, index, image) ...], ..]. Index only supplied if include_index = True.
        The index is the index of the site in the original (non-supercell)
        structure. This is needed for ewaldmatrix by keeping track of which
        sites contribute to the ewald sum.


    """
    recp_len = np.array(structure.lattice.reciprocal_lattice.abc)
    maxr = np.ceil((r + 0.15) * recp_len / (2 * math.pi))
    nmin = np.floor(np.min(structure.frac_coords, axis=0)) - maxr
    nmax = np.ceil(np.max(structure.frac_coords, axis=0)) + maxr

    all_ranges = [np.arange(x, y) for x, y in zip(nmin, nmax)]

    latt = structure._lattice
    neighbors = [list() for i in range(len(structure._sites))]
    all_fcoords = np.mod(structure.frac_coords, 1)
    coords_in_cell = latt.get_cartesian_coords(all_fcoords)
    site_coords = structure.cart_coords

    indices = np.arange(len(structure))
    for image in itertools.product(*all_ranges):
        coords = latt.get_cartesian_coords(image) + coords_in_cell
        all_dists = dist.dist(coords, site_coords, len(coords))
        all_within_r = np.bitwise_and(all_dists <= r, all_dists > 1e-8)

        for (j, d, within_r) in zip(indices, all_dists, all_within_r):
            nnsite = PeriodicSite(
                structure[j].specie,
                coords[j],
                latt,
                properties=structure[j].properties,
                coords_are_cartesian=True,
            )
            for i in indices[within_r]:
                item = (nnsite, d[i], j, image) if include_index else (nnsite,
                                                                       d[i])
                neighbors[i].append(item)
    return neighbors
示例#30
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
示例#31
0
def get_start_end_structures(
    isite: PeriodicSite,
    esite: PeriodicSite,
    base_struct: Structure,
    sc_mat: List[List[Union[int, float]]],
    vac_mode: bool,
    debug: bool = False,
) -> Tuple[Structure, Structure, Structure]:
    """
    Obtain the starting and terminating structures in a supercell for NEB calculations.

    Args:
        hop: object presenting the migration event
        base_struct: unit cell representation of the structure
        sc_mat: supercell transformation to create the simulation cell for the NEB calc

    Returns:
        initial structure, final structure, empty structure all in the supercell
    """
    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)

    base_sc = base_struct.copy() * sc_mat

    start_struct = base_struct.copy() * sc_mat
    end_struct = base_struct.copy() * sc_mat

    sc_mat_inv = np.linalg.inv(sc_mat)

    if not vac_mode:
        # insertion the endpoints
        start_struct.insert(
            0,
            esite.species_string,
            np.dot(isite.frac_coords, sc_mat_inv),
            properties={"magmom": 0},
        )
        end_struct.insert(
            0,
            esite.species_string,
            np.dot(esite.frac_coords, sc_mat_inv),
            properties={"magmom": 0},
        )
    else:
        # remove the other endpoint
        ipos_sc = np.dot(isite.frac_coords, sc_mat_inv)
        epos_sc = np.dot(esite.frac_coords, sc_mat_inv)
        if debug:
            icart = base_sc.lattice.get_cartesian_coords(ipos_sc)
            ecart = base_sc.lattice.get_cartesian_coords(epos_sc)
            assert abs(
                np.linalg.norm(icart - ecart) -
                np.linalg.norm(isite.coords - esite.coords)) < 1e-5
        i_ref_ = PeriodicSite(species=esite.species_string,
                              coords=ipos_sc,
                              lattice=base_sc.lattice)
        e_ref_ = PeriodicSite(species=esite.species_string,
                              coords=epos_sc,
                              lattice=base_sc.lattice)
        start_struct = remove_site_at_pos(start_struct, e_ref_)
        end_struct = remove_site_at_pos(end_struct, i_ref_)
    return start_struct, end_struct, base_sc