예제 #1
0
    def _get_valences(self):
        """
        Computes ionic valences of elements for all sites in the structure.
        """
        try:
            bv = BVAnalyzer()
            self._structure = bv.get_oxi_state_decorated_structure(
                self._structure)
            valences = bv.get_valences(self._structure)
        except:
            try:
                bv = BVAnalyzer(symm_tol=0.0)
                self._structure = bv.get_oxi_state_decorated_structure(
                    self._structure)
                valences = bv.get_valences(self._structure)
            except:
                valences = [0] * self._structure.num_sites
                #raise

        #el = [site.specie.symbol for site in self._structure.sites]
        #el = [site.species_string for site in self._structure.sites]
        #el = [site.specie for site in self._structure.sites]
        #valence_dict = dict(zip(el, valences))
        #print valence_dict
        return valences
예제 #2
0
class BVAnalyzerTest(PymatgenTest):

    def setUp(self):
        self.analyzer = BVAnalyzer()

    def test_get_valence(self):
        s = Structure.from_file(os.path.join(test_dir, "LiMn2O4.json"))
        ans = [1, 1, 3, 3, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2]
        self.assertEqual(self.analyzer.get_valences(s), ans)
        s = self.get_structure("LiFePO4")
        ans = [1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2, -2, -2, -2,
               - 2, -2, -2, -2, -2, -2, -2, -2, -2]
        self.assertEqual(self.analyzer.get_valences(s), ans)
        s = self.get_structure("Li3V2(PO4)3")
        ans = [1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 5, 5, 5, 5, 5, 5, -2, -2, -2, -2,
               - 2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
               - 2, -2, -2, -2]
        self.assertEqual(self.analyzer.get_valences(s), ans)
        s = Structure.from_file(os.path.join(test_dir, "Li4Fe3Mn1(PO4)4.json"))
        ans = [1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2, -2, -2, -2,
               - 2, -2, -2, -2, -2, -2, -2, -2, -2]
        self.assertEqual(self.analyzer.get_valences(s), ans)
        s = self.get_structure("NaFePO4")
        ans = [1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2, -2, -2, -2,
               - 2, -2, -2, -2, -2, -2, -2, -2, -2]
        self.assertEqual(self.analyzer.get_valences(s), ans)

    def test_get_oxi_state_structure(self):
        s = Structure.from_file(os.path.join(test_dir, "LiMn2O4.json"))
        news = self.analyzer.get_oxi_state_decorated_structure(s)
        self.assertIn(Specie("Mn", 3), news.composition.elements)
        self.assertIn(Specie("Mn", 4), news.composition.elements)
예제 #3
0
    def from_py_struct(structure: pymatgen.core.Structure):
        """Create a SmactStructure from a pymatgen Structure object.

        Args:
            structure: A pymatgen Structure.

        Returns:
            :class:`~.SmactStructure`

        """
        if not isinstance(structure, pymatgen.core.Structure):
            raise TypeError("Structure must be a pymatgen.core.Structure instance.")

        bva = BVAnalyzer()
        struct = bva.get_oxi_state_decorated_structure(structure)

        sites, species = SmactStructure.__parse_py_sites(struct)

        lattice_mat = struct.lattice.matrix

        lattice_param = 1.0

        return SmactStructure(
          species,
          lattice_mat,
          sites,
          lattice_param,
          sanitise_species=True, )
예제 #4
0
파일: creator.py 프로젝트: ZhewenSong/USIT
def get_basic_analysis_and_error_checks(d):
    initial_vol = d["input"]["crystal"]["lattice"]["volume"]
    final_vol = d["output"]["crystal"]["lattice"]["volume"]
    delta_vol = final_vol - initial_vol
    percent_delta_vol = delta_vol / initial_vol
    coord_num = get_coordination_numbers(d)
    calc = d["calculations"][-1]
    gap = calc["output"]["bandgap"]
    cbm = calc["output"]["cbm"]
    vbm = calc["output"]["vbm"]
    is_direct = calc["output"]["is_gap_direct"]

    if abs(percent_delta_vol) > 0.20:
        warning_msgs = ["Volume change > 20%"]
    else:
        warning_msgs = []

    bv_struct = Structure.from_dict(d["output"]["crystal"])
    try:
        bva = BVAnalyzer()
        bv_struct = bva.get_oxi_state_decorated_structure(bv_struct)
    except ValueError as e:
        logger.error("Valence cannot be determined due to {e}."
                     .format(e=e))
    except Exception as ex:
        logger.error("BVAnalyzer error {e}.".format(e=str(ex)))

    return {"delta_volume": delta_vol,
            "percent_delta_volume": percent_delta_vol,
            "warnings": warning_msgs, "coordination_numbers": coord_num,
            "bandgap": gap, "cbm": cbm, "vbm": vbm,
            "is_gap_direct": is_direct,
            "bv_structure": bv_struct.to_dict}
예제 #5
0
    def predict(self, structure, ref_structure, test_isostructural=True):
        """
        Given a structure, returns back the predicted volume.

        Args:
            structure (Structure): structure w/unknown volume
            ref_structure (Structure): A reference structure with a similar
                structure but different species.
            test_isostructural (bool): Whether to test that the two
                structures are isostructural. This algo works best for
                isostructural compounds. Defaults to True.

        Returns:
            a float value of the predicted volume
        """
        if not is_ox(structure):
            a = BVAnalyzer()
            structure = a.get_oxi_state_decorated_structure(structure)
        if not is_ox(ref_structure):
            a = BVAnalyzer()
            ref_structure = a.get_oxi_state_decorated_structure(ref_structure)

        if test_isostructural:
            m = StructureMatcher()
            mapping = m.get_best_electronegativity_anonymous_mapping(
                structure, ref_structure)
            if mapping is None:
                raise ValueError("Input structures do not match!")

        comp = structure.composition
        ref_comp = ref_structure.composition

        numerator = 0
        denominator = 0

        # Here, the 1/3 factor on the composition accounts for atomic
        # packing. We want the number per unit length.

        # TODO: AJ doesn't understand the (1/3). It would make sense to him
        # if you were doing atomic volume and not atomic radius
        for k, v in comp.items():
            numerator += k.ionic_radius * v**(1 / 3)
        for k, v in ref_comp.items():
            denominator += k.ionic_radius * v**(1 / 3)

        # The scaling factor is based on lengths. We apply a power of 3.
        return ref_structure.volume * (numerator / denominator)**3
예제 #6
0
def add_bv_structure(doc):
    struc = Structure.from_dict(doc["structure"])
    try:
        bva = BVAnalyzer()
        bv_struct = bva.get_oxi_state_decorated_structure(struc)
        doc["bv_structure"] = bv_struct.as_dict()
    except Exception as e:
        print("BVAnalyzer error: {}".format(e))
예제 #7
0
    def predict(self, structure, ref_structure, test_isostructural=True):
        """
        Given a structure, returns back the predicted volume.

        Args:
            structure (Structure): structure w/unknown volume
            ref_structure (Structure): A reference structure with a similar
                structure but different species.
            test_isostructural (bool): Whether to test that the two
                structures are isostructural. This algo works best for
                isostructural compounds. Defaults to True.

        Returns:
            a float value of the predicted volume
        """
        if not is_ox(structure):
            a = BVAnalyzer()
            structure = a.get_oxi_state_decorated_structure(structure)
        if not is_ox(ref_structure):
            a = BVAnalyzer()
            ref_structure = a.get_oxi_state_decorated_structure(ref_structure)

        if test_isostructural:
            m = StructureMatcher()
            mapping = m.get_best_electronegativity_anonymous_mapping(structure, ref_structure)
            if mapping is None:
                raise ValueError("Input structures do not match!")

        comp = structure.composition
        ref_comp = ref_structure.composition

        numerator = 0
        denominator = 0

        # Here, the 1/3 factor on the composition accounts for atomic
        # packing. We want the number per unit length.

        # TODO: AJ doesn't understand the (1/3). It would make sense to him
        # if you were doing atomic volume and not atomic radius
        for k, v in comp.items():
            numerator += k.ionic_radius * v ** (1 / 3)
        for k, v in ref_comp.items():
            denominator += k.ionic_radius * v ** (1 / 3)

        # The scaling factor is based on lengths. We apply a power of 3.
        return ref_structure.volume * (numerator / denominator) ** 3
