Esempio n. 1
0
    def process_item(self, item):
        """
        Process the list of entries into a phase diagram

        Args:
            item (set(entry)): a list of entries to process into a phase diagram

        Returns:
            [dict]: a list of thermo dictionaries to update thermo with
        """
        entries = self.compatibility.process_entries(item)

        try:
            pd = PhaseDiagram(entries)

            docs = []

            for e in entries:
                (decomp, ehull) = \
                    pd.get_decomp_and_e_above_hull(e)

                d = {
                    self.thermo.key: e.entry_id,
                    "thermo": {
                        "formation_energy_per_atom":
                        pd.get_form_energy_per_atom(e),
                        "e_above_hull": ehull,
                        "is_stable": e in pd.stable_entries
                    }
                }

                # Logic for if stable or decomposes
                if d["thermo"]["is_stable"]:
                    d["thermo"][
                        "eq_reaction_e"] = pd.get_equilibrium_reaction_energy(
                            e)
                else:
                    d["thermo"]["decomposes_to"] = [{
                        "task_id": de.entry_id,
                        "formula": de.composition.formula,
                        "amount": amt
                    } for de, amt in decomp.items()]

                d["thermo"]["entry"] = e.as_dict()
                d["thermo"][
                    "explanation"] = self.compatibility.get_explanation_dict(e)

                elsyms = sorted(
                    set([el.symbol for el in e.composition.elements]))
                d["chemsys"] = "-".join(elsyms)
                d["nelements"] = len(elsyms)
                d["elements"] = list(elsyms)

                docs.append(d)
        except PhaseDiagramError as p:
            print(e.as_dict())
            self.logger.warning("Phase diagram error: {}".format(p))
            return []

        return docs
Esempio n. 2
0
def convexHull(entries, tolerance=0, printing=False):
    # This initializes the REST adaptor. You may need to put your own API key in as an arg. If we need MP DB
    #with open("key.txt", "r") as myfile:
    #    data = myfile.readlines()
    #a = MPRester(data[0].rstrip())
    #    entries = a.get_entries_in_chemsys(elems)
    pd2 = PhaseDiagram(entries)
    data = collections.defaultdict(list)
    for e in pd2.stable_entries:
        ehull = pd2.get_equilibrium_reaction_energy(e)
        data["Composition"].append(e.composition.reduced_formula)
        data['NElements'].append(e.attribute)
        data["Ehull"].append(ehull)
        #data['Entropy'].append(e.data)
        #data['Gibbs'].append(ehull)
        data["Decomposition"].append("self")
    for e in pd2.unstable_entries:
        decomp, ehull = pd2.get_decomp_and_e_above_hull(e, allow_negative=True)
        #        decomp, ehull = pd2.get_decomp_and_e_above_hull(e)
        data["Composition"].append(e.composition.reduced_formula)
        data['NElements'].append(e.attribute)
        data["Ehull"].append(ehull)
        #data['Entropy'].append(e.data)
        #data['Gibbs'].append(ehull)
        data["Decomposition"].append(" + ".join([
            "%.2f %s" % (v, k.composition.formula) for k, v in decomp.items()
        ])[0:70])
    df = pd.DataFrame(
        data, columns=["NElements", "Composition", "Ehull", "Decomposition"])
    if printing == True:
        #print(df[df.Ehull<=tolerance])
        print(df)
    return (df)