예제 #8
0
def get_basic_analysis_and_error_checks(d, max_force_threshold=0.5,
                                        volume_change_threshold=0.2):

    initial_vol = d["input"]["crystal"]["lattice"]["volume"]
    final_vol = d["output"]["crystal"]["lattice"]["volume"]
    delta_vol = final_vol - initial_vol
    percent_delta_vol = delta_vol / initial_vol
    coord_num = get_coordination_numbers(d)
    calc = d["calculations"][-1]
    gap = calc["output"]["bandgap"]
    cbm = calc["output"]["cbm"]
    vbm = calc["output"]["vbm"]
    is_direct = calc["output"]["is_gap_direct"]

    warning_msgs = []
    error_msgs = []

    if abs(percent_delta_vol) > volume_change_threshold:
        warning_msgs.append("Volume change > {}%"
                            .format(volume_change_threshold * 100))

    bv_struct = Structure.from_dict(d["output"]["crystal"])
    try:
        bva = BVAnalyzer()
        bv_struct = bva.get_oxi_state_decorated_structure(bv_struct)
    except ValueError as e:
        logger.error("Valence cannot be determined due to {e}."
                     .format(e=e))
    except Exception as ex:
        logger.error("BVAnalyzer error {e}.".format(e=str(ex)))

    max_force = None
    if d["state"] == "successful" and \
            d["calculations"][0]["input"]["parameters"].get("NSW", 0) > 0:
        # handle the max force and max force error
        max_force = max([np.linalg.norm(a)
                        for a in d["calculations"][-1]["output"]
                        ["ionic_steps"][-1]["forces"]])

        if max_force > max_force_threshold:
            error_msgs.append("Final max force exceeds {} eV"
                              .format(max_force_threshold))
            d["state"] = "error"

        s = Structure.from_dict(d["output"]["crystal"])
        if not s.is_valid():
            error_msgs.append("Bad structure (atoms are too close!)")
            d["state"] = "error"

    return {"delta_volume": delta_vol,
            "max_force": max_force,
            "percent_delta_volume": percent_delta_vol,
            "warnings": warning_msgs,
            "errors": error_msgs,
            "coordination_numbers": coord_num,
            "bandgap": gap, "cbm": cbm, "vbm": vbm,
            "is_gap_direct": is_direct,
            "bv_structure": bv_struct.as_dict()}
예제 #9
0
class AutoOxiStateDecorationTransformation(AbstractTransformation):
    """
    This transformation automatically decorates a structure with oxidation
    states using a bond valence approach.
    """

    def __init__(
        self,
        symm_tol=0.1,
        max_radius=4,
        max_permutations=100000,
        distance_scale_factor=1.015,
    ):
        """
        Args:
            symm_tol (float): Symmetry tolerance used to determine which sites are
                symmetrically equivalent. Set to 0 to turn off symmetry.
            max_radius (float): Maximum radius in Angstrom used to find nearest
                neighbors.
            max_permutations (int): Maximum number of permutations of oxidation
                states to test.
            distance_scale_factor (float): A scale factor to be applied. This is
                useful for scaling distances, esp in the case of
                calculation-relaxed structures, which may tend to under (GGA) or
                over bind (LDA). The default of 1.015 works for GGA. For
                experimental structure, set this to 1.
        """
        self.symm_tol = symm_tol
        self.max_radius = max_radius
        self.max_permutations = max_permutations
        self.distance_scale_factor = distance_scale_factor
        self.analyzer = BVAnalyzer(symm_tol, max_radius, max_permutations, distance_scale_factor)

    def apply_transformation(self, structure):
        """
        Apply the transformation.

        Args:
            structure (Structure): Input Structure

        Returns:
            Oxidation state decorated Structure.
        """
        return self.analyzer.get_oxi_state_decorated_structure(structure)

    @property
    def inverse(self):
        """
        Returns: None
        """
        return None

    @property
    def is_one_to_many(self):
        """
        Returns: False
        """
        return False
예제 #10
0
class AutoOxiStateDecorationTransformation(AbstractTransformation):
    """
    This transformation automatically decorates a structure with oxidation
    states using a bond valence approach.
    """
    def __init__(self,
                 symm_tol=0.1,
                 max_radius=4,
                 max_permutations=100000,
                 distance_scale_factor=1.015):
        """
        Args:
            symm_tol:
                Symmetry tolerance used to determine which sites are
                symmetrically equivalent. Set to 0 to turn off symmetry.
            max_radius:
                Maximum radius in Angstrom used to find nearest neighbors.
            max_permutations:
                The maximum number of permutations of oxidation states to test.
            distance_scale_factor:
                A scale factor to be applied. This is useful for scaling
                distances, esp in the case of calculation-relaxed structures
                which may tend to under (GGA) or over bind (LDA). The default
                of 1.015 works for GGA. For experimental structure, set this to
                1.
        """
        self.analyzer = BVAnalyzer(symm_tol, max_radius, max_permutations,
                                   distance_scale_factor)

    def apply_transformation(self, structure):
        return self.analyzer.get_oxi_state_decorated_structure(structure)

    @property
    def inverse(self):
        return None

    @property
    def is_one_to_many(self):
        return False

    @property
    def to_dict(self):
        return {
            "name": self.__class__.__name__,
            "version": __version__,
            "init_args": {
                "symm_tol": self.analyzer.symm_tol,
                "max_radius": self.analyzer.max_radius,
                "max_permutations": self.analyzer.max_permutations,
                "distance_scale_factor": self.analyzer.dist_scale_factor
            },
            "@module": self.__class__.__module__,
            "@class": self.__class__.__name__
        }
예제 #11
0
class AutoOxiStateDecorationTransformation(AbstractTransformation):
    """
    This transformation automatically decorates a structure with oxidation
    states using a bond valence approach.
    """

    def __init__(self, symm_tol=0.1, max_radius=4, max_permutations=100000, distance_scale_factor=1.015):
        """
        Args:
            symm_tol:
                Symmetry tolerance used to determine which sites are
                symmetrically equivalent. Set to 0 to turn off symmetry.
            max_radius:
                Maximum radius in Angstrom used to find nearest neighbors.
            max_permutations:
                The maximum number of permutations of oxidation states to test.
            distance_scale_factor:
                A scale factor to be applied. This is useful for scaling
                distances, esp in the case of calculation-relaxed structures
                which may tend to under (GGA) or over bind (LDA). The default
                of 1.015 works for GGA. For experimental structure, set this to
                1.
        """
        self.analyzer = BVAnalyzer(symm_tol, max_radius, max_permutations, distance_scale_factor)

    def apply_transformation(self, structure):
        return self.analyzer.get_oxi_state_decorated_structure(structure)

    @property
    def inverse(self):
        return None

    @property
    def is_one_to_many(self):
        return False

    @property
    def to_dict(self):
        return {
            "name": self.__class__.__name__,
            "version": __version__,
            "init_args": {
                "symm_tol": self.analyzer.symm_tol,
                "max_radius": self.analyzer.max_radius,
                "max_permutations": self.analyzer.max_permutations,
                "distance_scale_factor": self.analyzer.dist_scale_factor,
            },
            "@module": self.__class__.__module__,
            "@class": self.__class__.__name__,
        }
예제 #12
0
    def _get_valences(self):
        """
        Computes ionic valences of elements for all sites in the structure.
        """
        try:
            bv = BVAnalyzer()
            self._structure = bv.get_oxi_state_decorated_structure(self._structure)
            valences = bv.get_valences(self._structure)
        except:
            try:
                bv = BVAnalyzer(symm_tol=0.0)
                self._structure = bv.get_oxi_state_decorated_structure(self._structure)
                valences = bv.get_valences(self._structure)
            except:
                raise

        #print valences
        #el = [site.specie.symbol for site in self._structure.sites]
        #el = [site.species_string for site in self._structure.sites]
        #el = [site.specie for site in self._structure.sites]
        #valence_dict = dict(zip(el, valences))
        #print valence_dict
        return valences
예제 #13
0
파일: structure.py 프로젝트: zhubonan/SMACT
    def from_mp(
        species: List[Union[Tuple[str, int, int], Tuple[smact.Species, int]]],
        api_key: str,
    ):
        """Create a SmactStructure using the first Materials Project entry for a composition.

        Args:
            species: See :meth:`~.__init__`.
            api_key: A www.materialsproject.org API key.

        Returns:
            :class:`~.SmactStructure`

        """
        sanit_species = SmactStructure._sanitise_species(species)

        with MPRester(api_key) as m:
            eles = SmactStructure._get_ele_stoics(sanit_species)
            formula = "".join(f"{ele}{stoic}" for ele, stoic in eles.items())
            structs = m.query(
                criteria={"reduced_cell_formula": formula},
                properties=["structure"],
            )

            if len(structs) == 0:
                raise ValueError(
                    "Could not find composition in Materials Project Database, "
                    "please supply a structure.")

            struct = structs[0][
                'structure']  # Default to first found structure

        if 0 not in (spec[1]
                     for spec in sanit_species):  # If everything's charged
            bva = BVAnalyzer()
            struct = bva.get_oxi_state_decorated_structure(struct)

        lattice_mat = struct.lattice.matrix

        lattice_param = 1.0  # TODO Use actual lattice parameter

        sites, _ = SmactStructure.__parse_py_sites(struct)

        return SmactStructure(
            sanit_species,
            lattice_mat,
            sites,
            lattice_param,
            sanitise_species=False,
        )
예제 #14
0
 def setUp(self):
     filepath = os.path.join(test_dir, 'POSCAR')
     p = Poscar.from_file(filepath)
     self.structure = p.structure
     bv = BVAnalyzer()
     self.structure = bv.get_oxi_state_decorated_structure(self.structure)
     valences = bv.get_valences(self.structure)
     radii = []
     for i in range(len(valences)):
         el = self.structure.sites[i].specie.symbol
         radius = Specie(el, valences[i]).ionic_radius
         radii.append(radius)
     el = [site.species_string for site in self.structure.sites]
     self.rad_dict = dict(zip(el, radii))
     for el in self.rad_dict.keys():
         print((el, self.rad_dict[el].real))
예제 #15
0
 def setUp(self):
     filepath = os.path.join(test_dir, 'POSCAR')
     p = Poscar.from_file(filepath)
     self.structure = p.structure
     bv = BVAnalyzer()
     self.structure = bv.get_oxi_state_decorated_structure(self.structure)
     valences = bv.get_valences(self.structure)
     radii = []
     for i in range(len(valences)):
         el = self.structure.sites[i].specie.symbol
         radius = Specie(el, valences[i]).ionic_radius
         radii.append(radius)
     el = [site.species_string for site in self.structure.sites]
     self.rad_dict = dict(zip(el, radii))
     for el in self.rad_dict.keys():
         print((el, self.rad_dict[el].real))
    def setUp(self):
        """
        Setup MgO rocksalt structure for testing Vacancy
        """
        mgo_latt = [[4.212, 0, 0], [0, 4.212, 0], [0, 0, 4.212]]
        mgo_specie = ["Mg"] * 4 + ["O"] * 4
        mgo_frac_cord = [[0, 0, 0], [0.5, 0.5, 0], [0.5, 0, 0.5], [0, 0.5, 0.5],
                         [0.5, 0, 0], [0, 0.5, 0], [0, 0, 0.5], [0.5, 0.5, 0.5]]
        self._mgo_uc = Structure(mgo_latt, mgo_specie, mgo_frac_cord, True,
                                 True)

        bv = BVAnalyzer()
        self._mgo_uc = bv.get_oxi_state_decorated_structure(self._mgo_uc)
        self._mgo_val_rad_eval = ValenceIonicRadiusEvaluator(self._mgo_uc)
        self._mgo_val = self._mgo_val_rad_eval.valences
        self._mgo_rad = self._mgo_val_rad_eval.radii
        self._mgo_vac = Vacancy(self._mgo_uc, self._mgo_val, self._mgo_rad)
예제 #17
0
    def setUp(self):
        """
        Setup MgO rocksalt structure for testing Vacancy
        """
        mgo_latt = [[4.212, 0, 0], [0, 4.212, 0], [0, 0, 4.212]]
        mgo_specie = ["Mg"] * 4 + ["O"] * 4
        mgo_frac_cord = [[0, 0, 0], [0.5, 0.5, 0], [0.5, 0, 0.5], [0, 0.5, 0.5],
                         [0.5, 0, 0], [0, 0.5, 0], [0, 0, 0.5], [0.5, 0.5, 0.5]]
        self._mgo_uc = Structure(mgo_latt, mgo_specie, mgo_frac_cord, True,
                                 True)

        bv = BVAnalyzer()
        self._mgo_uc = bv.get_oxi_state_decorated_structure(self._mgo_uc)
        self._mgo_val_rad_eval = ValenceIonicRadiusEvaluator(self._mgo_uc)
        self._mgo_val = self._mgo_val_rad_eval.valences
        self._mgo_rad = self._mgo_val_rad_eval.radii
        self._mgo_vac = Vacancy(self._mgo_uc, self._mgo_val, self._mgo_rad)
예제 #18
0
class BVAnalyzerTest(unittest.TestCase):
    def setUp(self):
        self.analyzer = BVAnalyzer()

    def test_get_valence(self):
        parser = CifParser(os.path.join(test_dir, "LiMn2O4.cif"))
        s = parser.get_structures()[0]
        ans = [1, 1, 3, 3, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2]
        self.assertEqual(self.analyzer.get_valences(s), ans)
        parser = CifParser(os.path.join(test_dir, "LiFePO4.cif"))
        s = parser.get_structures()[0]
        ans = [
            1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2, -2, -2, -2, -2,
            -2, -2, -2, -2, -2, -2, -2, -2
        ]
        self.assertEqual(self.analyzer.get_valences(s), ans)
        parser = CifParser(os.path.join(test_dir, "Li3V2(PO4)3.cif"))
        s = parser.get_structures()[0]
        ans = [
            1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 5, 5, 5, 5, 5, 5, -2, -2, -2, -2, -2,
            -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
            -2, -2
        ]
        self.assertEqual(self.analyzer.get_valences(s), ans)
        parser = CifParser(os.path.join(test_dir, "Li4Fe3Mn1(PO4)4.cif"))
        s = parser.get_structures()[0]
        ans = [
            1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2, -2, -2, -2, -2,
            -2, -2, -2, -2, -2, -2, -2, -2
        ]
        self.assertEqual(self.analyzer.get_valences(s), ans)
        parser = CifParser(os.path.join(test_dir, "NaFePO4.cif"))
        s = parser.get_structures()[0]
        ans = [
            1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2, -2, -2, -2, -2,
            -2, -2, -2, -2, -2, -2, -2, -2
        ]
        self.assertEqual(self.analyzer.get_valences(s), ans)

    def test_get_oxi_state_structure(self):
        parser = CifParser(os.path.join(test_dir, "LiMn2O4.cif"))
        s = parser.get_structures()[0]
        news = self.analyzer.get_oxi_state_decorated_structure(s)
        self.assertIn(Specie("Mn", 3), news.composition.elements)
        self.assertIn(Specie("Mn", 4), news.composition.elements)
예제 #19
0
    def get_valences(self):
        """
        Uses Pymatgen to obtain likely valence states of every element in structure

        Returns
            vals: dictionary of average valence state for every element in composition
        """

        struct = self.structure
        bv = BVAnalyzer()
        try:
            valences = bv.get_valences(struct)
            struct = bv.get_oxi_state_decorated_structure(struct)
        except:
            return None

        if isinstance(valences[0], list):
            valences = [item for sublist in valences for item in sublist]

        stoich = defaultdict(int)
        for site in struct.as_dict()['sites']:
            elem = site['species'][0]['element']
            stoich[elem] += 1

        vals = {}

        for spec in stoich.keys():
            vals[spec] = 0.0

        for atom in range(len(struct)):
            try:
                vals[struct.as_dict()['sites'][atom]['species'][0]
                     ['element']] += valences[atom]
            except Exception as e:
                print("Trouble with {}".format(struct.formula))
                print('Do you have partial occupancies?')
                return None

        for spec in vals:
            vals[spec] = vals[spec] / stoich[spec]
            vals[spec] = int(round(vals[spec]))

        return vals
예제 #20
0
    def __init__(self, structure, valences, radii):
        """
        Given a structure, generate symmetrically distinct interstitial sites.

        Args:
            structure: pymatgen.core.structure.Structure
            valences: Dictionary of oxidation states of elements in {
                El:valence} form
            radii: Radii of elemnts in the structure
        """

        bv = BVAnalyzer()
        self._structure = bv.get_oxi_state_decorated_structure(structure)
        #self._structure = structure
        self._valence_dict = valences
        self._rad_dict = radii

        #Use Zeo++ to obtain the voronoi nodes. Apply symmetry reduction and
        #the symmetry reduced voronoi nodes
        #are possible candidates for interstitial sites
        #try:
        possible_interstitial_sites = symmetry_reduced_voronoi_nodes(
                self._structure, self._rad_dict)
        #except:
        #    raise ValueError("Symmetry_reduced_voronoi_nodes failed")

        #Do futher processing on possibleInterstitialSites to obtain
        #interstitial sites
        self._defect_sites = possible_interstitial_sites
        self._defectsite_coord_no = []
        self._defect_coord_sites = []
        self._defect_coord_charge = []
        self._radii = []

        for site in self._defect_sites:
            coord_no, coord_sites, chrg = self._get_coord_no_sites_chrg(site)
            self._defectsite_coord_no.append(coord_no)
            self._defect_coord_sites.append(coord_sites)
            self._defect_coord_charge.append(chrg)

        for site in self._defect_sites:
            self._radii.append(float(site.properties['voronoi_radius']))