Esempio n. 3
0
    def from_entries(cls, entries: List[ComputedEntry], sandboxes=None):

        pd = PhaseDiagram(entries)
        sandboxes = sandboxes or ["core"]

        docs = []

        for e in entries:
            (decomp, ehull) = pd.get_decomp_and_e_above_hull(e)

            d = {
                "material_id":
                e.entry_id,
                "uncorrected_energy_per_atom":
                e.uncorrected_energy / e.composition.num_atoms,
                "energy_per_atom":
                e.uncorrected_energy / e.composition.num_atoms,
                "formation_energy_per_atom":
                pd.get_form_energy_per_atom(e),
                "energy_above_hull":
                ehull,
                "is_stable":
                e in pd.stable_entries,
                "sandboxes":
                sandboxes,
            }

            if "last_updated" in e.data:
                d["last_updated"] = e.data["last_updated"]

            # Store different info if stable vs decomposes
            if d["is_stable"]:
                d["equillibrium_reaction_energy_per_atom"] = pd.get_equilibrium_reaction_energy(
                    e)
            else:
                d["decomposes_to"] = [{
                    "material_id": de.entry_id,
                    "formula": de.composition.formula,
                    "amount": amt,
                } for de, amt in decomp.items()]

            d["energy_type"] = e.parameters.get("run_type", "Unknown")
            d["entry_types"] = [e.parameters.get("run_type", "Unknown")]
            d["entries"] = {e.parameters.get("run_type", ""): e}

            for k in ["last_updated"]:
                if k in e.parameters:
                    d[k] = e.parameters[k]
                elif k in e.data:
                    d[k] = e.data[k]

            docs.append(
                ThermoDoc.from_composition(composition=e.composition, **d))

        return docs