예제 #21
0
class AutoOxiStateDecorationTransformation(AbstractTransformation):
    """
    This transformation automatically decorates a structure with oxidation
    states using a bond valence approach.

    Args:
        symm_tol (float): Symmetry tolerance used to determine which sites are
            symmetrically equivalent. Set to 0 to turn off symmetry.
        max_radius (float): Maximum radius in Angstrom used to find nearest
            neighbors.
        max_permutations (int): Maximum number of permutations of oxidation
            states to test.
        distance_scale_factor (float): A scale factor to be applied. This is
            useful for scaling distances, esp in the case of
            calculation-relaxed structures, which may tend to under (GGA) or
            over bind (LDA). The default of 1.015 works for GGA. For
            experimental structure, set this to 1.
    """

    def __init__(self, symm_tol=0.1, max_radius=4, max_permutations=100000,
                 distance_scale_factor=1.015):
        self.symm_tol = symm_tol
        self.max_radius = max_radius
        self.max_permutations = max_permutations
        self.distance_scale_factor = distance_scale_factor
        self.analyzer = BVAnalyzer(symm_tol, max_radius, max_permutations,
                                   distance_scale_factor)

    def apply_transformation(self, structure):
        return self.analyzer.get_oxi_state_decorated_structure(structure)

    @property
    def inverse(self):
        return None

    @property
    def is_one_to_many(self):
        return False
예제 #22
0
class BVAnalyzerTest(unittest.TestCase):

    def setUp(self):
        self.analyzer = BVAnalyzer()

    def test_get_valence(self):
        parser = CifParser(os.path.join(test_dir, "LiMn2O4.cif"))
        s = parser.get_structures()[0]
        ans = [1, 1, 3, 3, 4, 4, -2, -2, -2, -2, -2, -2, -2, -2]
        self.assertEqual(self.analyzer.get_valences(s), ans)
        parser = CifParser(os.path.join(test_dir, "LiFePO4.cif"))
        s = parser.get_structures()[0]
        ans = [1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2, -2, -2, -2,
               - 2, -2, -2, -2, -2, -2, -2, -2, -2]
        self.assertEqual(self.analyzer.get_valences(s), ans)
        parser = CifParser(os.path.join(test_dir, "Li3V2(PO4)3.cif"))
        s = parser.get_structures()[0]
        ans = [1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 5, 5, 5, 5, 5, 5, -2, -2, -2, -2,
               - 2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
               - 2, -2, -2, -2]
        self.assertEqual(self.analyzer.get_valences(s), ans)
        parser = CifParser(os.path.join(test_dir, "Li4Fe3Mn1(PO4)4.cif"))
        s = parser.get_structures()[0]
        ans = [1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2, -2, -2, -2,
               - 2, -2, -2, -2, -2, -2, -2, -2, -2]
        self.assertEqual(self.analyzer.get_valences(s), ans)
        parser = CifParser(os.path.join(test_dir, "NaFePO4.cif"))
        s = parser.get_structures()[0]
        ans = [1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2, -2, -2, -2,
               - 2, -2, -2, -2, -2, -2, -2, -2, -2]
        self.assertEqual(self.analyzer.get_valences(s), ans)

    def test_get_oxi_state_structure(self):
        parser = CifParser(os.path.join(test_dir, "LiMn2O4.cif"))
        s = parser.get_structures()[0]
        news = self.analyzer.get_oxi_state_decorated_structure(s)
        self.assertIn(Specie("Mn", 3), news.composition.elements)
        self.assertIn(Specie("Mn", 4), news.composition.elements)
예제 #23
0
def get_basic_analysis_and_error_checks(d):
    initial_vol = d["input"]["crystal"]["lattice"]["volume"]
    final_vol = d["output"]["crystal"]["lattice"]["volume"]
    delta_vol = final_vol - initial_vol
    percent_delta_vol = delta_vol / initial_vol
    coord_num = get_coordination_numbers(d)
    calc = d["calculations"][-1]
    gap = calc["output"]["bandgap"]
    cbm = calc["output"]["cbm"]
    vbm = calc["output"]["vbm"]
    is_direct = calc["output"]["is_gap_direct"]

    if abs(percent_delta_vol) > 0.20:
        warning_msgs = ["Volume change > 20%"]
    else:
        warning_msgs = []

    bv_struct = Structure.from_dict(d["output"]["crystal"])
    try:
        bva = BVAnalyzer()
        bv_struct = bva.get_oxi_state_decorated_structure(bv_struct)
    except ValueError as e:
        logger.error("Valence cannot be determined due to {e}.".format(e=e))
    except Exception as ex:
        logger.error("BVAnalyzer error {e}.".format(e=str(ex)))

    return {
        "delta_volume": delta_vol,
        "percent_delta_volume": percent_delta_vol,
        "warnings": warning_msgs,
        "coordination_numbers": coord_num,
        "bandgap": gap,
        "cbm": cbm,
        "vbm": vbm,
        "is_gap_direct": is_direct,
        "bv_structure": bv_struct.to_dict
    }
def Calc_Ewald(pmg_struct, formal_val=[]):
    """
    input: pmg_struct: pymatgen structure
           formal_val: list - list of valence for each atom

    TBD: use input formal valence list to decorate the structure
    """
    # GET valence list
    if len(formal_val)==0:
        bv_analyzer=BVAnalyzer(max_radius=4) #max_radius default is 4
        formal_val=bv_analyzer.get_valences(pmg_struct)

    # default for cutoff is set to None
    real_cutoff = None
    rec_cutoff = None

    # Oxidation states are decorated automatically.  
    decorated_struct = bv_analyzer.get_oxi_state_decorated_structure(pmg_struct)
    NUM_sites=pmg_struct.num_sites
    
    # Per atom
    ewald_per_atom=1/NUM_sites*EwaldSummation(decorated_struct,
        real_space_cut=real_cutoff, recip_space_cut=rec_cutoff).total_energy
    return ewald_per_atom
예제 #25
0
    def __init__(self,
                 structure,
                 valences,
                 radii,
                 site_type='voronoi_vertex',
                 accuracy='Normal',
                 symmetry_flag=True,
                 oxi_state=False):
        """
        Given a structure, generate symmetrically distinct interstitial sites.

        Args:
            structure: pymatgen.core.structure.Structure
            valences: Dictionary of oxidation states of elements in 
                {el:valence} form
            radii: Radii of elemnts in the structure
            site_type: "voronoi_vertex" uses voronoi nodes
                "voronoi_facecenter" uses voronoi polyhedra face centers
                Default is "voronoi_vertex"
            accuracy: Flag denoting whether to use high accuracy version 
                of Zeo++. Options are "Normal" and "High". Default is normal.
            symmetry_flag: If True, only returns symmetrically distinct sites
            oxi_state: If False, input structure is considered devoid of 
                oxidation-state decoration. And oxi-state for each site is 
                determined. Use True, if input structure is oxi-state 
                decorated. This option is useful when the structure is 
                not electro-neutral after deleting/adding sites. In that
                case oxi-decorate the structure before deleting/adding the
                sites.
        """
        if not oxi_state:
            try:
                bv = BVAnalyzer()
                self._structure = bv.get_oxi_state_decorated_structure(
                    structure)
            except:
                try:
                    bv = BVAnalyzer(symm_tol=0.0)
                    self._structure = bv.get_oxi_state_decorated_structure(
                        structure)
                except:
                    raise
        else:
            self._structure = structure

        self._valence_dict = valences
        self._rad_dict = radii
        """
        Use Zeo++ to obtain the voronoi nodes. Apply symmetry reduction
        and the symmetry reduced voronoi nodes are possible candidates
        for interstitial sites.
        """
        if accuracy == "Normal":
            high_accuracy_flag = False
        elif accuracy == "High":
            high_accuracy_flag = True
        else:
            raise ValueError("Accuracy setting not understood.")

        vor_node_sites, vor_facecenter_sites = symmetry_reduced_voronoi_nodes(
            self._structure, self._rad_dict, high_accuracy_flag, symmetry_flag)

        if site_type == 'voronoi_vertex':
            possible_interstitial_sites = vor_node_sites
        elif site_type == 'voronoi_facecenter':
            possible_interstitial_sites = vor_facecenter_sites
        else:
            raise ValueError("Input site type not implemented")

        #Do futher processing on possibleInterstitialSites to obtain
        #interstitial sites
        self._defect_sites = possible_interstitial_sites
        self._defectsite_coord_no = []
        self._defect_coord_sites = []
        self._defect_coord_charge = []
        self._radii = []

        for site in self._defect_sites:
            coord_no, coord_sites, chrg = self._get_coord_no_sites_chrg(site)
            self._defectsite_coord_no.append(coord_no)
            self._defect_coord_sites.append(coord_sites)
            self._defect_coord_charge.append(chrg)

        for site in self._defect_sites:
            self._radii.append(float(site.properties['voronoi_radius']))
예제 #26
0
    def __init__(
        self,
        structure,
        valences,
        radii,
        site_type="voronoi_vertex",
        accuracy="Normal",
        symmetry_flag=True,
        oxi_state=False,
    ):
        """
        Given a structure, generate symmetrically distinct interstitial sites.

        Args:
            structure: pymatgen.core.structure.Structure
            valences: Dictionary of oxidation states of elements in 
                {el:valence} form
            radii: Radii of elemnts in the structure
            site_type: "voronoi_vertex" uses voronoi nodes
                "voronoi_facecenter" uses voronoi polyhedra face centers
                Default is "voronoi_vertex"
            accuracy: Flag denoting whether to use high accuracy version 
                of Zeo++. Options are "Normal" and "High". Default is normal.
            symmetry_flag: If True, only returns symmetrically distinct sites
            oxi_state: If False, input structure is considered devoid of 
                oxidation-state decoration. And oxi-state for each site is 
                determined. Use True, if input structure is oxi-state 
                decorated. This option is useful when the structure is 
                not electro-neutral after deleting/adding sites. In that
                case oxi-decorate the structure before deleting/adding the
                sites.
        """
        if not oxi_state:
            try:
                bv = BVAnalyzer()
                self._structure = bv.get_oxi_state_decorated_structure(structure)
            except:
                try:
                    bv = BVAnalyzer(symm_tol=0.0)
                    self._structure = bv.get_oxi_state_decorated_structure(structure)
                except:
                    raise
        else:
            self._structure = structure

        self._valence_dict = valences
        self._rad_dict = radii

        """
        Use Zeo++ to obtain the voronoi nodes. Apply symmetry reduction
        and the symmetry reduced voronoi nodes are possible candidates
        for interstitial sites.
        """
        if accuracy == "Normal":
            high_accuracy_flag = False
        elif accuracy == "High":
            high_accuracy_flag = True
        else:
            raise ValueError("Accuracy setting not understood.")

        vor_node_sites, vor_facecenter_sites = symmetry_reduced_voronoi_nodes(
            self._structure, self._rad_dict, high_accuracy_flag, symmetry_flag
        )

        if site_type == "voronoi_vertex":
            possible_interstitial_sites = vor_node_sites
        elif site_type == "voronoi_facecenter":
            possible_interstitial_sites = vor_facecenter_sites
        else:
            raise ValueError("Input site type not implemented")

        # Do futher processing on possibleInterstitialSites to obtain
        # interstitial sites
        self._defect_sites = possible_interstitial_sites
        self._defectsite_coord_no = []
        self._defect_coord_sites = []
        self._defect_coord_charge = []
        self._radii = []

        for site in self._defect_sites:
            coord_no, coord_sites, chrg = self._get_coord_no_sites_chrg(site)
            self._defectsite_coord_no.append(coord_no)
            self._defect_coord_sites.append(coord_sites)
            self._defect_coord_charge.append(chrg)

        for site in self._defect_sites:
            self._radii.append(float(site.properties["voronoi_radius"]))
예제 #27
0
    def predict(self, structure, ref_structure):
        """
        Given a structure, returns the predicted volume.
        Args:
            structure (Structure): structure w/unknown volume
            ref_structure (Structure): A reference structure with a similar
                structure but different species.
        Returns:
            a float value of the predicted volume
        """

        if self.check_isostructural:
            m = StructureMatcher()
            mapping = m.get_best_electronegativity_anonymous_mapping(
                structure, ref_structure)
            if mapping is None:
                raise ValueError("Input structures do not match!")

        if "ionic" in self.radii_type:
            try:
                # Use BV analyzer to determine oxidation states only if the
                # oxidation states are not already specified in the structure
                # and use_bv is true.
                if (not is_ox(structure)) and self.use_bv:
                    a = BVAnalyzer()
                    structure = a.get_oxi_state_decorated_structure(structure)
                if (not is_ox(ref_structure)) and self.use_bv:
                    a = BVAnalyzer()
                    ref_structure = a.get_oxi_state_decorated_structure(
                        ref_structure)

                comp = structure.composition
                ref_comp = ref_structure.composition

                # Check if all the associated ionic radii are available.
                if any([k.ionic_radius is None for k in list(comp.keys())]) or \
                        any([k.ionic_radius is None for k in
                             list(ref_comp.keys())]):
                    raise ValueError("Not all the ionic radii are available!")

                numerator = 0
                denominator = 0
                # Here, the 1/3 factor on the composition accounts for atomic
                # packing. We want the number per unit length.
                for k, v in comp.items():
                    numerator += k.ionic_radius * v ** (1 / 3)
                for k, v in ref_comp.items():
                    denominator += k.ionic_radius * v ** (1 / 3)

                return ref_structure.volume * (numerator / denominator) ** 3
            except Exception as ex:
                warnings.warn("Exception occured. Will attempt atomic radii.")
                # If error occurs during use of ionic radii scheme, pass
                # and see if we can resolve it using atomic radii.
                pass

        if "atomic" in self.radii_type:
            comp = structure.composition
            ref_comp = ref_structure.composition
            # Here, the 1/3 factor on the composition accounts for atomic
            # packing. We want the number per unit length.
            numerator = 0
            denominator = 0
            for k, v in comp.items():
                numerator += k.atomic_radius * v ** (1 / 3)
            for k, v in ref_comp.items():
                denominator += k.atomic_radius * v ** (1 / 3)
            return ref_structure.volume * (numerator / denominator) ** 3

        raise ValueError("Cannot find volume scaling based on radii choices "
                         "specified!")
예제 #28
0
    def __init__(self, structure, valences, radii, site_type='voronoi_vertex',
                 accuracy='Normal', symmetry_flag=True):
        """
        Given a structure, generate symmetrically distinct interstitial sites.

        Args:
            structure: pymatgen.core.structure.Structure
            valences: Dictionary of oxidation states of elements in 
                {el:valence} form
            radii: Radii of elemnts in the structure
            site_type: "voronoi_vertex" uses voronoi nodes
                "voronoi_facecenter" uses voronoi polyhedra face centers
                Default is "voronoi_vertex"
            accuracy: Flag denoting whether to use high accuracy version 
                of Zeo++. Options are "Normal" and "High". Default is normal.
        """
        try:
            bv = BVAnalyzer()
            self._structure = bv.get_oxi_state_decorated_structure(structure)
        except:
            try:
                bv = BVAnalyzer(symm_tol=0.0)
                self._structure = bv.get_oxi_state_decorated_structure(
                        structure
                        )
            except:
                raise
        self._valence_dict = valences
        self._rad_dict = radii

        """
        Use Zeo++ to obtain the voronoi nodes. Apply symmetry reduction
        and the symmetry reduced voronoi nodes are possible candidates
        for interstitial sites.
        """
        if accuracy == "Normal":
            high_accuracy_flag = False
        elif accuracy == "High":
            high_accuracy_flag = True
        else:
            raise ValueError("Accuracy setting not understood.")

        vor_node_sites, vor_facecenter_sites = symmetry_reduced_voronoi_nodes(
                self._structure, self._rad_dict, high_accuracy_flag, symmetry_flag
                )
        
        if site_type == 'voronoi_vertex':
            possible_interstitial_sites = vor_node_sites
        elif site_type == 'voronoi_facecenter':
            possible_interstitial_sites = vor_facecenter_sites
        else:
            raise ValueError("Input site type not implemented")

        #Do futher processing on possibleInterstitialSites to obtain
        #interstitial sites
        self._defect_sites = possible_interstitial_sites
        self._defectsite_coord_no = []
        self._defect_coord_sites = []
        self._defect_coord_charge = []
        self._radii = []

        for site in self._defect_sites:
            coord_no, coord_sites, chrg = self._get_coord_no_sites_chrg(site)
            self._defectsite_coord_no.append(coord_no)
            self._defect_coord_sites.append(coord_sites)
            self._defect_coord_charge.append(chrg)

        for site in self._defect_sites:
            self._radii.append(float(site.properties['voronoi_radius']))