Esempio n. 4
0
class PhaseDiagramTest(unittest.TestCase):
    def setUp(self):
        self.entries = EntrySet.from_csv(str(module_dir /
                                             "pdentries_test.csv"))
        self.pd = PhaseDiagram(self.entries)
        warnings.simplefilter("ignore")

    def tearDown(self):
        warnings.simplefilter("default")

    def test_init(self):
        # Ensure that a bad set of entries raises a PD error. Remove all Li
        # from self.entries.
        entries = filter(
            lambda e: (not e.composition.is_element) or e.composition.elements[
                0] != Element("Li"),
            self.entries,
        )
        self.assertRaises(PhaseDiagramError, PhaseDiagram, entries)

    def test_dim1(self):
        # Ensure that dim 1 PDs can eb generated.
        for el in ["Li", "Fe", "O2"]:
            entries = [
                e for e in self.entries if e.composition.reduced_formula == el
            ]
            pd = PhaseDiagram(entries)
            self.assertEqual(len(pd.stable_entries), 1)

            for e in entries:
                decomp, ehull = pd.get_decomp_and_e_above_hull(e)
                self.assertGreaterEqual(ehull, 0)
            plotter = PDPlotter(pd)
            lines, stable_entries, unstable_entries = plotter.pd_plot_data
            self.assertEqual(lines[0][1], [0, 0])

    def test_ordering(self):
        # Test sorting of elements
        entries = [
            ComputedEntry(Composition(formula), 0)
            for formula in ["O", "N", "Fe"]
        ]
        pd = PhaseDiagram(entries)
        sorted_elements = (Element("Fe"), Element("N"), Element("O"))
        self.assertEqual(tuple(pd.elements), sorted_elements)

        entries.reverse()
        pd = PhaseDiagram(entries)
        self.assertEqual(tuple(pd.elements), sorted_elements)

        # Test manual specification of order
        ordering = [Element(elt_string) for elt_string in ["O", "N", "Fe"]]
        pd = PhaseDiagram(entries, elements=ordering)
        self.assertEqual(tuple(pd.elements), tuple(ordering))

    def test_stable_entries(self):
        stable_formulas = [
            ent.composition.reduced_formula for ent in self.pd.stable_entries
        ]
        expected_stable = [
            "Fe2O3",
            "Li5FeO4",
            "LiFeO2",
            "Fe3O4",
            "Li",
            "Fe",
            "Li2O",
            "O2",
            "FeO",
        ]
        for formula in expected_stable:
            self.assertTrue(formula in stable_formulas,
                            formula + " not in stable entries!")

    def test_get_formation_energy(self):
        stable_formation_energies = {
            ent.composition.reduced_formula: self.pd.get_form_energy(ent)
            for ent in self.pd.stable_entries
        }
        expected_formation_energies = {
            "Li5FeO4": -164.8117344866667,
            "Li2O2": -14.119232793333332,
            "Fe2O3": -16.574164339999996,
            "FeO": -5.7141519966666685,
            "Li": 0.0,
            "LiFeO2": -7.732752316666666,
            "Li2O": -6.229303868333332,
            "Fe": 0.0,
            "Fe3O4": -22.565714456666683,
            "Li2FeO3": -45.67166036000002,
            "O2": 0.0,
        }
        for formula, energy in expected_formation_energies.items():
            self.assertAlmostEqual(energy, stable_formation_energies[formula],
                                   7)

    def test_all_entries_hulldata(self):
        self.assertEqual(len(self.pd.all_entries_hulldata), 492)

    def test_planar_inputs(self):
        e1 = PDEntry("H", 0)
        e2 = PDEntry("He", 0)
        e3 = PDEntry("Li", 0)
        e4 = PDEntry("Be", 0)
        e5 = PDEntry("B", 0)
        e6 = PDEntry("Rb", 0)

        pd = PhaseDiagram([e1, e2, e3, e4, e5, e6],
                          map(Element, ["Rb", "He", "B", "Be", "Li", "H"]))

        self.assertEqual(len(pd.facets), 1)

    def test_str(self):
        self.assertIsNotNone(str(self.pd))

    def test_get_e_above_hull(self):
        for entry in self.pd.stable_entries:
            self.assertLess(
                self.pd.get_e_above_hull(entry),
                1e-11,
                "Stable entries should have e above hull of zero!",
            )

        for entry in self.pd.all_entries:
            if entry not in self.pd.stable_entries:
                e_ah = self.pd.get_e_above_hull(entry)
                self.assertTrue(isinstance(e_ah, Number))
                self.assertGreaterEqual(e_ah, 0)

    def test_get_equilibrium_reaction_energy(self):
        for entry in self.pd.stable_entries:
            self.assertLessEqual(
                self.pd.get_equilibrium_reaction_energy(entry),
                0,
                "Stable entries should have negative equilibrium reaction energy!",
            )

    def test_get_quasi_e_to_hull(self):
        for entry in self.pd.unstable_entries:
            # catch duplicated stable entries
            if entry.normalize(
                    inplace=False) in self.pd.get_stable_entries_normed():
                self.assertLessEqual(
                    self.pd.get_quasi_e_to_hull(entry),
                    0,
                    "Duplicated stable entries should have negative decomposition energy!",
                )
            else:
                self.assertGreaterEqual(
                    self.pd.get_quasi_e_to_hull(entry),
                    0,
                    "Unstable entries should have positive decomposition energy!",
                )

        for entry in self.pd.stable_entries:
            if entry.composition.is_element:
                self.assertEqual(
                    self.pd.get_quasi_e_to_hull(entry),
                    0,
                    "Stable elemental entries should have decomposition energy of zero!",
                )
            else:
                self.assertLessEqual(
                    self.pd.get_quasi_e_to_hull(entry),
                    0,
                    "Stable entries should have negative decomposition energy!",
                )

        novel_stable_entry = PDEntry("Li5FeO4", -999)
        self.assertLess(
            self.pd.get_quasi_e_to_hull(novel_stable_entry),
            0,
            "Novel stable entries should have negative decomposition energy!",
        )

        novel_unstable_entry = PDEntry("Li5FeO4", 999)
        self.assertGreater(
            self.pd.get_quasi_e_to_hull(novel_unstable_entry),
            0,
            "Novel unstable entries should have positive decomposition energy!",
        )

        duplicate_entry = PDEntry("Li2O", -14.31361175)
        scaled_dup_entry = PDEntry("Li4O2", -14.31361175 * 2)
        stable_entry = [e for e in self.pd.stable_entries
                        if e.name == "Li2O"][0]

        self.assertEqual(
            self.pd.get_quasi_e_to_hull(duplicate_entry),
            self.pd.get_quasi_e_to_hull(stable_entry),
            "Novel duplicates of stable entries should have same decomposition energy!",
        )

        self.assertEqual(
            self.pd.get_quasi_e_to_hull(scaled_dup_entry),
            self.pd.get_quasi_e_to_hull(stable_entry),
            "Novel scaled duplicates of stable entries should have same decomposition energy!",
        )

    def test_get_decomposition(self):
        for entry in self.pd.stable_entries:
            self.assertEqual(
                len(self.pd.get_decomposition(entry.composition)),
                1,
                "Stable composition should have only 1 decomposition!",
            )
        dim = len(self.pd.elements)
        for entry in self.pd.all_entries:
            ndecomp = len(self.pd.get_decomposition(entry.composition))
            self.assertTrue(
                ndecomp > 0 and ndecomp <= dim,
                "The number of decomposition phases can at most be equal to the number of components.",
            )

        # Just to test decomp for a ficitious composition
        ansdict = {
            entry.composition.formula: amt
            for entry, amt in self.pd.get_decomposition(
                Composition("Li3Fe7O11")).items()
        }
        expected_ans = {
            "Fe2 O2": 0.0952380952380949,
            "Li1 Fe1 O2": 0.5714285714285714,
            "Fe6 O8": 0.33333333333333393,
        }
        for k, v in expected_ans.items():
            self.assertAlmostEqual(ansdict[k], v)

    def test_get_transition_chempots(self):
        for el in self.pd.elements:
            self.assertLessEqual(len(self.pd.get_transition_chempots(el)),
                                 len(self.pd.facets))

    def test_get_element_profile(self):
        for el in self.pd.elements:
            for entry in self.pd.stable_entries:
                if not (entry.composition.is_element):
                    self.assertLessEqual(
                        len(self.pd.get_element_profile(el,
                                                        entry.composition)),
                        len(self.pd.facets),
                    )

        expected = [
            {
                "evolution": 1.0,
                "chempot": -4.2582781416666666,
                "reaction": "Li2O + 0.5 O2 -> Li2O2",
            },
            {
                "evolution": 0,
                "chempot": -5.0885906699999968,
                "reaction": "Li2O -> Li2O",
            },
            {
                "evolution": -1.0,
                "chempot": -10.487582010000001,
                "reaction": "Li2O -> 2 Li + 0.5 O2",
            },
        ]
        result = self.pd.get_element_profile(Element("O"), Composition("Li2O"))
        for d1, d2 in zip(expected, result):
            self.assertAlmostEqual(d1["evolution"], d2["evolution"])
            self.assertAlmostEqual(d1["chempot"], d2["chempot"])
            self.assertEqual(d1["reaction"], str(d2["reaction"]))

    def test_get_get_chempot_range_map(self):
        elements = [el for el in self.pd.elements if el.symbol != "Fe"]
        self.assertEqual(len(self.pd.get_chempot_range_map(elements)), 10)

    def test_getmu_vertices_stability_phase(self):
        results = self.pd.getmu_vertices_stability_phase(
            Composition("LiFeO2"), Element("O"))
        self.assertAlmostEqual(len(results), 6)
        test_equality = False
        for c in results:
            if (abs(c[Element("O")] + 7.115) < 1e-2
                    and abs(c[Element("Fe")] + 6.596) < 1e-2
                    and abs(c[Element("Li")] + 3.931) < 1e-2):
                test_equality = True
        self.assertTrue(test_equality,
                        "there is an expected vertex missing in the list")

    def test_getmu_range_stability_phase(self):
        results = self.pd.get_chempot_range_stability_phase(
            Composition("LiFeO2"), Element("O"))
        self.assertAlmostEqual(results[Element("O")][1], -4.4501812249999997)
        self.assertAlmostEqual(results[Element("Fe")][0], -6.5961470999999996)
        self.assertAlmostEqual(results[Element("Li")][0], -3.6250022625000007)

    def test_get_hull_energy(self):
        for entry in self.pd.stable_entries:
            h_e = self.pd.get_hull_energy(entry.composition)
            self.assertAlmostEqual(h_e, entry.energy)
            n_h_e = self.pd.get_hull_energy(
                entry.composition.fractional_composition)
            self.assertAlmostEqual(n_h_e, entry.energy_per_atom)

    def test_1d_pd(self):
        entry = PDEntry("H", 0)
        pd = PhaseDiagram([entry])
        decomp, e = pd.get_decomp_and_e_above_hull(PDEntry("H", 1))
        self.assertAlmostEqual(e, 1)
        self.assertAlmostEqual(decomp[entry], 1.0)

    def test_get_critical_compositions_fractional(self):
        c1 = Composition("Fe2O3").fractional_composition
        c2 = Composition("Li3FeO4").fractional_composition
        c3 = Composition("Li2O").fractional_composition

        comps = self.pd.get_critical_compositions(c1, c2)
        expected = [
            Composition("Fe2O3").fractional_composition,
            Composition("Li0.3243244Fe0.1621621O0.51351349"),
            Composition("Li3FeO4").fractional_composition,
        ]
        for crit, exp in zip(comps, expected):
            self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5))

        comps = self.pd.get_critical_compositions(c1, c3)
        expected = [
            Composition("Fe0.4O0.6"),
            Composition("LiFeO2").fractional_composition,
            Composition("Li5FeO4").fractional_composition,
            Composition("Li2O").fractional_composition,
        ]
        for crit, exp in zip(comps, expected):
            self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5))

    def test_get_critical_compositions(self):
        c1 = Composition("Fe2O3")
        c2 = Composition("Li3FeO4")
        c3 = Composition("Li2O")

        comps = self.pd.get_critical_compositions(c1, c2)
        expected = [
            Composition("Fe2O3"),
            Composition("Li0.3243244Fe0.1621621O0.51351349") * 7.4,
            Composition("Li3FeO4"),
        ]
        for crit, exp in zip(comps, expected):
            self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5))

        comps = self.pd.get_critical_compositions(c1, c3)
        expected = [
            Composition("Fe2O3"),
            Composition("LiFeO2"),
            Composition("Li5FeO4") / 3,
            Composition("Li2O"),
        ]
        for crit, exp in zip(comps, expected):
            self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5))

        # Don't fail silently if input compositions aren't in phase diagram
        # Can be very confusing if you're working with a GrandPotentialPD
        self.assertRaises(
            ValueError,
            self.pd.get_critical_compositions,
            Composition("Xe"),
            Composition("Mn"),
        )

        # For the moment, should also fail even if compositions are in the gppd
        # because it isn't handled properly
        gppd = GrandPotentialPhaseDiagram(self.pd.all_entries, {"Xe": 1},
                                          self.pd.elements + [Element("Xe")])
        self.assertRaises(
            ValueError,
            gppd.get_critical_compositions,
            Composition("Fe2O3"),
            Composition("Li3FeO4Xe"),
        )

        # check that the function still works though
        comps = gppd.get_critical_compositions(c1, c2)
        expected = [
            Composition("Fe2O3"),
            Composition("Li0.3243244Fe0.1621621O0.51351349") * 7.4,
            Composition("Li3FeO4"),
        ]
        for crit, exp in zip(comps, expected):
            self.assertTrue(crit.almost_equals(exp, rtol=0, atol=1e-5))

        # case where the endpoints are identical
        self.assertEqual(self.pd.get_critical_compositions(c1, c1 * 2),
                         [c1, c1 * 2])

    def test_get_composition_chempots(self):
        c1 = Composition("Fe3.1O4")
        c2 = Composition("Fe3.2O4.1Li0.01")

        e1 = self.pd.get_hull_energy(c1)
        e2 = self.pd.get_hull_energy(c2)

        cp = self.pd.get_composition_chempots(c1)
        calc_e2 = e1 + sum(cp[k] * v for k, v in (c2 - c1).items())
        self.assertAlmostEqual(e2, calc_e2)

    def test_get_all_chempots(self):
        c1 = Composition("Fe3.1O4")
        c2 = Composition("FeO")

        cp1 = self.pd.get_all_chempots(c1)
        cpresult = {
            Element("Li"): -4.077061954999998,
            Element("Fe"): -6.741593864999999,
            Element("O"): -6.969907375000003,
        }

        for elem, energy in cpresult.items():
            self.assertAlmostEqual(cp1["Fe3O4-FeO-LiFeO2"][elem], energy)

        cp2 = self.pd.get_all_chempots(c2)
        cpresult = {
            Element("O"): -7.115354140000001,
            Element("Fe"): -6.5961471,
            Element("Li"): -3.9316151899999987,
        }

        for elem, energy in cpresult.items():
            self.assertAlmostEqual(cp2["FeO-LiFeO2-Fe"][elem], energy)

    def test_to_from_dict(self):

        # test round-trip for other entry types such as ComputedEntry
        entry = ComputedEntry("H", 0.0, 0.0, entry_id="test")
        pd = PhaseDiagram([entry])
        d = pd.as_dict()
        pd_roundtrip = PhaseDiagram.from_dict(d)
        self.assertEqual(pd.all_entries[0].entry_id,
                         pd_roundtrip.all_entries[0].entry_id)