예제 #29
0
    def get_analysis_and_structure(self,
                                   structure,
                                   calculate_valences=True,
                                   guesstimate_spin=False,
                                   op_threshold=0.1):
        """
        Obtain an analysis of a given structure and if it may be Jahn-Teller
        active or not. This is a heuristic, and may give false positives and
        false negatives (false positives are preferred).

        :param structure: input structure
        :param calculate_valences (bool): whether to attempt to calculate valences or not, structure
        should have oxidation states to perform analysis
        :param guesstimate_spin (bool): whether to guesstimate spin state from magnetic moments
        or not, use with caution
        :param op_threshold (float): threshold for order parameter above which to consider site
        to match an octahedral or tetrahedral motif, since Jahn-Teller structures can often be
        quite distorted, this threshold is smaller than one might expect
        :return (dict): analysis of structure, with key 'strength' which may be 'none', 'strong',
        'weak', or 'unknown'
        """

        structure = structure.get_primitive_structure()

        if calculate_valences:
            bva = BVAnalyzer()
            structure = bva.get_oxi_state_decorated_structure(structure)

        # no point testing multiple equivalent sites, doesn't make any difference to analysis
        # but makes returned
        symmetrized_structure = SpacegroupAnalyzer(structure).get_symmetrized_structure()

        # to detect structural motifs of a given site
        op = LocalStructOrderParams(['oct', 'tet'])

        # dict of site index to the Jahn-Teller analysis of that site
        jt_sites = []
        non_jt_sites = []

        for indices in symmetrized_structure.equivalent_indices:

            idx = indices[0]
            site = symmetrized_structure[idx]

            # only interested in sites with oxidation states
            if isinstance(site.specie, Specie) and site.specie.element.is_transition_metal:

                # get motif around site
                order_params = op.get_order_parameters(symmetrized_structure, idx)

                if order_params[0] > order_params[1] and order_params[0] > op_threshold:
                    motif = 'oct'
                    motif_order_parameter = order_params[0]
                elif order_params[1] > op_threshold:
                    motif = 'tet'
                    motif_order_parameter = order_params[1]
                else:
                    motif = 'unknown'
                    motif_order_parameter = None

                if motif == "oct" or motif == "tet":

                    # guess spin of metal ion
                    if guesstimate_spin and 'magmom' in site.properties:
                        # estimate if high spin or low spin
                        magmom = site.properties['magmom']
                        spin_state = self._estimate_spin_state(site.specie, motif, magmom)
                    else:
                        spin_state = "unknown"

                    magnitude = self.get_magnitude_of_effect_from_species(site.specie,
                                                                          spin_state,
                                                                          motif)

                    if magnitude != "none":

                        ligands = get_neighbors_of_site_with_index(structure, idx,
                                                                   approach="min_dist",
                                                                   delta=0.15)
                        ligand_bond_lengths = [ligand.distance(structure[idx])
                                               for ligand in ligands]
                        ligands_species = list(set([str(ligand.specie) for ligand in ligands]))
                        ligand_bond_length_spread = max(ligand_bond_lengths) - \
                                                    min(ligand_bond_lengths)

                        def trim(f):
                            # avoid storing to unreasonable precision, hurts readability
                            return float("{:.4f}".format(f))

                        # to be Jahn-Teller active, all ligands have to be the same
                        if len(ligands_species) == 1:

                            jt_sites.append({'strength': magnitude,
                                             'motif': motif,
                                             'motif_order_parameter': trim(motif_order_parameter),
                                             'spin_state': spin_state,
                                             'species': str(site.specie),
                                             'ligand': ligands_species[0],
                                             'ligand_bond_lengths': [trim(length) for length in
                                                                     ligand_bond_lengths],
                                             'ligand_bond_length_spread':
                                                 trim(ligand_bond_length_spread),
                                             'site_indices': indices})

            # store reasons for not being J-T active
                    else:
                        non_jt_sites.append({'site_indices': indices,
                                             'strength': "none",
                                             'reason': "Not Jahn-Teller active for this "
                                                       "electronic configuration."})
                else:
                    non_jt_sites.append({'site_indices': indices,
                                         'strength': "none",
                                         'reason': "motif is {}".format(motif)})

        # perform aggregation of all sites
        if jt_sites:
            analysis = {'active': True}
            # if any site could exhibit 'strong' Jahn-Teller effect
            # then mark whole structure as strong
            strong_magnitudes = [site['strength'] == "strong" for site in jt_sites]
            if any(strong_magnitudes):
                analysis['strength'] = "strong"
            else:
                analysis['strength'] = "weak"
            analysis['sites'] = jt_sites
            return analysis, structure
        else:
            return {'active': False, 'sites': non_jt_sites}, structure
예제 #30
0
    def predict(self, structure, ref_structure):
        """
        Given a structure, returns the predicted volume.

        Args:
            structure (Structure): structure w/unknown volume
            ref_structure (Structure): A reference structure with a similar
                structure but different species.
        Returns:
            a float value of the predicted volume
        """

        if self.check_isostructural:
            m = StructureMatcher()
            mapping = m.get_best_electronegativity_anonymous_mapping(structure, ref_structure)
            if mapping is None:
                raise ValueError("Input structures do not match!")

        if "ionic" in self.radii_type:
            try:
                # Use BV analyzer to determine oxidation states only if the
                # oxidation states are not already specified in the structure
                # and use_bv is true.
                if (not _is_ox(structure)) and self.use_bv:
                    a = BVAnalyzer()
                    structure = a.get_oxi_state_decorated_structure(structure)
                if (not _is_ox(ref_structure)) and self.use_bv:
                    a = BVAnalyzer()
                    ref_structure = a.get_oxi_state_decorated_structure(ref_structure)

                comp = structure.composition
                ref_comp = ref_structure.composition

                # Check if all the associated ionic radii are available.
                if any(k.ionic_radius is None for k in list(comp.keys())) or any(
                    k.ionic_radius is None for k in list(ref_comp.keys())
                ):
                    raise ValueError("Not all the ionic radii are available!")

                numerator = 0
                denominator = 0
                # Here, the 1/3 factor on the composition accounts for atomic
                # packing. We want the number per unit length.
                for k, v in comp.items():
                    numerator += k.ionic_radius * v ** (1 / 3)
                for k, v in ref_comp.items():
                    denominator += k.ionic_radius * v ** (1 / 3)

                return ref_structure.volume * (numerator / denominator) ** 3
            except Exception:
                warnings.warn("Exception occured. Will attempt atomic radii.")
                # If error occurs during use of ionic radii scheme, pass
                # and see if we can resolve it using atomic radii.
                pass

        if "atomic" in self.radii_type:
            comp = structure.composition
            ref_comp = ref_structure.composition
            # Here, the 1/3 factor on the composition accounts for atomic
            # packing. We want the number per unit length.
            numerator = 0
            denominator = 0
            for k, v in comp.items():
                numerator += k.atomic_radius * v ** (1 / 3)
            for k, v in ref_comp.items():
                denominator += k.atomic_radius * v ** (1 / 3)
            return ref_structure.volume * (numerator / denominator) ** 3

        raise ValueError("Cannot find volume scaling based on radii choices specified!")
예제 #31
0
    def apply_transformation(self, structure, return_ranked_list=False):
        """
        Args:
            structure (Structure): Input structure to dope

        Returns:
            [{"structure": Structure, "energy": float}]
        """
        comp = structure.composition
        logger.info("Composition: %s" % comp)

        for sp in comp:
            try:
                sp.oxi_state
            except AttributeError:
                analyzer = BVAnalyzer()
                structure = analyzer.get_oxi_state_decorated_structure(
                    structure)
                comp = structure.composition
                break

        ox = self.dopant.oxi_state
        radius = self.dopant.ionic_radius

        compatible_species = [
            sp for sp in comp
            if sp.oxi_state == ox and abs(sp.ionic_radius / radius -
                                          1) < self.ionic_radius_tol
        ]

        if (not compatible_species) and self.alio_tol:
            # We only consider aliovalent doping if there are no compatible
            # isovalent species.
            compatible_species = [
                sp for sp in comp if abs(sp.oxi_state - ox) <= self.alio_tol
                and abs(sp.ionic_radius / radius -
                        1) < self.ionic_radius_tol and sp.oxi_state * ox >= 0
            ]

        if self.allowed_doping_species is not None:
            # Only keep allowed doping species.
            compatible_species = [
                sp for sp in compatible_species
                if sp in [get_el_sp(s) for s in self.allowed_doping_species]
            ]

        logger.info("Compatible species: %s" % compatible_species)

        lengths = structure.lattice.abc
        scaling = [
            max(1, int(round(math.ceil(self.min_length / x)))) for x in lengths
        ]
        logger.info("Lengths are %s" % str(lengths))
        logger.info("Scaling = %s" % str(scaling))

        all_structures = []
        t = EnumerateStructureTransformation(**self.kwargs)

        for sp in compatible_species:
            supercell = structure * scaling
            nsp = supercell.composition[sp]
            if sp.oxi_state == ox:
                supercell.replace_species(
                    {sp: {
                        sp: (nsp - 1) / nsp,
                        self.dopant: 1 / nsp
                    }})
                logger.info("Doping %s for %s at level %.3f" %
                            (sp, self.dopant, 1 / nsp))
            elif self.codopant:
                codopant = _find_codopant(sp, 2 * sp.oxi_state - ox)
                supercell.replace_species({
                    sp: {
                        sp: (nsp - 2) / nsp,
                        self.dopant: 1 / nsp,
                        codopant: 1 / nsp
                    }
                })
                logger.info("Doping %s for %s + %s at level %.3f" %
                            (sp, self.dopant, codopant, 1 / nsp))
            elif abs(sp.oxi_state) < abs(ox):
                # Strategy: replace the target species with a
                # combination of dopant and vacancy.
                # We will choose the lowest oxidation state species as a
                # vacancy compensation species as it is likely to be lower in
                # energy
                sp_to_remove = min([s for s in comp if s.oxi_state * ox > 0],
                                   key=lambda ss: abs(ss.oxi_state))

                if sp_to_remove == sp:
                    common_charge = lcm(int(abs(sp.oxi_state)), int(abs(ox)))
                    ndopant = common_charge / abs(ox)
                    nsp_to_remove = common_charge / abs(sp.oxi_state)
                    logger.info("Doping %d %s with %d %s." %
                                (nsp_to_remove, sp, ndopant, self.dopant))
                    supercell.replace_species({
                        sp: {
                            sp: (nsp - nsp_to_remove) / nsp,
                            self.dopant: ndopant / nsp
                        }
                    })
                else:
                    ox_diff = int(abs(round(sp.oxi_state - ox)))
                    vac_ox = int(abs(sp_to_remove.oxi_state))
                    common_charge = lcm(vac_ox, ox_diff)
                    ndopant = common_charge / ox_diff
                    nx_to_remove = common_charge / vac_ox
                    nx = supercell.composition[sp_to_remove]
                    logger.info(
                        "Doping %d %s with %s and removing %d %s." %
                        (ndopant, sp, self.dopant, nx_to_remove, sp_to_remove))
                    supercell.replace_species({
                        sp: {
                            sp: (nsp - ndopant) / nsp,
                            self.dopant: ndopant / nsp
                        },
                        sp_to_remove: {
                            sp_to_remove: (nx - nx_to_remove) / nx
                        }
                    })
            elif abs(sp.oxi_state) > abs(ox):
                # Strategy: replace the target species with dopant and also
                # remove some opposite charged species for charge neutrality
                if ox > 0:
                    sp_to_remove = max(supercell.composition.keys(),
                                       key=lambda el: el.X)
                else:
                    sp_to_remove = min(supercell.composition.keys(),
                                       key=lambda el: el.X)
                # Confirm species are of opposite oxidation states.
                assert sp_to_remove.oxi_state * sp.oxi_state < 0

                ox_diff = int(abs(round(sp.oxi_state - ox)))
                anion_ox = int(abs(sp_to_remove.oxi_state))
                nx = supercell.composition[sp_to_remove]
                common_charge = lcm(anion_ox, ox_diff)
                ndopant = common_charge / ox_diff
                nx_to_remove = common_charge / anion_ox
                logger.info(
                    "Doping %d %s with %s and removing %d %s." %
                    (ndopant, sp, self.dopant, nx_to_remove, sp_to_remove))
                supercell.replace_species({
                    sp: {
                        sp: (nsp - ndopant) / nsp,
                        self.dopant: ndopant / nsp
                    },
                    sp_to_remove: {
                        sp_to_remove: (nx - nx_to_remove) / nx
                    }
                })

            ss = t.apply_transformation(
                supercell, return_ranked_list=self.max_structures_per_enum)
            logger.info("%s distinct structures" % len(ss))
            all_structures.extend(ss)

        logger.info("Total %s doped structures" % len(all_structures))
        if return_ranked_list:
            return all_structures[:return_ranked_list]

        return all_structures[0]["structure"]
예제 #32
0
    def apply_transformation(self, structure, return_ranked_list=False):
        """
        Args:
            structure (Structure): Input structure to dope

        Returns:
            [{"structure": Structure, "energy": float}]
        """
        comp = structure.composition
        logger.info("Composition: %s" % comp)

        for sp in comp:
            try:
                sp.oxi_state
            except AttributeError:
                analyzer = BVAnalyzer()
                structure = analyzer.get_oxi_state_decorated_structure(
                    structure)
                comp = structure.composition
                break

        ox = self.dopant.oxi_state
        radius = self.dopant.ionic_radius

        compatible_species = [
            sp for sp in comp if sp.oxi_state == ox and
            abs(sp.ionic_radius / radius - 1) < self.ionic_radius_tol]

        if (not compatible_species) and self.alio_tol:
            # We only consider aliovalent doping if there are no compatible
            # isovalent species.
            compatible_species = [
                sp for sp in comp
                if abs(sp.oxi_state - ox) <= self.alio_tol and
                abs(sp.ionic_radius / radius - 1) < self.ionic_radius_tol and
                sp.oxi_state * ox >= 0]

        if self.allowed_doping_species is not None:
            # Only keep allowed doping species.
            compatible_species = [
                sp for sp in compatible_species
                if sp in [get_el_sp(s) for s in self.allowed_doping_species]]

        logger.info("Compatible species: %s" % compatible_species)

        lengths = structure.lattice.abc
        scaling = [max(1, int(round(math.ceil(self.min_length/x))))
                   for x in lengths]
        logger.info("Lengths are %s" % str(lengths))
        logger.info("Scaling = %s" % str(scaling))

        all_structures = []
        t = EnumerateStructureTransformation(**self.kwargs)

        for sp in compatible_species:
            supercell = structure * scaling
            nsp = supercell.composition[sp]
            if sp.oxi_state == ox:
                supercell.replace_species({sp: {sp: (nsp - 1)/nsp,
                                                self.dopant: 1/nsp}})
                logger.info("Doping %s for %s at level %.3f" % (
                    sp, self.dopant, 1 / nsp))
            elif self.codopant:
                codopant = _find_codopant(sp, 2 * sp.oxi_state - ox)
                supercell.replace_species({sp: {sp: (nsp - 2) / nsp,
                                                self.dopant: 1 / nsp,
                                                codopant: 1 / nsp}})
                logger.info("Doping %s for %s + %s at level %.3f" % (
                    sp, self.dopant, codopant, 1 / nsp))
            elif abs(sp.oxi_state) < abs(ox):
                # Strategy: replace the target species with a
                # combination of dopant and vacancy.
                # We will choose the lowest oxidation state species as a
                # vacancy compensation species as it is likely to be lower in
                # energy
                sp_to_remove = min([s for s in comp if s.oxi_state * ox > 0],
                                    key=lambda ss: abs(ss.oxi_state))

                if sp_to_remove == sp:
                    common_charge = lcm(int(abs(sp.oxi_state)), int(abs(ox)))
                    ndopant = common_charge / abs(ox)
                    nsp_to_remove = common_charge / abs(sp.oxi_state)
                    logger.info("Doping %d %s with %d %s." %
                                (nsp_to_remove, sp, ndopant, self.dopant))
                    supercell.replace_species(
                        {sp: {sp: (nsp - nsp_to_remove) / nsp,
                              self.dopant: ndopant / nsp}})
                else:
                    ox_diff = int(abs(round(sp.oxi_state - ox)))
                    vac_ox = int(abs(sp_to_remove.oxi_state))
                    common_charge = lcm(vac_ox, ox_diff)
                    ndopant = common_charge / ox_diff
                    nx_to_remove = common_charge / vac_ox
                    nx = supercell.composition[sp_to_remove]
                    logger.info("Doping %d %s with %s and removing %d %s." %
                                (ndopant, sp, self.dopant,
                                 nx_to_remove, sp_to_remove))
                    supercell.replace_species(
                        {sp: {sp: (nsp - ndopant) / nsp,
                              self.dopant: ndopant / nsp},
                         sp_to_remove: {
                             sp_to_remove: (nx - nx_to_remove) / nx}})
            elif abs(sp.oxi_state) > abs(ox):
                # Strategy: replace the target species with dopant and also
                # remove some opposite charged species for charge neutrality
                if ox > 0:
                    sp_to_remove = max(supercell.composition.keys(),
                                       key=lambda el: el.X)
                else:
                    sp_to_remove = min(supercell.composition.keys(),
                                       key=lambda el: el.X)
                # Confirm species are of opposite oxidation states.
                assert sp_to_remove.oxi_state * sp.oxi_state < 0

                ox_diff = int(abs(round(sp.oxi_state - ox)))
                anion_ox = int(abs(sp_to_remove.oxi_state))
                nx = supercell.composition[sp_to_remove]
                common_charge = lcm(anion_ox, ox_diff)
                ndopant = common_charge / ox_diff
                nx_to_remove = common_charge / anion_ox
                logger.info("Doping %d %s with %s and removing %d %s." %
                            (ndopant, sp, self.dopant,
                             nx_to_remove, sp_to_remove))
                supercell.replace_species(
                    {sp: {sp: (nsp - ndopant) / nsp,
                          self.dopant: ndopant / nsp},
                     sp_to_remove: {sp_to_remove: (nx - nx_to_remove)/nx}})

            ss = t.apply_transformation(
                supercell, return_ranked_list=self.max_structures_per_enum)
            logger.info("%s distinct structures" % len(ss))
            all_structures.extend(ss)

        logger.info("Total %s doped structures" % len(all_structures))
        if return_ranked_list:
            return all_structures[:return_ranked_list]

        return all_structures[0]["structure"]