Esempio n. 5
0
    def process_item(self, item):
        """
        Process the list of entries into thermo docs for each sandbox
        Args:
            item (set(entry)): a list of entries to process into a phase diagram

        Returns:
            [dict]: a list of thermo dictionaries to update thermo with
        """

        docs = []

        sandboxes, entries = item
        entries = self.compatibility.process_entries(entries)

        # determine chemsys
        chemsys = "-".join(
            sorted(
                set([
                    el.symbol for e in entries for el in e.composition.elements
                ])))

        self.logger.debug(
            f"Procesing {len(entries)} entries for {chemsys} - {sandboxes}")

        try:
            pd = PhaseDiagram(entries)

            docs = []

            for e in entries:
                (decomp, ehull) = pd.get_decomp_and_e_above_hull(e)

                d = {
                    self.thermo.key: e.entry_id,
                    "thermo": {
                        "energy": e.uncorrected_energy,
                        "energy_per_atom":
                        e.uncorrected_energy / e.composition.num_atoms,
                        "formation_energy_per_atom":
                        pd.get_form_energy_per_atom(e),
                        "e_above_hull": ehull,
                        "is_stable": e in pd.stable_entries,
                    },
                }

                # Store different info if stable vs decomposes
                if d["thermo"]["is_stable"]:
                    d["thermo"][
                        "eq_reaction_e"] = pd.get_equilibrium_reaction_energy(
                            e)
                else:
                    d["thermo"]["decomposes_to"] = [{
                        "task_id": de.entry_id,
                        "formula": de.composition.formula,
                        "amount": amt,
                    } for de, amt in decomp.items()]

                d["thermo"]["entry"] = e.as_dict()
                d["thermo"][
                    "explanation"] = self.compatibility.get_explanation_dict(e)

                elsyms = sorted(
                    set([el.symbol for el in e.composition.elements]))
                d["chemsys"] = "-".join(elsyms)
                d["nelements"] = len(elsyms)
                d["elements"] = list(elsyms)
                d["_sbxn"] = list(sandboxes)

                docs.append(d)
        except PhaseDiagramError as p:
            elsyms = []
            for e in entries:
                elsyms.extend([el.symbol for el in e.composition.elements])

            self.logger.warning("Phase diagram errorin chemsys {}: {}".format(
                "-".join(sorted(set(elsyms))), p))
            return []

        return docs