예제 #33
0
    def get_analysis_and_structure(self,
                                   structure,
                                   calculate_valences=True,
                                   guesstimate_spin=False,
                                   op_threshold=0.1):
        """
        Obtain an analysis of a given structure and if it may be Jahn-Teller
        active or not. This is a heuristic, and may give false positives and
        false negatives (false positives are preferred).

        :param structure: input structure
        :param calculate_valences (bool): whether to attempt to calculate valences or not, structure
            should have oxidation states to perform analysis
        :param guesstimate_spin (bool): whether to guesstimate spin state from magnetic moments
            or not, use with caution
        :param op_threshold (float): threshold for order parameter above which to consider site
            to match an octahedral or tetrahedral motif, since Jahn-Teller structures can often be
            quite distorted, this threshold is smaller than one might expect
        :return (dict): analysis of structure, with key 'strength' which may be 'none', 'strong',
            'weak', or 'unknown'
        """

        structure = structure.get_primitive_structure()

        if calculate_valences:
            bva = BVAnalyzer()
            structure = bva.get_oxi_state_decorated_structure(structure)

        # no point testing multiple equivalent sites, doesn't make any difference to analysis
        # but makes returned
        symmetrized_structure = SpacegroupAnalyzer(
            structure).get_symmetrized_structure()

        # to detect structural motifs of a given site
        op = LocalStructOrderParams(['oct', 'tet'])

        # dict of site index to the Jahn-Teller analysis of that site
        jt_sites = []
        non_jt_sites = []

        for indices in symmetrized_structure.equivalent_indices:

            idx = indices[0]
            site = symmetrized_structure[idx]

            # only interested in sites with oxidation states
            if isinstance(site.specie,
                          Specie) and site.specie.element.is_transition_metal:

                # get motif around site
                order_params = op.get_order_parameters(symmetrized_structure,
                                                       idx)

                if order_params[0] > order_params[1] and order_params[
                        0] > op_threshold:
                    motif = 'oct'
                    motif_order_parameter = order_params[0]
                elif order_params[1] > op_threshold:
                    motif = 'tet'
                    motif_order_parameter = order_params[1]
                else:
                    motif = 'unknown'
                    motif_order_parameter = None

                if motif == "oct" or motif == "tet":

                    # guess spin of metal ion
                    if guesstimate_spin and 'magmom' in site.properties:

                        # estimate if high spin or low spin
                        magmom = site.properties['magmom']
                        spin_state = self._estimate_spin_state(
                            site.specie, motif, magmom)
                    else:
                        spin_state = "unknown"

                    magnitude = self.get_magnitude_of_effect_from_species(
                        site.specie, spin_state, motif)

                    if magnitude != "none":

                        ligands = get_neighbors_of_site_with_index(
                            structure, idx, approach="min_dist", delta=0.15)
                        ligand_bond_lengths = [
                            ligand.distance(structure[idx])
                            for ligand in ligands
                        ]
                        ligands_species = list(
                            set([str(ligand.specie) for ligand in ligands]))
                        ligand_bond_length_spread = max(ligand_bond_lengths) - \
                                                    min(ligand_bond_lengths)

                        def trim(f):
                            # avoid storing to unreasonable precision, hurts readability
                            return float("{:.4f}".format(f))

                        # to be Jahn-Teller active, all ligands have to be the same
                        if len(ligands_species) == 1:
                            jt_sites.append({
                                'strength':
                                magnitude,
                                'motif':
                                motif,
                                'motif_order_parameter':
                                trim(motif_order_parameter),
                                'spin_state':
                                spin_state,
                                'species':
                                str(site.specie),
                                'ligand':
                                ligands_species[0],
                                'ligand_bond_lengths': [
                                    trim(length)
                                    for length in ligand_bond_lengths
                                ],
                                'ligand_bond_length_spread':
                                trim(ligand_bond_length_spread),
                                'site_indices':
                                indices
                            })

                    # store reasons for not being J-T active
                    else:
                        non_jt_sites.append({
                            'site_indices':
                            indices,
                            'strength':
                            "none",
                            'reason':
                            "Not Jahn-Teller active for this "
                            "electronic configuration."
                        })
                else:
                    non_jt_sites.append({
                        'site_indices': indices,
                        'strength': "none",
                        'reason': "motif is {}".format(motif)
                    })

        # perform aggregation of all sites
        if jt_sites:
            analysis = {'active': True}
            # if any site could exhibit 'strong' Jahn-Teller effect
            # then mark whole structure as strong
            strong_magnitudes = [
                site['strength'] == "strong" for site in jt_sites
            ]
            if any(strong_magnitudes):
                analysis['strength'] = "strong"
            else:
                analysis['strength'] = "weak"
            analysis['sites'] = jt_sites
            return analysis, structure
        else:
            return {'active': False, 'sites': non_jt_sites}, structure
예제 #34
0
    def get_analysis_and_structure(
        self,
        structure: Structure,
        calculate_valences: bool = True,
        guesstimate_spin: bool = False,
        op_threshold: float = 0.1,
    ) -> Tuple[Dict, Structure]:
        """Obtain an analysis of a given structure and if it may be Jahn-Teller
        active or not. This is a heuristic, and may give false positives and
        false negatives (false positives are preferred).

        Args:
            structure: input structure
            calculate_valences: whether to attempt to calculate valences or not, structure
                should have oxidation states to perform analysis (Default value = True)
            guesstimate_spin: whether to guesstimate spin state from magnetic moments
                or not, use with caution (Default value = False)
            op_threshold: threshold for order parameter above which to consider site
                to match an octahedral or tetrahedral motif, since Jahn-Teller structures
                can often be
                quite distorted, this threshold is smaller than one might expect

        Returns:
            analysis of structure, with key 'strength' which may be 'none', 'strong',
            'weak', or 'unknown' (Default value = 0.1) and decorated structure

        """

        structure = structure.get_primitive_structure()

        if calculate_valences:
            bva = BVAnalyzer()
            structure = bva.get_oxi_state_decorated_structure(structure)

        # no point testing multiple equivalent sites, doesn't make any difference to analysis
        # but makes returned
        symmetrized_structure = SpacegroupAnalyzer(
            structure).get_symmetrized_structure()

        # to detect structural motifs of a given site
        op = LocalStructOrderParams(["oct", "tet"])

        # dict of site index to the Jahn-Teller analysis of that site
        jt_sites = []
        non_jt_sites = []

        for indices in symmetrized_structure.equivalent_indices:

            idx = indices[0]
            site = symmetrized_structure[idx]

            # only interested in sites with oxidation states
            if isinstance(site.specie,
                          Species) and site.specie.element.is_transition_metal:

                # get motif around site
                order_params = op.get_order_parameters(symmetrized_structure,
                                                       idx)

                if order_params[0] > order_params[1] and order_params[
                        0] > op_threshold:
                    motif = "oct"
                    motif_order_parameter = order_params[0]
                elif order_params[1] > op_threshold:
                    motif = "tet"
                    motif_order_parameter = order_params[1]
                else:
                    motif = "unknown"
                    motif_order_parameter = None

                if motif in ["oct", "tet"]:

                    motif = cast(Literal["oct", "tet"],
                                 motif)  # mypy needs help

                    # guess spin of metal ion
                    if guesstimate_spin and "magmom" in site.properties:

                        # estimate if high spin or low spin
                        magmom = site.properties["magmom"]
                        spin_state = self._estimate_spin_state(
                            site.specie, motif, magmom)
                    else:
                        spin_state = "unknown"

                    magnitude = self.get_magnitude_of_effect_from_species(
                        site.specie, spin_state, motif)

                    if magnitude != "none":

                        ligands = get_neighbors_of_site_with_index(
                            structure, idx, approach="min_dist", delta=0.15)
                        ligand_bond_lengths = [
                            ligand.distance(structure[idx])
                            for ligand in ligands
                        ]
                        ligands_species = list(
                            {str(ligand.specie)
                             for ligand in ligands})
                        ligand_bond_length_spread = max(
                            ligand_bond_lengths) - min(ligand_bond_lengths)

                        def trim(f):
                            """
                            Avoid storing to unreasonable precision, hurts readability.
                            """
                            return float(f"{f:.4f}")

                        # to be Jahn-Teller active, all ligands have to be the same
                        if len(ligands_species) == 1:
                            jt_sites.append({
                                "strength":
                                magnitude,
                                "motif":
                                motif,
                                "motif_order_parameter":
                                trim(motif_order_parameter),
                                "spin_state":
                                spin_state,
                                "species":
                                str(site.specie),
                                "ligand":
                                ligands_species[0],
                                "ligand_bond_lengths": [
                                    trim(length)
                                    for length in ligand_bond_lengths
                                ],
                                "ligand_bond_length_spread":
                                trim(ligand_bond_length_spread),
                                "site_indices":
                                indices,
                            })

                    # store reasons for not being J-T active
                    else:
                        non_jt_sites.append({
                            "site_indices":
                            indices,
                            "strength":
                            "none",
                            "reason":
                            "Not Jahn-Teller active for this electronic configuration.",
                        })
                else:
                    non_jt_sites.append({
                        "site_indices": indices,
                        "strength": "none",
                        "reason": f"motif is {motif}",
                    })

        # perform aggregation of all sites
        if jt_sites:
            analysis = {"active": True}  # type: Dict[str, Any]
            # if any site could exhibit 'strong' Jahn-Teller effect
            # then mark whole structure as strong
            strong_magnitudes = [
                site["strength"] == "strong" for site in jt_sites
            ]
            if any(strong_magnitudes):
                analysis["strength"] = "strong"
            else:
                analysis["strength"] = "weak"
            analysis["sites"] = jt_sites
            return analysis, structure
        return {"active": False, "sites": non_jt_sites}, structure