Example #1
0
 def setUp(self):
     entrylist = list()
     weights = list()
     comp = Composition("Mn2O3")
     entry = PDEntry(comp, 49)
     entrylist.append(PourbaixEntry(entry))
     weights.append(1.0)
     comp = Ion.from_formula("MnO4[-]")
     entry = IonEntry(comp, 25)
     entrylist.append(PourbaixEntry(entry))
     weights.append(0.25)
     comp = Composition("Fe2O3")
     entry = PDEntry(comp, 50)
     entrylist.append(PourbaixEntry(entry))
     weights.append(0.5)
     comp = Ion.from_formula("Fe[2+]")
     entry = IonEntry(comp, 15)
     entrylist.append(PourbaixEntry(entry))
     weights.append(2.5)
     comp = Ion.from_formula("Fe[3+]")
     entry = IonEntry(comp, 20)
     entrylist.append(PourbaixEntry(entry))
     weights.append(1.5)
     self.weights = weights
     self.entrylist = entrylist
     self.multientry = MultiEntry(entrylist, weights)
Example #2
0
 def setUp(self):
     entrylist = list()
     weights = list()
     comp = Composition("Mn2O3")
     entry = PDEntry(comp, 49)
     entrylist.append(PourbaixEntry(entry))
     weights.append(1.0)
     comp = Ion.from_formula("MnO4[-]")
     entry = IonEntry(comp, 25)
     entrylist.append(PourbaixEntry(entry))
     weights.append(0.25)
     comp = Composition("Fe2O3")
     entry = PDEntry(comp, 50)
     entrylist.append(PourbaixEntry(entry))
     weights.append(0.5)
     comp = Ion.from_formula("Fe[2+]")
     entry = IonEntry(comp, 15)
     entrylist.append(PourbaixEntry(entry))
     weights.append(2.5)
     comp = Ion.from_formula("Fe[3+]")
     entry = IonEntry(comp, 20)
     entrylist.append(PourbaixEntry(entry))
     weights.append(1.5)
     self.weights = weights
     self.entrylist = entrylist
     self.multientry = MultiEntry(entrylist, weights)
Example #3
0
 def setUp(self):
     self.comp = list()
     self.comp.append(Ion.from_formula("Li+"))
     self.comp.append(Ion.from_formula("MnO4-"))
     self.comp.append(Ion.from_formula("Mn++"))
     self.comp.append(Ion.from_formula("PO3-2"))
     self.comp.append(Ion.from_formula("Fe(CN)6-3"))
     self.comp.append(Ion.from_formula("Fe(CN)6----"))
     self.comp.append(Ion.from_formula("Fe2((PO4)3(CO3)5)2-3"))
     self.comp.append(Ion.from_formula("Ca[2+]"))
     self.comp.append(Ion.from_formula("NaOH(aq)"))
Example #4
0
 def setUp(self):
     self.comp = list()
     self.comp.append(Ion.from_formula("Li+"))
     self.comp.append(Ion.from_formula("MnO4-"))
     self.comp.append(Ion.from_formula("Mn++"))
     self.comp.append(Ion.from_formula("PO3-2"))
     self.comp.append(Ion.from_formula("Fe(CN)6-3"))
     self.comp.append(Ion.from_formula("Fe(CN)6----"))
     self.comp.append(Ion.from_formula("Fe2((PO4)3(CO3)5)2-3"))
     self.comp.append(Ion.from_formula("Ca[2+]"))
     self.comp.append(Ion.from_formula("NaOH(aq)"))
Example #5
0
 def len_elts(entry):
     if "(s)" in entry:
         comp = Composition(entry[:-3])
     else:
         comp = Ion.from_formula(entry)
     return len([el for el in comp.elements if el not in
                 [Element("H"), Element("O")]])
Example #6
0
 def len_elts(entry):
     if "(s)" in entry:
         comp = Composition(entry[:-3])
     else:
         comp = Ion.from_formula(entry)
     return len([el for el in comp.elements if el not in
                 [Element("H"), Element("O")]])
Example #7
0
    def test_mpr_pipeline(self):
        from pymatgen.ext.matproj import MPRester

        mpr = MPRester()
        data = mpr.get_pourbaix_entries(["Zn"])
        pbx = PourbaixDiagram(data, filter_solids=True, conc_dict={"Zn": 1e-8})
        pbx.find_stable_entry(10, 0)

        data = mpr.get_pourbaix_entries(["Ag", "Te"])
        pbx = PourbaixDiagram(data, filter_solids=True, conc_dict={"Ag": 1e-8, "Te": 1e-8})
        self.assertEqual(len(pbx.stable_entries), 30)
        test_entry = pbx.find_stable_entry(8, 2)
        self.assertAlmostEqual(test_entry.energy, 2.3894017960000009, 1)

        # Test custom ions
        entries = mpr.get_pourbaix_entries(["Sn", "C", "Na"])
        ion = IonEntry(Ion.from_formula("NaO28H80Sn12C24+"), -161.676)
        custom_ion_entry = PourbaixEntry(ion, entry_id="my_ion")
        pbx = PourbaixDiagram(
            entries + [custom_ion_entry],
            filter_solids=True,
            comp_dict={"Na": 1, "Sn": 12, "C": 24},
        )
        self.assertAlmostEqual(pbx.get_decomposition_energy(custom_ion_entry, 5, 2), 2.1209002582, 1)

        # Test against ion sets with multiple equivalent ions (Bi-V regression)
        entries = mpr.get_pourbaix_entries(["Bi", "V"])
        pbx = PourbaixDiagram(entries, filter_solids=True, conc_dict={"Bi": 1e-8, "V": 1e-8})
        self.assertTrue(all(["Bi" in entry.composition and "V" in entry.composition for entry in pbx.all_entries]))
Example #8
0
    def test_mpr_pipeline(self):
        from pymatgen import MPRester
        mpr = MPRester()
        data = mpr.get_pourbaix_entries(["Zn"])
        pbx = PourbaixDiagram(data, filter_solids=True, conc_dict={"Zn": 1e-8})
        pbx.find_stable_entry(10, 0)

        data = mpr.get_pourbaix_entries(["Ag", "Te"])
        pbx = PourbaixDiagram(data,
                              filter_solids=True,
                              conc_dict={
                                  "Ag": 1e-8,
                                  "Te": 1e-8
                              })
        self.assertEqual(len(pbx.stable_entries), 30)
        test_entry = pbx.find_stable_entry(8, 2)
        self.assertAlmostEqual(test_entry.energy, 2.3936747835000016, 3)

        # Test custom ions
        entries = mpr.get_pourbaix_entries(["Sn", "C", "Na"])
        ion = IonEntry(Ion.from_formula("NaO28H80Sn12C24+"), -161.676)
        custom_ion_entry = PourbaixEntry(ion, entry_id='my_ion')
        pbx = PourbaixDiagram(entries + [custom_ion_entry],
                              filter_solids=True,
                              comp_dict={
                                  "Na": 1,
                                  "Sn": 12,
                                  "C": 24
                              })
        self.assertAlmostEqual(
            pbx.get_decomposition_energy(custom_ion_entry, 5, 2),
            8.31202738629504, 2)
Example #9
0
    def test_read_write_csv(self):
        Zn_solids = ["Zn", "ZnO", "ZnO2"]
        sol_g = [0.0, -3.338, -1.315]
        Zn_ions = ["Zn[2+]", "ZnOH[+]", "HZnO2[-]", "ZnO2[2-]", "ZnO"]
        liq_g = [-1.527, -3.415, -4.812, -4.036, -2.921]
        liq_conc = [1e-6, 1e-6, 1e-6, 1e-6, 1e-6]
        solid_entry = list()
        for sol in Zn_solids:
            comp = Composition(sol)
            delg = sol_g[Zn_solids.index(sol)]
            solid_entry.append(PourbaixEntry(PDEntry(comp, delg)))
        ion_entry = list()
        for ion in Zn_ions:
            comp_ion = Ion.from_formula(ion)
            delg = liq_g[Zn_ions.index(ion)]
            conc = liq_conc[Zn_ions.index(ion)]
            PoE = PourbaixEntry(IonEntry(comp_ion, delg))
            PoE.conc = conc
            ion_entry.append(PoE)
        entries = solid_entry + ion_entry
        PourbaixEntryIO.to_csv("pourbaix_test_entries.csv", entries)

        (elements,
         entries) = PourbaixEntryIO.from_csv("pourbaix_test_entries.csv")
        self.assertEqual(
            elements, [Element('Zn'),
                       Element('H'), Element('O')], "Wrong elements!")
        self.assertEqual(len(entries), 8, "Wrong number of entries!")
        os.remove("pourbaix_test_entries.csv")
Example #10
0
    def test_read_write_csv(self):
        Zn_solids = ["Zn", "ZnO", "ZnO2"]
        sol_g = [0.0, -3.338, -1.315]
        Zn_ions = ["Zn[2+]", "ZnOH[+]", "HZnO2[-]", "ZnO2[2-]", "ZnO"]
        liq_g = [-1.527, -3.415, -4.812, -4.036, -2.921]
        liq_conc = [1e-6, 1e-6, 1e-6, 1e-6, 1e-6]
        solid_entry = list()
        for sol in Zn_solids:
            comp = Composition(sol)
            delg = sol_g[Zn_solids.index(sol)]
            solid_entry.append(PourbaixEntry(PDEntry(comp, delg)))
        ion_entry = list()
        for ion in Zn_ions:
            comp_ion = Ion.from_formula(ion)
            delg = liq_g[Zn_ions.index(ion)]
            conc = liq_conc[Zn_ions.index(ion)]
            PoE = PourbaixEntry(IonEntry(comp_ion, delg))
            PoE.set_conc(conc)
            ion_entry.append(PoE)
        entries = solid_entry + ion_entry
        PourbaixEntryIO.to_csv("pourbaix_test_entries.csv", entries)

        (elements, entries) = PourbaixEntryIO.from_csv(
            "pourbaix_test_entries.csv")
        self.assertEqual(elements,
                         [Element('Zn'), Element('H'), Element('O')],
                         "Wrong elements!")
        self.assertEqual(len(entries), 8, "Wrong number of entries!")
        os.remove("pourbaix_test_entries.csv")
Example #11
0
 def setUp(self):
     comp = Composition("Mn2O3")
     self.solentry = PDEntry(comp, 49)
     ion = Ion.from_formula("MnO4-")
     self.ionentry = IonEntry(ion, 25)
     self.PxIon = PourbaixEntry(self.ionentry)
     self.PxSol = PourbaixEntry(self.solentry)
     self.PxIon.set_conc(1e-4)
Example #12
0
 def setUp(self):
     comp = Composition("Mn2O3")
     self.solentry = PDEntry(comp, 49)
     ion = Ion.from_formula("MnO4-")
     self.ionentry = IonEntry(ion, 25)
     self.PxIon = PourbaixEntry(self.ionentry)
     self.PxSol = PourbaixEntry(self.solentry)
     self.PxIon.conc = 1e-4
Example #13
0
 def setUp(self):
     # comp = Composition("Mn2O3")
     self.solentry = ComputedEntry("Mn2O3", 49)
     ion = Ion.from_formula("MnO4-")
     self.ionentry = IonEntry(ion, 25)
     self.PxIon = PourbaixEntry(self.ionentry)
     self.PxSol = PourbaixEntry(self.solentry)
     self.PxIon.concentration = 1e-4
Example #14
0
 def setUp(self):
     # comp = Composition("Mn2O3")
     self.solentry = ComputedEntry("Mn2O3", 49)
     ion = Ion.from_formula("MnO4-")
     self.ionentry = IonEntry(ion, 25)
     self.PxIon = PourbaixEntry(self.ionentry)
     self.PxSol = PourbaixEntry(self.solentry)
     self.PxIon.concentration = 1e-4
Example #15
0
    def get_pourbaix_entries(self, chemsys):
        """
        A helper function to get all entries necessary to generate
        a pourbaix diagram from the rest interface.

        Args:
            chemsys ([str]): A list of elements comprising the chemical
                system, e.g. ['Li', 'Fe']
        """
        from pymatgen.analysis.pourbaix.entry import PourbaixEntry, IonEntry
        from pymatgen.analysis.phase_diagram import PhaseDiagram
        from pymatgen.core.ion import Ion
        from pymatgen.entries.compatibility import\
            MaterialsProjectAqueousCompatibility

        chemsys = list(set(chemsys + ['O', 'H']))
        entries = self.get_entries_in_chemsys(chemsys,
                                              property_data=['e_above_hull'],
                                              compatible_only=False)
        compat = MaterialsProjectAqueousCompatibility("Advanced")
        entries = compat.process_entries(entries)
        solid_pd = PhaseDiagram(
            entries)  # Need this to get ion formation energy
        url = '/pourbaix_diagram/reference_data/' + '-'.join(chemsys)
        ion_data = self._make_request(url)

        pbx_entries = []
        for entry in entries:
            if not set(entry.composition.elements)\
                    <= {Element('H'), Element('O')}:
                pbx_entry = PourbaixEntry(entry)
                pbx_entry.g0_replace(solid_pd.get_form_energy(entry))
                pbx_entry.reduced_entry()
                pbx_entries.append(pbx_entry)

        # position the ion energies relative to most stable reference state
        for n, i_d in enumerate(ion_data):
            ion_entry = IonEntry(Ion.from_formula(i_d['Name']), i_d['Energy'])
            refs = [
                e for e in entries
                if e.composition.reduced_formula == i_d['Reference Solid']
            ]
            if not refs:
                raise ValueError("Reference solid not contained in entry list")
            stable_ref = sorted(refs, key=lambda x: x.data['e_above_hull'])[0]
            rf = stable_ref.composition.get_reduced_composition_and_factor()[1]
            solid_diff = solid_pd.get_form_energy(stable_ref)\
                         - i_d['Reference solid energy'] * rf
            elt = i_d['Major_Elements'][0]
            correction_factor = ion_entry.ion.composition[elt]\
                                / stable_ref.composition[elt]
            correction = solid_diff * correction_factor
            pbx_entries.append(
                PourbaixEntry(ion_entry, correction, 'ion-{}'.format(n)))
        return pbx_entries
Example #16
0
    def get_pourbaix_entries(self, chemsys):
        """
        A helper function to get all entries necessary to generate
        a pourbaix diagram from the rest interface.

        Args:
            chemsys ([str]): A list of elements comprising the chemical
                system, e.g. ['Li', 'Fe']
        """
        from pymatgen.analysis.pourbaix.entry import PourbaixEntry, IonEntry
        from pymatgen.analysis.phase_diagram import PhaseDiagram
        from pymatgen.core.ion import Ion
        from pymatgen.entries.compatibility import\
            MaterialsProjectAqueousCompatibility

        chemsys = list(set(chemsys + ['O', 'H']))
        entries = self.get_entries_in_chemsys(
            chemsys, property_data=['e_above_hull'], compatible_only=False)
        compat = MaterialsProjectAqueousCompatibility("Advanced")
        entries = compat.process_entries(entries)
        solid_pd = PhaseDiagram(entries) # Need this to get ion formation energy
        url = '/pourbaix_diagram/reference_data/' + '-'.join(chemsys)
        ion_data = self._make_request(url)

        pbx_entries = []
        for entry in entries:
            if not set(entry.composition.elements)\
                    <= {Element('H'), Element('O')}:
                pbx_entry = PourbaixEntry(entry)
                pbx_entry.g0_replace(solid_pd.get_form_energy(entry))
                pbx_entry.reduced_entry()
                pbx_entries.append(pbx_entry)

        # position the ion energies relative to most stable reference state
        for n, i_d in enumerate(ion_data):
            ion_entry = IonEntry(Ion.from_formula(i_d['Name']), i_d['Energy'])
            refs = [e for e in entries
                    if e.composition.reduced_formula == i_d['Reference Solid']]
            if not refs:
                raise ValueError("Reference solid not contained in entry list")
            stable_ref = sorted(refs, key=lambda x: x.data['e_above_hull'])[0]
            rf = stable_ref.composition.get_reduced_composition_and_factor()[1]
            solid_diff = solid_pd.get_form_energy(stable_ref)\
                         - i_d['Reference solid energy'] * rf
            elt = i_d['Major_Elements'][0]
            correction_factor = ion_entry.ion.composition[elt]\
                                / stable_ref.composition[elt]
            correction = solid_diff * correction_factor
            pbx_entries.append(PourbaixEntry(ion_entry, correction,
                                             'ion-{}'.format(n)))
        return pbx_entries
Example #17
0
    def test_get_ion_entries(self, mpr):
        entries = mpr.get_entries_in_chemsys("Ti-O-H")
        pd = PhaseDiagram(entries)
        ion_entry_data = mpr.get_ion_reference_data_for_chemsys("Ti-O-H")
        ion_entries = mpr.get_ion_entries(pd, ion_entry_data)
        assert len(ion_entries) == 5
        assert all([isinstance(i, IonEntry) for i in ion_entries])

        # test an incomplete phase diagram
        entries = mpr.get_entries_in_chemsys("Ti-O")
        pd = PhaseDiagram(entries)
        with pytest.raises(ValueError,
                           match="The phase diagram chemical system"):
            mpr.get_ion_entries(pd)

        # test ion energy calculation
        ion_data = mpr.get_ion_reference_data_for_chemsys('S')
        ion_ref_comps = [
            Ion.from_formula(d["data"]["RefSolid"]).composition
            for d in ion_data
        ]
        ion_ref_elts = set(
            itertools.chain.from_iterable(i.elements for i in ion_ref_comps))
        ion_ref_entries = mpr.get_entries_in_chemsys(
            list([str(e) for e in ion_ref_elts] + ["O", "H"]))
        mpc = MaterialsProjectAqueousCompatibility()
        ion_ref_entries = mpc.process_entries(ion_ref_entries)
        ion_ref_pd = PhaseDiagram(ion_ref_entries)
        ion_entries = mpr.get_ion_entries(ion_ref_pd, ion_ref_data=ion_data)

        # In ion ref data, SO4-2 is -744.27 kJ/mol; ref solid is -1,279.0 kJ/mol
        # so the ion entry should have an energy (-744.27 +1279) = 534.73 kJ/mol
        # or 5.542 eV/f.u. above the energy of Na2SO4
        so4_two_minus = [
            e for e in ion_entries if e.ion.reduced_formula == "SO4[-2]"
        ][0]

        # the ref solid is Na2SO4, ground state mp-4770
        # the rf factor correction is necessary to make sure the composition
        # of the reference solid is normalized to a single formula unit
        ref_solid_entry = [
            e for e in ion_ref_entries if e.entry_id == 'mp-4770'
        ][0]
        rf = ref_solid_entry.composition.get_reduced_composition_and_factor(
        )[1]
        solid_energy = ion_ref_pd.get_form_energy(ref_solid_entry) / rf

        assert np.allclose(so4_two_minus.energy,
                           solid_energy + 5.542,
                           atol=1e-3)
Example #18
0
    def test_special_formulas(self):
        special_formulas = [
            ("Cl-", "Cl[-1]"),
            ("H+", "H[+1]"),
            ("F-", "F[-1]"),
            ("H4O4", "H2O2(aq)"),
            ("OH-", "OH[-1]"),
            ("CH3COO-", "CH3COO[-1]"),
            ("CH3COOH", "CH3COOH(aq)"),
            ("CH3OH", "CH3OH(aq)"),
            ("H4CO", "CH3OH(aq)"),
            ("C2H6O", "C2H5OH(aq)"),
            ("C3H8O", "C3H7OH(aq)"),
            ("C4H10O", "C4H9OH(aq)"),
            ("Fe(OH)4+", "FeO2.2H2O[+1]"),
            ("Zr(OH)4", "ZrO2.2H2O(aq)"),
        ]

        for tup in special_formulas:
            self.assertEqual(Ion.from_formula(tup[0]).reduced_formula, tup[1])

        self.assertEqual(Ion.from_formula("Fe(OH)4+").get_reduced_formula_and_factor(hydrates=False), ("Fe(OH)4", 1))
        self.assertEqual(Ion.from_formula("Zr(OH)4").get_reduced_formula_and_factor(hydrates=False), ("Zr(OH)4", 1))
Example #19
0
    def __init__(self, entries, comp_dict=None):
        """
        Args:
            entries:
                Entries list containing both Solids and Ions
            comp_dict:
                Dictionary of compositions
        """
        self._solid_entries = list()
        self._ion_entries = list()
        for entry in entries:
            if entry.phase_type == "Solid":
                self._solid_entries.append(entry)
            elif entry.phase_type == "Ion":
                self._ion_entries.append(entry)
            else:
                raise StandardError("Incorrect Phase type - needs to be \
                Pourbaix entry of phase type Ion/Solid")
        self._unprocessed_entries = self._solid_entries + self._ion_entries
        self._elt_comp = comp_dict
        if comp_dict:
            self._multielement = True
            self.pourbaix_elements = [key for key in comp_dict]
            w = [comp_dict[key] for key in comp_dict]
            A = []
            for comp in comp_dict:
                m = re.search(r"\[([^\[\]]+)\]|\(aq\)", comp)
                if m:
                    comp_obj = Ion.from_formula(comp)
                else:
                    comp_obj = Composition.from_formula(comp)
                Ai = []
                for elt in self.pourbaix_elements:
                    Ai.append(comp_obj[Element(elt)])
                A.append(Ai)
            A = np.array(A).T.astype(float)
            w = np.array(w)
            A /= np.dot([A[i].sum() for i in xrange(len(A))], w)
            x = np.linalg.solve(A, w)
            self._elt_comp = dict(zip(self.pourbaix_elements, x))

        else:
            self._multielement = False
            self.pourbaix_elements = [
                el.symbol for el in entries[0].composition.elements
                if el.symbol not in ["H", "O"]
            ]
        self._make_pourbaixdiagram()
Example #20
0
    def __init__(self, entries, comp_dict=None):
        """
        Args:
            entries:
                Entries list containing both Solids and Ions
            comp_dict:
                Dictionary of compositions
        """
        self._solid_entries = list()
        self._ion_entries = list()
        for entry in entries:
            if entry.phase_type == "Solid":
                self._solid_entries.append(entry)
            elif entry.phase_type == "Ion":
                self._ion_entries.append(entry)
            else:
                raise StandardError("Incorrect Phase type - needs to be \
                Pourbaix entry of phase type Ion/Solid")
        self._unprocessed_entries = self._solid_entries + self._ion_entries
        self._elt_comp = comp_dict
        if comp_dict:
            self._multielement = True
            self.pourbaix_elements = [key for key in comp_dict]
            w = [comp_dict[key] for key in comp_dict]
            A = []
            for comp in comp_dict:
                m = re.search(r"\[([^\[\]]+)\]|\(aq\)", comp)
                if m:
                    comp_obj = Ion.from_formula(comp)
                else:
                    comp_obj = Composition.from_formula(comp)
                Ai = []
                for elt in self.pourbaix_elements:
                    Ai.append(comp_obj[Element(elt)])
                A.append(Ai)
            A = np.array(A).T.astype(float)
            w = np.array(w)
            A /= np.dot([A[i].sum() for i in xrange(len(A))], w)
            x = np.linalg.solve(A, w)
            self._elt_comp = dict(zip(self.pourbaix_elements, x))

        else:
            self._multielement = False
            self.pourbaix_elements = [el.symbol
                                      for el in entries[0].composition.elements
                                      if el.symbol not in ["H", "O"]]
        self._make_pourbaixdiagram()
Example #21
0
    def test_get_decomposition(self):
        # Test a stable entry to ensure that it's zero in the stable region
        entry = self.test_data["Zn"][12]  # Should correspond to mp-2133
        self.assertAlmostEqual(
            self.pbx.get_decomposition_energy(entry, 10, 1),
            0.0,
            5,
            "Decomposition energy of ZnO is not 0.",
        )

        # Test an unstable entry to ensure that it's never zero
        entry = self.test_data["Zn"][11]
        ph, v = np.meshgrid(np.linspace(0, 14), np.linspace(-2, 4))
        result = self.pbx_nofilter.get_decomposition_energy(entry, ph, v)
        self.assertTrue((result >= 0).all(),
                        "Unstable energy has hull energy of 0 or less")

        # Test an unstable hydride to ensure HER correction works
        self.assertAlmostEqual(
            self.pbx.get_decomposition_energy(entry, -3, -2), 3.6979147983333)
        # Test a list of pHs
        self.pbx.get_decomposition_energy(entry, np.linspace(0, 2, 5), 2)

        # Test a list of Vs
        self.pbx.get_decomposition_energy(entry, 4, np.linspace(-3, 3, 10))

        # Test a set of matching arrays
        ph, v = np.meshgrid(np.linspace(0, 14), np.linspace(-3, 3))
        self.pbx.get_decomposition_energy(entry, ph, v)

        # Test custom ions
        entries = self.test_data["C-Na-Sn"]
        ion = IonEntry(Ion.from_formula("NaO28H80Sn12C24+"), -161.676)
        custom_ion_entry = PourbaixEntry(ion, entry_id="my_ion")
        pbx = PourbaixDiagram(
            entries + [custom_ion_entry],
            filter_solids=True,
            comp_dict={
                "Na": 1,
                "Sn": 12,
                "C": 24
            },
        )
        self.assertAlmostEqual(
            pbx.get_decomposition_energy(custom_ion_entry, 5, 2), 2.1209002582,
            1)
Example #22
0
def ion_or_solid_comp_object(formula):
    """
    Returns either an ion object or composition object given
    a formula.

    Args:
        formula: String formula. Eg. of ion: NaOH(aq), Na[+];
            Eg. of solid: Fe2O3(s), Fe(s), Na2O

    Returns:
        Composition/Ion object
    """
    m = re.search(r"\[([^\[\]]+)\]|\(aq\)", formula)
    if m:
        comp_obj = Ion.from_formula(formula)
    elif re.search(r"\(s\)", formula):
        comp_obj = Composition(formula[:-3])
    else:
        comp_obj = Composition(formula)
    return comp_obj
Example #23
0
def ion_or_solid_comp_object(formula):
    """
    Returns either an ion object or composition object given
    a formula.

    Args:
        formula: String formula. Eg. of ion: NaOH(aq), Na[+];
            Eg. of solid: Fe2O3(s), Fe(s), Na2O

    Returns:
        Composition/Ion object
    """
    m = re.search(r"\[([^\[\]]+)\]|\(aq\)", formula)
    if m:
        comp_obj = Ion.from_formula(formula)
    elif re.search(r"\(s\)", formula):
        comp_obj = Composition(formula[:-3])
    else:
        comp_obj = Composition(formula)
    return comp_obj
Example #24
0
    def from_csv(filename):
        """
        Imports PourbaixEntries from a csv.

        Args:
            filename - Filename to import from.

        Returns:
            List of Entries
        """
        import csv
        reader = csv.reader(open(filename, "rb"),
                            delimiter=",",
                            quotechar="\"",
                            quoting=csv.QUOTE_MINIMAL)
        entries = list()
        header_read = False
        for row in reader:
            if not header_read:
                elements = row[1:(len(row) - 4)]
                header_read = True
            else:
                name = row[0]
                energy = float(row[-4])
                conc = float(row[-1])
                comp = dict()
                for ind in range(1, len(row) - 4):
                    if float(row[ind]) > 0:
                        comp[Element(elements[ind - 1])] = float(row[ind])
                phase_type = row[-3]
                if phase_type == "Ion":
                    PoE = PourbaixEntry(
                        IonEntry(Ion.from_formula(name), energy))
                    PoE.set_conc(conc)
                    PoE.set_name(name)
                    entries.append(PoE)
                else:
                    entries.append(
                        PourbaixEntry(PDEntry(Composition(comp), energy)))
        elements = [Element(el) for el in elements]
        return elements, entries
Example #25
0
    def from_csv(filename):
        """
        Imports PourbaixEntries from a csv.

        Args:
            filename: Filename to import from.

        Returns:
            List of Entries
        """
        with open(filename, "r") as f:
            reader = csv.reader(f, delimiter=unicode2str(","),
                                quotechar=unicode2str("\""),
                                quoting=csv.QUOTE_MINIMAL)
            entries = list()
            header_read = False
            for row in reader:
                if not header_read:
                    elements = row[1:(len(row) - 4)]
                    header_read = True
                else:
                    name = row[0]
                    energy = float(row[-4])
                    conc = float(row[-1])
                    comp = dict()
                    for ind in range(1, len(row) - 4):
                        if float(row[ind]) > 0:
                            comp[Element(elements[ind - 1])] = float(row[ind])
                    phase_type = row[-3]
                    if phase_type == "Ion":
                        PoE = PourbaixEntry(IonEntry(Ion.from_formula(name),
                                                     energy))
                        PoE.conc = conc
                        PoE.name = name
                        entries.append(PoE)
                    else:
                        entries.append(PourbaixEntry(PDEntry(Composition(comp),
                                                             energy)))
        elements = [Element(el) for el in elements]
        return elements, entries
Example #26
0
    def test_mpr_pipeline(self):
        from pymatgen import MPRester
        mpr = MPRester()
        data = mpr.get_pourbaix_entries(["Zn"])
        pbx = PourbaixDiagram(data, filter_solids=True, conc_dict={"Zn": 1e-8})
        pbx.find_stable_entry(10, 0)

        data = mpr.get_pourbaix_entries(["Ag", "Te"])
        pbx = PourbaixDiagram(data, filter_solids=True,
                              conc_dict={"Ag": 1e-8, "Te": 1e-8})
        self.assertEqual(len(pbx.stable_entries), 30)
        test_entry = pbx.find_stable_entry(8, 2)
        self.assertAlmostEqual(test_entry.energy, 2.393900378500001)

        # Test custom ions
        entries = mpr.get_pourbaix_entries(["Sn", "C", "Na"])
        ion = IonEntry(Ion.from_formula("NaO28H80Sn12C24+"), -161.676)
        custom_ion_entry = PourbaixEntry(ion, entry_id='my_ion')
        pbx = PourbaixDiagram(entries + [custom_ion_entry], filter_solids=True,
                              comp_dict={"Na": 1, "Sn": 12, "C": 24})
        self.assertAlmostEqual(pbx.get_decomposition_energy(custom_ion_entry, 5, 2),
                               8.31082110278154)
Example #27
0
def mke_pour_ion_entr(mtnme, ion_dict, stable_solids_minus_h2o, ref_state, entries_aqcorr, ref_dict):
    """
    """
    #| -  mke_pour_ion_entr
    from pymatgen import Element # Accesses properties of element
    from pymatgen.core.ion import Ion
    from pymatgen.phasediagram.maker import PhaseDiagram
    from pymatgen.analysis.pourbaix.entry import PourbaixEntry, IonEntry

    from pd_make import ref_entry_find, ref_entry_stoich
    pd = PhaseDiagram(entries_aqcorr)

    ref_entry = ref_entry_find(stable_solids_minus_h2o, ref_state)
    ref_stoich_fact = ref_entry_stoich(ref_entry)

    ## Calculate DFT reference E for ions (Persson et al, PRB (2012))
    dft_for_e=pd.get_form_energy(ref_entry)/ref_stoich_fact # DFT formation E, normalized by composition "factor"
    ion_correction_1 = dft_for_e-ref_dict[ref_state] # Difference of DFT form E and exp for E of reference

    el = Element(mtnme)
    pbx_ion_entries_1 = []
    for id in ion_dict:
        comp = Ion.from_formula(id['Name'])     # Ion name-> Ion comp name (ex. Fe[3+] -> Ion: Fe1 +3)
                               # comp.composition[el] : number of Fe atoms in ion
        num_el_ref = (ref_entry.composition[el]) / ref_stoich_fact # number of element atoms in reference
        factor = comp.composition[el] / num_el_ref    # Stoicheometric factor for ionic correction
                                   # (i.e. Fe2O3 ref but calc E for Fe[2+] ion)
        energy = id['Energy'] + ion_correction_1 * factor

        #TEMP_PRINT
        if id['Name']=='Pd[2+]':
            energy = 123
        # print id['Name']

        pbx_entry_ion = PourbaixEntry(IonEntry(comp, energy))
        pbx_entry_ion.name = id['Name']
        pbx_ion_entries_1.append(pbx_entry_ion)

    return pbx_ion_entries_1
Example #28
0
Created on Wed Feb 28 15:40:02 2018

@author: jherfson
"""
from pymatgen.analysis.pourbaix.entry import PourbaixEntry, IonEntry  #, MultiEntry
from pymatgen.analysis.pourbaix.entry import PourbaixEntryIO
from pymatgen.analysis.phase_diagram import PDEntry
from pymatgen.core.ion import Ion
from pymatgen.core.structure import Composition

Zn_solids = ["Zn", "ZnO", "ZnO2"]
sol_g = [0.0, -3.338, -1.315]
Zn_ions = ["Zn[2+]", "ZnOH[+]", "HZnO2[-]", "ZnO2[2-]", "ZnO"]
liq_g = [-1.527, -3.415, -4.812, -4.036, -2.921]
liq_conc = [1e-6, 1e-6, 1e-6, 1e-6, 1e-6]
solid_entry = list()
for sol in Zn_solids:
    comp = Composition(sol)
    delg = sol_g[Zn_solids.index(sol)]
    solid_entry.append(PourbaixEntry(PDEntry(comp, delg)))
ion_entry = list()
for ion in Zn_ions:
    comp_ion = Ion.from_formula(ion)
    delg = liq_g[Zn_ions.index(ion)]
    conc = liq_conc[Zn_ions.index(ion)]
    PoE = PourbaixEntry(IonEntry(comp_ion, delg))
    PoE.conc = conc
    ion_entry.append(PoE)
entries = solid_entry + ion_entry
PourbaixEntryIO.to_csv("pourbaix_test_entries.csv", entries)
Example #29
0
    def get_pourbaix_entries(self, chemsys):
        """
        A helper function to get all entries necessary to generate
        a pourbaix diagram from the rest interface.

        Args:
            chemsys ([str]): A list of elements comprising the chemical
                system, e.g. ['Li', 'Fe']
        """
        from pymatgen.analysis.pourbaix_diagram import PourbaixEntry, IonEntry
        from pymatgen.analysis.phase_diagram import PhaseDiagram
        from pymatgen.core.ion import Ion
        from pymatgen.entries.compatibility import\
            MaterialsProjectAqueousCompatibility

        pbx_entries = []

        # Get ion entries first, because certain ions have reference
        # solids that aren't necessarily in the chemsys (Na2SO4)
        url = '/pourbaix_diagram/reference_data/' + '-'.join(chemsys)
        ion_data = self._make_request(url)
        ion_ref_comps = [Composition(d['Reference Solid']) for d in ion_data]
        ion_ref_elts = list(itertools.chain.from_iterable(
            i.elements for i in ion_ref_comps))
        ion_ref_entries = self.get_entries_in_chemsys(
            list(set([str(e) for e in ion_ref_elts] + ['O', 'H'])),
            property_data=['e_above_hull'], compatible_only=False)
        compat = MaterialsProjectAqueousCompatibility("Advanced")
        ion_ref_entries = compat.process_entries(ion_ref_entries)
        ion_ref_pd = PhaseDiagram(ion_ref_entries)

        # position the ion energies relative to most stable reference state
        for n, i_d in enumerate(ion_data):
            ion_entry = IonEntry(Ion.from_formula(i_d['Name']), i_d['Energy'])
            refs = [e for e in ion_ref_entries
                    if e.composition.reduced_formula == i_d['Reference Solid']]
            if not refs:
                raise ValueError("Reference solid not contained in entry list")
            stable_ref = sorted(refs, key=lambda x: x.data['e_above_hull'])[0]
            rf = stable_ref.composition.get_reduced_composition_and_factor()[1]
            solid_diff = ion_ref_pd.get_form_energy(stable_ref)\
                         - i_d['Reference solid energy'] * rf
            elt = i_d['Major_Elements'][0]
            correction_factor = ion_entry.ion.composition[elt]\
                                / stable_ref.composition[elt]
            ion_entry.energy += solid_diff * correction_factor
            pbx_entries.append(PourbaixEntry(ion_entry, 'ion-{}'.format(n)))
            # import nose; nose.tools.set_trace()

        # Construct the solid pourbaix entries from filtered ion_ref entries
        extra_elts = set(ion_ref_elts) - {Element(s) for s in chemsys}\
            - {Element('H'), Element('O')}
        for entry in ion_ref_entries:
            entry_elts = set(entry.composition.elements)
            # Ensure no OH chemsys or extraneous elements from ion references
            if not (entry_elts <= {Element('H'), Element('O')} or \
                    extra_elts.intersection(entry_elts)):
                # replace energy with formation energy, use dict to
                # avoid messing with the ion_ref_pd and to keep all old params
                form_e = ion_ref_pd.get_form_energy(entry)
                new_entry = deepcopy(entry)
                new_entry.uncorrected_energy = form_e
                new_entry.correction = 0.0
                pbx_entry = PourbaixEntry(new_entry)
                if entry.entry_id == "mp-697146":
                    pass
                    # import nose; nose.tools.set_trace()
                # pbx_entry.reduced_entry()
                pbx_entries.append(pbx_entry)

        return pbx_entries
Example #30
0
    def get_pourbaix_plot_colorfill_by_element(self, limits=None, title="",
                                                label_domains=True, element=None):
        """
        Color domains by element
        """
        from matplotlib.patches import Polygon

        entry_dict_of_multientries = collections.defaultdict(list)
        plt = pretty_plot(16)
        optim_colors = ['#0000FF', '#FF0000', '#00FF00', '#FFFF00', '#FF00FF',
                         '#FF8080', '#DCDCDC', '#800000', '#FF8000']
        optim_font_color = ['#FFFFA0', '#00FFFF', '#FF00FF', '#0000FF', '#00FF00',
                            '#007F7F', '#232323', '#7FFFFF', '#007FFF']
        hatch = ['/', '\\', '|', '-', '+', 'o', '*']
        (stable, unstable) = self.pourbaix_plot_data(limits)
        num_of_overlaps = {key: 0 for key in stable.keys()}
        for entry in stable:
            if isinstance(entry, MultiEntry):
                for e in entry.entrylist:
                    if element in e.composition.elements:
                        entry_dict_of_multientries[e.name].append(entry)
                        num_of_overlaps[entry] += 1
            else:
                entry_dict_of_multientries[entry.name].append(entry)
        if limits:
            xlim = limits[0]
            ylim = limits[1]
        else:
            xlim = self._analyzer.chempot_limits[0]
            ylim = self._analyzer.chempot_limits[1]

        h_line = np.transpose([[xlim[0], -xlim[0] * PREFAC],
                               [xlim[1], -xlim[1] * PREFAC]])
        o_line = np.transpose([[xlim[0], -xlim[0] * PREFAC + 1.23],
                               [xlim[1], -xlim[1] * PREFAC + 1.23]])
        neutral_line = np.transpose([[7, ylim[0]], [7, ylim[1]]])
        V0_line = np.transpose([[xlim[0], 0], [xlim[1], 0]])

        ax = plt.gca()
        ax.set_xlim(xlim)
        ax.set_ylim(ylim)
        from pymatgen import Composition, Element
        from pymatgen.core.ion import Ion

        def len_elts(entry):
            if "(s)" in entry:
                comp = Composition(entry[:-3])
            else:
                comp = Ion.from_formula(entry)
            return len([el for el in comp.elements if el not in
                        [Element("H"), Element("O")]])

        sorted_entry = entry_dict_of_multientries.keys()
        sorted_entry.sort(key=len_elts)
        i = -1
        label_chr = map(chr, list(range(65, 91)))
        for entry in sorted_entry:
            color_indx = 0
            x_coord = 0.0
            y_coord = 0.0
            npts = 0
            i += 1
            for e in entry_dict_of_multientries[entry]:
                hc = 0
                fc = 0
                bc = 0
                xy = self.domain_vertices(e)
                c = self.get_center(stable[e])
                x_coord += c[0]
                y_coord += c[1]
                npts += 1
                color_indx = i
                if "(s)" in entry:
                    comp = Composition(entry[:-3])
                else:
                    comp = Ion.from_formula(entry)
                if len([el for el in comp.elements if el not in
                         [Element("H"), Element("O")]]) == 1:
                    if color_indx >= len(optim_colors):
                        color_indx = color_indx -\
                         int(color_indx / len(optim_colors)) * len(optim_colors)
                    patch = Polygon(xy, facecolor=optim_colors[color_indx],
                                     closed=True, lw=3.0, fill=True)
                    bc = optim_colors[color_indx]
                else:
                    if color_indx >= len(hatch):
                        color_indx = color_indx - int(color_indx / len(hatch)) * len(hatch)
                    patch = Polygon(xy, hatch=hatch[color_indx], closed=True, lw=3.0, fill=False)
                    hc = hatch[color_indx]
                ax.add_patch(patch)

            xy_center = (x_coord / npts, y_coord / npts)
            if label_domains:
                if color_indx >= len(optim_colors):
                    color_indx = color_indx -\
                        int(color_indx / len(optim_colors)) * len(optim_colors)
                fc = optim_font_color[color_indx]
                if bc and not hc:
                    bbox = dict(boxstyle="round", fc=fc)
                if hc and not bc:
                    bc = 'k'
                    fc = 'w'
                    bbox = dict(boxstyle="round", hatch=hc, fill=False)
                if bc and hc:
                    bbox = dict(boxstyle="round", hatch=hc, fc=fc)
#                 bbox.set_path_effects([PathEffects.withSimplePatchShadow()])
                plt.annotate(latexify_ion(latexify(entry)), xy_center,
                            color=bc, fontsize=30, bbox=bbox)
#                 plt.annotate(label_chr[i], xy_center,
#                               color=bc, fontsize=30, bbox=bbox)

        lw = 3
        plt.plot(h_line[0], h_line[1], "r--", linewidth=lw)
        plt.plot(o_line[0], o_line[1], "r--", linewidth=lw)
        plt.plot(neutral_line[0], neutral_line[1], "k-.", linewidth=lw)
        plt.plot(V0_line[0], V0_line[1], "k-.", linewidth=lw)

        plt.xlabel("pH")
        plt.ylabel("E (V)")
        plt.title(title, fontsize=20, fontweight='bold')
        return plt
Example #31
0
    def test_charge_from_formula(self):
        self.assertEqual(Ion.from_formula("Li+").charge, 1)
        self.assertEqual(Ion.from_formula("Li[+]").charge, 1)
        self.assertEqual(Ion.from_formula("Ca[2+]").charge, 2)
        self.assertEqual(Ion.from_formula("Ca[+2]").charge, 2)
        self.assertEqual(Ion.from_formula("Ca++").charge, 2)
        self.assertEqual(Ion.from_formula("Ca[++]").charge, 2)
        self.assertEqual(Ion.from_formula("Ca2+").charge, 1)

        self.assertEqual(Ion.from_formula("Cl-").charge, -1)
        self.assertEqual(Ion.from_formula("Cl[-]").charge, -1)
        self.assertEqual(Ion.from_formula("SO4[-2]").charge, -2)
        self.assertEqual(Ion.from_formula("SO4-2").charge, -2)
        self.assertEqual(Ion.from_formula("SO42-").charge, -1)
        self.assertEqual(Ion.from_formula("SO4--").charge, -2)
        self.assertEqual(Ion.from_formula("SO4[--]").charge, -2)

        self.assertEqual(Ion.from_formula("Na[+-+]").charge, 1)
Example #32
0
    def get_pourbaix_plot_colorfill_by_element(self, limits=None, title="",
                                                label_domains=True, element=None):
        """
        Color domains by element
        """
        from matplotlib.patches import Polygon
        import matplotlib.patheffects as PathEffects

        entry_dict_of_multientries = collections.defaultdict(list)
        plt = get_publication_quality_plot(16)
        optim_colors = ['#0000FF', '#FF0000', '#00FF00', '#FFFF00', '#FF00FF',
                         '#FF8080', '#DCDCDC', '#800000', '#FF8000']
        optim_font_color = ['#FFFFA0', '#00FFFF', '#FF00FF', '#0000FF', '#00FF00',
                            '#007F7F', '#232323', '#7FFFFF', '#007FFF']
        hatch = ['/', '\\', '|', '-', '+', 'o', '*']
        (stable, unstable) = self.pourbaix_plot_data(limits)
        num_of_overlaps = {key: 0 for key in stable.keys()}
        for entry in stable:
            if isinstance(entry, MultiEntry):
                for e in entry.entrylist:
                    if element in e.composition.elements:
                        entry_dict_of_multientries[e.name].append(entry)
                        num_of_overlaps[entry] += 1
            else:
                entry_dict_of_multientries[entry.name].append(entry)
        if limits:
            xlim = limits[0]
            ylim = limits[1]
        else:
            xlim = self._analyzer.chempot_limits[0]
            ylim = self._analyzer.chempot_limits[1]

        h_line = np.transpose([[xlim[0], -xlim[0] * PREFAC],
                               [xlim[1], -xlim[1] * PREFAC]])
        o_line = np.transpose([[xlim[0], -xlim[0] * PREFAC + 1.23],
                               [xlim[1], -xlim[1] * PREFAC + 1.23]])
        neutral_line = np.transpose([[7, ylim[0]], [7, ylim[1]]])
        V0_line = np.transpose([[xlim[0], 0], [xlim[1], 0]])

        ax = plt.gca()
        ax.set_xlim(xlim)
        ax.set_ylim(ylim)
        from pymatgen import Composition, Element
        from pymatgen.core.ion import Ion

        def len_elts(entry):
            if "(s)" in entry:
                comp = Composition(entry[:-3])
            else:
                comp = Ion.from_formula(entry)
            return len([el for el in comp.elements if el not in
                        [Element("H"), Element("O")]])

        sorted_entry = entry_dict_of_multientries.keys()
        sorted_entry.sort(key=len_elts)
        i = -1
        label_chr = map(chr, range(65, 91))
        for entry in sorted_entry:
            color_indx = 0
            x_coord = 0.0
            y_coord = 0.0
            npts = 0
            i += 1
            for e in entry_dict_of_multientries[entry]:
                hc = 0
                fc = 0
                bc = 0
                xy = self.domain_vertices(e)
                c = self.get_center(stable[e])
                x_coord += c[0]
                y_coord += c[1]
                npts += 1
                color_indx = i
                if "(s)" in entry:
                    comp = Composition(entry[:-3])
                else:
                    comp = Ion.from_formula(entry)
                if len([el for el in comp.elements if el not in
                         [Element("H"), Element("O")]]) == 1:
                    if color_indx >= len(optim_colors):
                        color_indx = color_indx -\
                         int(color_indx / len(optim_colors)) * len(optim_colors)
                    patch = Polygon(xy, facecolor=optim_colors[color_indx],
                                     closed=True, lw=3.0, fill=True)
                    bc = optim_colors[color_indx]
                else:
                    if color_indx >= len(hatch):
                        color_indx = color_indx - int(color_indx / len(hatch)) * len(hatch)
                    patch = Polygon(xy, hatch=hatch[color_indx], closed=True, lw=3.0, fill=False)
                    hc = hatch[color_indx]
                ax.add_patch(patch)
                
            xy_center = (x_coord / npts, y_coord / npts)
            if label_domains:
                if color_indx >= len(optim_colors):
                    color_indx = color_indx -\
                        int(color_indx / len(optim_colors)) * len(optim_colors)
                fc = optim_font_color[color_indx]
                if bc and not hc:
                    bbox = dict(boxstyle="round", fc=fc)
                if hc and not bc:
                    bc = 'k'
                    fc = 'w'
                    bbox = dict(boxstyle="round", hatch=hc, fill=False)
                if bc and hc:
                    bbox = dict(boxstyle="round", hatch=hc, fc=fc)
#                 bbox.set_path_effects([PathEffects.withSimplePatchShadow()])
#                 plt.annotate(latexify_ion(latexify(entry)), xy_center,
#                               color=fc, fontsize=30, bbox=bbox)
                plt.annotate(label_chr[i], xy_center,
                              color=bc, fontsize=30, bbox=bbox)

        lw = 3
        plt.plot(h_line[0], h_line[1], "r--", linewidth=lw)
        plt.plot(o_line[0], o_line[1], "r--", linewidth=lw)
        plt.plot(neutral_line[0], neutral_line[1], "k-.", linewidth=lw)
        plt.plot(V0_line[0], V0_line[1], "k-.", linewidth=lw)

        plt.xlabel("pH")
        plt.ylabel("E (V)")
        plt.title(title, fontsize=20, fontweight='bold')
        return plt
Example #33
0
    def get_ion(
        ion: Union[Ion, str],
        parameter_set: str = "auto",
        water_model: str = "auto",
        mixing_rule: Optional[str] = None,
    ) -> LammpsData:
        """
        Retrieve force field parameters for an ion in water.

        Args:
            ion: Formula of the ion (e.g., "Li+"). Not case sensitive. May be
                passed as either a string or an Ion object.
            parameter_set: Force field parameters to use for ions.
                Valid choices are:
                    1. "jj" for the Jensen and Jorgensen parameters (2006)"
                    2. "jc" for Joung-Cheatham parameters (2008)
                    3. "lm" for the Li and Merz group parameters (2020-2021)"
                The default parameter set is "auto", which assigns a recommended
                parameter set that is compatible with the chosen water model.
            water_model: Water model to use. Models must be given as a string
                (not case sensitive). "-" and "/" are ignored. Hence "tip3pfb"
                and "TIP3P-FB" are both valid inputs for the TIP3P-FB water model.
                Available water models are:
                    1. SPC
                    2. SPC/E
                    3. TIP3P-EW
                    4. TIP3P-FB
                    5. OPC3
                    6. TIP4P-EW
                    7. TIP4P-2005
                    8. TIP4P-FB
                    9. OPC
                The default water model is "auto", which assigns a recommended
                water model that is compatible with the chosen ion parameters. Other
                combinations are possible at your own risk. See documentation.

            When both the parameter_set and water_model are set to "auto", the function returns the
            Joung-Cheatham parameters for the SPC/E water model.

                For a systematic comparison of the performance of different water models, refer to

                    Sachini et al., Systematic Comparison of the Structural and Dynamic Properties of
                    Commonly Used Water Models for Molecular Dynamics Simulations. J. Chem. Inf. Model.
                    2021, 61, 9, 4521–4536. https://doi.org/10.1021/acs.jcim.1c00794

            mixing_rule: The mixing rule to use for the ion parameter. Default to None, which does not
                change the original mixing rule of the parameter set. Available choices are 'LB'
                (Lorentz-Berthelot or arithmetic) and 'geometric'. If the specified mixing rule does not
                match the default mixing rule of the parameter set, the output parameter will be converted
                accordingly.


        Returns:
            Force field parameters for the chosen water model.
        """
        alias = {
            "aq": "aqvist",
            "jj": "jensen_jorgensen",
            "jc": "joung_cheatham",
            "lm": "li_merz"
        }
        default_sets = {
            "spc": "N/A",
            "spce": "jc",
            "tip3p": "jc",
            "tip3pew": "N/A",
            "tip3pfb": "lm",
            "opc3": "lm",
            "tip4p2005": "N/A",
            "tip4p": "jj",
            "tip4pew": "jc",
            "tip4pfb": "lm",
            "opc": "lm",
            "jj": "tip4p",
            "jc": "spce",
            "lm": "tip4pfb",
        }
        water_model = water_model.replace("-", "").replace("/", "").lower()
        parameter_set = parameter_set.lower()

        if water_model == "auto" and parameter_set == "auto":
            water_model = "spce"
            parameter_set = "jc"
        elif parameter_set == "auto":
            parameter_set = default_sets.get(water_model, parameter_set)
            if parameter_set == "N/A":
                raise ValueError(
                    f"The {water_model} water model has no specifically parameterized ion parameter sets"
                    "Please try a different water model.")
        elif water_model == "auto":
            water_model = default_sets.get(parameter_set, water_model)

        parameter_set = alias.get(parameter_set, parameter_set)

        # Make the Ion object to get mass and charge
        if isinstance(ion, Ion):
            ion_obj = ion
        else:
            ion_obj = Ion.from_formula(ion.capitalize())

        # load ion data as a list of IonLJData objects
        ion_data = loadfn(os.path.join(DATA_DIR, "ion_lj_params.json"))

        # make sure the ion is in the DataFrame
        key = ion_obj.reduced_formula
        filtered_data = [d for d in ion_data if d.formula == key]
        if len(filtered_data) == 0:
            raise ValueError(
                f"Ion {key} not found in database. Please try a different ion."
            )

        # make sure the parameter set is in the DataFrame
        filtered_data = [
            d for d in filtered_data
            if d.name == parameter_set and d.water_model == water_model
        ]
        if len(filtered_data) == 0:
            raise ValueError(
                f"No {parameter_set} parameters for water model {water_model} for ion {key}. "
                "See documentation and try a different combination.")

        if len(filtered_data) != 1:
            raise ValueError(
                f"Something is wrong: multiple ion data entries for {key}, {parameter_set}, and {water_model}"
            )

        # we only consider monatomic ions at present
        # construct a cubic LammpsBox from a lattice
        lat = Lattice([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
        box = lattice_2_lmpbox(lat)[0]
        # put it in the center of a cubic Structure
        struct = Structure(lat, ion_obj, [[0.5, 0.5, 0.5]])
        # construct Topology with the ion centered in the box
        topo = Topology(struct, charges=[ion_obj.charge])

        # retrieve Lennard-Jones parameters
        # construct ForceField object
        sigma = filtered_data[0].sigma
        epsilon = filtered_data[0].epsilon
        if mixing_rule is None:
            pass
        else:
            default_mixing = filtered_data[0].combining_rule
            water_sigma = WATER_SIGMA.get(filtered_data[0].water_model)
            if mixing_rule.lower() in [
                    "lb", "arithmetic", "lorentz-berthelot",
                    "lorentz berthelot"
            ]:
                mixing_rule = "LB"
            elif mixing_rule.lower() == "geometric":
                mixing_rule = "geometric"
            else:
                raise ValueError(
                    "Invalid mixing rule. Supported mixing rules are 'LB'(arithmetic) and 'geometric'. "
                )
            if default_mixing == mixing_rule:
                pass
            elif default_mixing == "LB" and mixing_rule == "geometric":
                sigma = ((water_sigma + sigma) / 2)**2 / water_sigma
                print(
                    "The parameter mixing rule has been converted from the original 'LB' to 'geometric'.\n"
                    "Please use the parameter set with caution!")
            else:
                sigma = 2 * ((water_sigma * sigma)**(1 / 2)) - water_sigma
                print(
                    "The parameter mixing rule has been converted from the original 'geometric' to 'LB'.\n"
                    "Please use the parameter set with caution!")
        ff = ForceField([(str(e), e) for e in ion_obj.elements],
                        nonbond_coeffs=[[epsilon, sigma]])

        return LammpsData.from_ff_and_topologies(box,
                                                 ff, [topo],
                                                 atom_style="full")
Example #34
0
 def setUp(self):
     ion = Ion.from_formula("MnO4[-]")
     self.entry = IonEntry(ion, 49)
Example #35
0
def plot_pourbaix_diagram(metastability=0.0, ion_concentration=1e-6, fmt='pdf'):
    """
    Creates a Pourbaix diagram for the material in the cwd.

    Args:
        metastability (float): desired metastable tolerance energy
            (meV/atom). <~50 is generally a sensible range to use.
        ion_concentration (float): in mol/kg. Sensible values are
            generally between 1e-8 and 1.
        fmt (str): matplotlib format style. Check the matplotlib
            docs for options.
    """

    # Create a ComputedEntry object for the 2D material.
    composition = Structure.from_file('POSCAR').composition
    energy = Vasprun('vasprun.xml').final_energy

    cmpd = ComputedEntry(composition, energy)

    # Define the chemsys that describes the 2D compound.
    chemsys = ['O', 'H'] + [elt.symbol for elt in composition.elements
                            if elt.symbol not in ['O', 'H']]

    # Pick out the ions pertaining to the 2D compound.
    ion_dict = dict()
    for elt in chemsys:
        if elt not in ['O', 'H'] and ION_FORMATION_ENERGIES[elt]:
            ion_dict.update(ION_FORMATION_ENERGIES[elt])

    elements = [Element(elt) for elt in chemsys if elt not in ['O', 'H']]

    # Add "correction" for metastability
    cmpd.correction -= float(cmpd.composition.num_atoms)\
        * float(metastability) / 1000.0

    # Calculate formation energy of the compound from its end
    # members
    form_energy = cmpd.energy
    for elt in composition.as_dict():
        form_energy -= CHEMICAL_POTENTIALS[elt] * cmpd.composition[elt]

    # Convert the compound entry to a pourbaix entry.
    # Default concentration for solid entries = 1
    pbx_cmpd = PourbaixEntry(cmpd)
    pbx_cmpd.g0_replace(form_energy)
    pbx_cmpd.reduced_entry()

    # Add corrected ionic entries to the pourbaix diagram
    # dft corrections for experimental ionic energies:
    # Persson et.al PHYSICAL REVIEW B 85, 235438 (2012)
    pbx_ion_entries = list()

    # Get PourbaixEntry corresponding to each ion.
    # Default concentration for ionic entries = 1e-6
    # ion_energy = ion_exp_energy + ion_correction * factor
    # where factor = fraction of element el in the ionic entry
    # compared to the reference entry
    for elt in elements:
        for key in ion_dict:
            comp = Ion.from_formula(key)
            if comp.composition[elt] != 0:
                factor = comp.composition[elt]
                energy = ion_dict[key]
                pbx_entry_ion = PourbaixEntry(IonEntry(comp, energy))
                pbx_entry_ion.correction = (
                    ION_CORRECTIONS[elt.symbol] * factor
                )
                pbx_entry_ion.conc = ion_concentration
                pbx_entry_ion.name = key
                pbx_ion_entries.append(pbx_entry_ion)

    # Generate and plot Pourbaix diagram
    # Each bulk solid/ion has a free energy g of the form:
    # g = g0_ref + 0.0591 * log10(conc) - nO * mu_H2O +
    # (nH - 2nO) * pH + phi * (-nH + 2nO + q)

    all_entries = [pbx_cmpd] + pbx_ion_entries

    total = sum([composition[el] for el in elements])
    comp_dict = {el.symbol: composition[el]/total for el in elements}
    pourbaix_diagram = PourbaixDiagram(all_entries, comp_dict=comp_dict)

    plotter = PourbaixPlotter(pourbaix_diagram)

    # Plotting details...
    font = "serif"
    fig = plt.figure(figsize=(14, 9))
    ax1 = fig.gca()
    ax1.set_xlim([0, 14])
    ax1.set_xticklabels([int(t) for t in ax1.get_xticks()], fontname=font,
                        fontsize=18)
    ax1.set_ylim(-2, 2)
    ax1.set_yticklabels(ax1.get_yticks(), fontname=font, fontsize=18)
    ax1.set_xlabel("pH", fontname=font, fontsize=18)
    ax1.set_ylabel("Potential vs. SHE (V)", fontname=font, fontsize=18)

    # Outline water's stability range.
    ax1.plot([0, 14], [0, -0.829], color="gray", linestyle="--", alpha=0.7,
             linewidth=2)
    ax1.plot([0, 14], [1.229, 0.401], color="gray", linestyle="--", alpha=0.7,
             linewidth=2)


    stable_entries = plotter.pourbaix_plot_data(
        limits=[[0, 14], [-2, 2]])[0]

    # Add coloring.
    colors = sb.color_palette("Set2", len(stable_entries))

    i = 0
    for entry in stable_entries:
        col = colors[i]
        i += 1
        vertices = plotter.domain_vertices(entry)
        center_x = sum([v[0] for v in vertices])/len(vertices)
        center_y = sum([v[1] for v in vertices])/len(vertices)
        patch = Polygon(vertices, closed=True, fill=True, facecolor=col,
                        linewidth=2, edgecolor="w")
        ax1.text(center_x, center_y, plotter.print_name(entry),
                 verticalalignment="center", horizontalalignment="center",
                 fontname=font, fontsize=18)
        ax1.add_patch(patch)

    plt.savefig("pourbaix.{}".format(fmt))

    plt.close()
Example #36
0
 def setUp(self):
     ion = Ion.from_formula("MnO4[-]")
     self.entry = IonEntry(ion, 49)
Example #37
0
    def get_ion_entries(self,
                        pd: PhaseDiagram,
                        ion_ref_data: List[dict] = None) -> List[IonEntry]:
        """
        Retrieve IonEntry objects that can be used in the construction of
        Pourbaix Diagrams. The energies of the IonEntry are calculaterd from
        the solid energies in the provided Phase Diagram to be
        consistent with experimental free energies.

        NOTE! This is an advanced method that assumes detailed understanding
        of how to construct computational Pourbaix Diagrams. If you just want
        to build a Pourbaix Diagram using default settings, use get_pourbaix_entries.

        Args:
            pd: Solid phase diagram on which to construct IonEntry. Note that this
                Phase Diagram MUST include O and H in its chemical system. For example,
                to retrieve IonEntry for Ti, the phase diagram passed here should contain
                materials in the H-O-Ti chemical system. It is also assumed that solid
                energies have already been corrected with MaterialsProjectAqueousCompatibility,
                which is necessary for proper construction of Pourbaix diagrams.
            ion_ref_data: Aqueous ion reference data. If None (default), the data
                are downloaded from the Aqueous Ion Reference Data project hosted
                on MPContribs. To add a custom ionic species, first download
                data using get_ion_reference_data, then add or customize it with
                your additional data, and pass the customized list here.

        Returns:
            [IonEntry]: IonEntry are similar to PDEntry objects. Their energies
                are free energies in eV.
        """
        # determine the chemsys from the phase diagram
        chemsys = "-".join([el.symbol for el in pd.elements])

        # raise ValueError if O and H not in chemsys
        if "O" not in chemsys or "H" not in chemsys:
            raise ValueError(
                "The phase diagram chemical system must contain O and H! Your"
                f" diagram chemical system is {chemsys}.")

        if not ion_ref_data:
            ion_data = self.get_ion_reference_data_for_chemsys(chemsys)
        else:
            ion_data = ion_ref_data

        # position the ion energies relative to most stable reference state
        ion_entries = []
        for n, i_d in enumerate(ion_data):
            ion = Ion.from_formula(i_d["formula"])
            refs = [
                e for e in pd.all_entries
                if e.composition.reduced_formula == i_d["data"]["RefSolid"]
            ]
            if not refs:
                raise ValueError("Reference solid not contained in entry list")
            stable_ref = sorted(refs, key=lambda x: x.energy_per_atom)[0]
            rf = stable_ref.composition.get_reduced_composition_and_factor()[1]

            # TODO - need a more robust way to convert units
            # use pint here?
            if i_d["data"]["ΔGᶠRefSolid"]["unit"] == "kJ/mol":
                # convert to eV/formula unit
                ref_solid_energy = i_d["data"]["ΔGᶠRefSolid"]["value"] / 96.485
            elif i_d["data"]["ΔGᶠRefSolid"]["unit"] == "MJ/mol":
                # convert to eV/formula unit
                ref_solid_energy = i_d["data"]["ΔGᶠRefSolid"]["value"] / 96485
            else:
                raise ValueError(
                    f"Ion reference solid energy has incorrect unit {i_d['data']['ΔGᶠRefSolid']['unit']}"
                )
            solid_diff = pd.get_form_energy(stable_ref) - ref_solid_energy * rf
            elt = i_d["data"]["MajElements"]
            correction_factor = ion.composition[elt] / stable_ref.composition[
                elt]
            # TODO - need a more robust way to convert units
            # use pint here?
            if i_d["data"]["ΔGᶠ"]["unit"] == "kJ/mol":
                # convert to eV/formula unit
                ion_free_energy = i_d["data"]["ΔGᶠ"]["value"] / 96.485
            elif i_d["data"]["ΔGᶠ"]["unit"] == "MJ/mol":
                # convert to eV/formula unit
                ion_free_energy = i_d["data"]["ΔGᶠ"]["value"] / 96485
            else:
                raise ValueError(
                    f"Ion free energy has incorrect unit {i_d['data']['ΔGᶠ']['unit']}"
                )
            energy = ion_free_energy + solid_diff * correction_factor
            ion_entries.append(IonEntry(ion, energy))

        return ion_entries
Example #38
0
def plot_pourbaix_diagram(metastability=0.0,
                          ion_concentration=1e-6,
                          fmt='pdf'):
    """
    Creates a Pourbaix diagram for the material in the cwd.

    Args:
        metastability (float): desired metastable tolerance energy
            (meV/atom). <~50 is generally a sensible range to use.
        ion_concentration (float): in mol/kg. Sensible values are
            generally between 1e-8 and 1.
        fmt (str): matplotlib format style. Check the matplotlib
            docs for options.
    """

    # Create a ComputedEntry object for the 2D material.
    composition = Structure.from_file('POSCAR').composition
    energy = Vasprun('vasprun.xml').final_energy

    cmpd = ComputedEntry(composition, energy)

    # Define the chemsys that describes the 2D compound.
    chemsys = ['O', 'H'] + [
        elt.symbol
        for elt in composition.elements if elt.symbol not in ['O', 'H']
    ]

    # Pick out the ions pertaining to the 2D compound.
    ion_dict = dict()
    for elt in chemsys:
        if elt not in ['O', 'H'] and ION_FORMATION_ENERGIES[elt]:
            ion_dict.update(ION_FORMATION_ENERGIES[elt])

    elements = [Element(elt) for elt in chemsys if elt not in ['O', 'H']]

    # Add "correction" for metastability
    cmpd.correction -= float(cmpd.composition.num_atoms)\
        * float(metastability) / 1000.0

    # Calculate formation energy of the compound from its end
    # members
    form_energy = cmpd.energy
    for elt in composition.as_dict():
        form_energy -= CHEMICAL_POTENTIALS[elt] * cmpd.composition[elt]

    # Convert the compound entry to a pourbaix entry.
    # Default concentration for solid entries = 1
    pbx_cmpd = PourbaixEntry(cmpd)
    pbx_cmpd.g0_replace(form_energy)
    pbx_cmpd.reduced_entry()

    # Add corrected ionic entries to the pourbaix diagram
    # dft corrections for experimental ionic energies:
    # Persson et.al PHYSICAL REVIEW B 85, 235438 (2012)
    pbx_ion_entries = list()

    # Get PourbaixEntry corresponding to each ion.
    # Default concentration for ionic entries = 1e-6
    # ion_energy = ion_exp_energy + ion_correction * factor
    # where factor = fraction of element el in the ionic entry
    # compared to the reference entry
    for elt in elements:
        for key in ion_dict:
            comp = Ion.from_formula(key)
            if comp.composition[elt] != 0:
                factor = comp.composition[elt]
                energy = ion_dict[key]
                pbx_entry_ion = PourbaixEntry(IonEntry(comp, energy))
                pbx_entry_ion.correction = (ION_CORRECTIONS[elt.symbol] *
                                            factor)
                pbx_entry_ion.conc = ion_concentration
                pbx_entry_ion.name = key
                pbx_ion_entries.append(pbx_entry_ion)

    # Generate and plot Pourbaix diagram
    # Each bulk solid/ion has a free energy g of the form:
    # g = g0_ref + 0.0591 * log10(conc) - nO * mu_H2O +
    # (nH - 2nO) * pH + phi * (-nH + 2nO + q)

    all_entries = [pbx_cmpd] + pbx_ion_entries

    pourbaix = PourbaixDiagram(all_entries)

    # Analysis features
    # panalyzer = PourbaixAnalyzer(pourbaix)
    # instability = panalyzer.get_e_above_hull(pbx_cmpd)

    plotter = PourbaixPlotter(pourbaix)
    plot = plotter.get_pourbaix_plot(limits=[[0, 14], [-2, 2]],
                                     label_domains=True)
    fig = plot.gcf()
    ax1 = fig.gca()

    # Add coloring to highlight the stability region for the 2D
    # material, if one exists.
    stable_entries = plotter.pourbaix_plot_data(limits=[[0, 14], [-2, 2]])[0]

    for entry in stable_entries:
        if entry == pbx_cmpd:
            col = plt.cm.Blues(0)
        else:
            col = plt.cm.rainbow(
                float(ION_COLORS[entry.composition.reduced_formula]))

        vertices = plotter.domain_vertices(entry)
        patch = Polygon(vertices, closed=True, fill=True, color=col)
        ax1.add_patch(patch)

    fig.set_size_inches((11.5, 9))
    plot.tight_layout(pad=1.09)

    # Save plot
    if metastability:
        plot.suptitle('Metastable Tolerance ='
                      ' {} meV/atom'.format(metastability),
                      fontsize=20)
        plot.savefig('{}_{}.{}'.format(composition.reduced_formula,
                                       ion_concentration, fmt),
                     transparent=True)
    else:
        plot.savefig('{}_{}.{}'.format(composition.reduced_formula,
                                       ion_concentration, fmt),
                     transparent=True)

    plot.close()
Example #39
0
 def len_elts(entry):
     comp = Composition(entry[:-3]) if "(s)" in entry else Ion.from_formula(entry)
     return len(set(comp.elements) - {Element("H"), Element("O")})
Example #40
0
    def get_pourbaix_entries(self, chemsys):
        """
        A helper function to get all entries necessary to generate
        a pourbaix diagram from the rest interface.

        Args:
            chemsys ([str]): A list of elements comprising the chemical
                system, e.g. ['Li', 'Fe']
        """
        from pymatgen.analysis.pourbaix_diagram import PourbaixEntry, IonEntry
        from pymatgen.analysis.phase_diagram import PhaseDiagram
        from pymatgen.core.ion import Ion
        from pymatgen.entries.compatibility import\
            MaterialsProjectAqueousCompatibility

        pbx_entries = []

        # Get ion entries first, because certain ions have reference
        # solids that aren't necessarily in the chemsys (Na2SO4)
        url = '/pourbaix_diagram/reference_data/' + '-'.join(chemsys)
        ion_data = self._make_request(url)
        ion_ref_comps = [Composition(d['Reference Solid']) for d in ion_data]
        ion_ref_elts = list(itertools.chain.from_iterable(
            i.elements for i in ion_ref_comps))
        ion_ref_entries = self.get_entries_in_chemsys(
            list(set([str(e) for e in ion_ref_elts] + ['O', 'H'])),
            property_data=['e_above_hull'], compatible_only=False)
        compat = MaterialsProjectAqueousCompatibility("Advanced")
        ion_ref_entries = compat.process_entries(ion_ref_entries)
        ion_ref_pd = PhaseDiagram(ion_ref_entries)

        # position the ion energies relative to most stable reference state
        for n, i_d in enumerate(ion_data):
            ion_entry = IonEntry(Ion.from_formula(i_d['Name']), i_d['Energy'])
            refs = [e for e in ion_ref_entries
                    if e.composition.reduced_formula == i_d['Reference Solid']]
            if not refs:
                raise ValueError("Reference solid not contained in entry list")
            stable_ref = sorted(refs, key=lambda x: x.data['e_above_hull'])[0]
            rf = stable_ref.composition.get_reduced_composition_and_factor()[1]
            solid_diff = ion_ref_pd.get_form_energy(stable_ref)\
                         - i_d['Reference solid energy'] * rf
            elt = i_d['Major_Elements'][0]
            correction_factor = ion_entry.ion.composition[elt]\
                                / stable_ref.composition[elt]
            ion_entry.energy += solid_diff * correction_factor
            pbx_entries.append(PourbaixEntry(ion_entry, 'ion-{}'.format(n)))
            # import nose; nose.tools.set_trace()

        # Construct the solid pourbaix entries from filtered ion_ref entries
        extra_elts = set(ion_ref_elts) - {Element(s) for s in chemsys}\
            - {Element('H'), Element('O')}
        for entry in ion_ref_entries:
            entry_elts = set(entry.composition.elements)
            # Ensure no OH chemsys or extraneous elements from ion references
            if not (entry_elts <= {Element('H'), Element('O')} or \
                    extra_elts.intersection(entry_elts)):
                # replace energy with formation energy, use dict to
                # avoid messing with the ion_ref_pd and to keep all old params
                form_e = ion_ref_pd.get_form_energy(entry)
                new_entry = deepcopy(entry)
                new_entry.uncorrected_energy = form_e
                new_entry.correction = 0.0
                pbx_entry = PourbaixEntry(new_entry)
                if entry.entry_id == "mp-697146":
                    pass
                    # import nose; nose.tools.set_trace()
                # pbx_entry.reduced_entry()
                pbx_entries.append(pbx_entry)

        return pbx_entries
Example #41
0
pbx_solid_entries = []
for entry in stable_solids_minus_h2o:
    pbx_entry = PourbaixEntry(entry)

    # Replace E with newly corrected E
    pbx_entry.g0_replace(pd.get_form_energy(entry))
    pbx_entry.reduced_entry()  # Applies reduction factor?????
    pbx_solid_entries.append(pbx_entry)

# | - Processing My Solid Entries
for key, value in ion_dict_solids_expt.items():
    print("IDJFIJDS")
    split_key = key.split("_")
    formula_i = split_key[0]

    comp = Ion.from_formula(formula_i)
    energy = value
    pbx_entry_ion = PourbaixEntry(
        ComputedEntry(comp, energy, attribute={"full_name": key}))

    # AP pbx_entry_ion.name = key
    pbx_entry_ion.conc = 1
    pbx_solid_entries.append(pbx_entry_ion)

for a in pbx_solid_entries:
    print(a, a.conc)
    print('hkjo')

#__|

# +
Example #42
0
    def get_pourbaix_entries(
        self,
        chemsys: Union[str, List],
        solid_compat="MaterialsProject2020Compatibility",
        use_gibbs: Optional[Literal[300]] = None,
    ):
        """
        A helper function to get all entries necessary to generate
        a Pourbaix diagram from the rest interface.

        Args:
            chemsys (str or [str]): Chemical system string comprising element
                symbols separated by dashes, e.g., "Li-Fe-O" or List of element
                symbols, e.g., ["Li", "Fe", "O"].
            solid_compat: Compatiblity scheme used to pre-process solid DFT energies prior
                to applying aqueous energy adjustments. May be passed as a class (e.g.
                MaterialsProject2020Compatibility) or an instance
                (e.g., MaterialsProject2020Compatibility()). If None, solid DFT energies
                are used as-is. Default: MaterialsProject2020Compatibility
            use_gibbs: Set to 300 (for 300 Kelvin) to use a machine learning model to
                estimate solid free energy from DFT energy (see GibbsComputedStructureEntry).
                This can slightly improve the accuracy of the Pourbaix diagram in some
                cases. Default: None. Note that temperatures other than 300K are not
                permitted here, because MaterialsProjectAqueousCompatibility corrections,
                used in Pourbaix diagram construction, are calculated based on 300 K data.
        """
        # imports are not top-level due to expense
        from pymatgen.analysis.pourbaix_diagram import PourbaixEntry
        from pymatgen.entries.compatibility import (
            Compatibility,
            MaterialsProject2020Compatibility,
            MaterialsProjectAqueousCompatibility,
            MaterialsProjectCompatibility,
        )
        from pymatgen.entries.computed_entries import ComputedEntry

        if solid_compat == "MaterialsProjectCompatibility":
            solid_compat = MaterialsProjectCompatibility()
        elif solid_compat == "MaterialsProject2020Compatibility":
            solid_compat = MaterialsProject2020Compatibility()
        elif isinstance(solid_compat, Compatibility):
            solid_compat = solid_compat
        else:
            raise ValueError(
                "Solid compatibility can only be 'MaterialsProjectCompatibility', "
                "'MaterialsProject2020Compatibility', or an instance of a Compatability class"
            )

        pbx_entries = []

        if isinstance(chemsys, str):
            chemsys = chemsys.split("-")
        # capitalize and sort the elements
        chemsys = sorted(e.capitalize() for e in chemsys)

        # Get ion entries first, because certain ions have reference
        # solids that aren't necessarily in the chemsys (Na2SO4)

        # download the ion reference data from MPContribs
        ion_data = self.get_ion_reference_data_for_chemsys(chemsys)

        # build the PhaseDiagram for get_ion_entries
        ion_ref_comps = [
            Ion.from_formula(d["data"]["RefSolid"]).composition
            for d in ion_data
        ]
        ion_ref_elts = set(
            itertools.chain.from_iterable(i.elements for i in ion_ref_comps))
        # TODO - would be great if the commented line below would work
        # However for some reason you cannot process GibbsComputedStructureEntry with
        # MaterialsProjectAqueousCompatibility
        ion_ref_entries = self.get_entries_in_chemsys(
            list([str(e) for e in ion_ref_elts] + ["O", "H"]),
            # use_gibbs=use_gibbs
        )

        # suppress the warning about supplying the required energies; they will be calculated from the
        # entries we get from MPRester
        with warnings.catch_warnings():
            warnings.filterwarnings(
                "ignore",
                message="You did not provide the required O2 and H2O energies.",
            )
            compat = MaterialsProjectAqueousCompatibility(
                solid_compat=solid_compat)
        # suppress the warning about missing oxidation states
        with warnings.catch_warnings():
            warnings.filterwarnings(
                "ignore", message="Failed to guess oxidation states.*")
            ion_ref_entries = compat.process_entries(ion_ref_entries)
        # TODO - if the commented line above would work, this conditional block
        # could be removed
        if use_gibbs:
            # replace the entries with GibbsComputedStructureEntry
            from pymatgen.entries.computed_entries import GibbsComputedStructureEntry

            ion_ref_entries = GibbsComputedStructureEntry.from_entries(
                ion_ref_entries, temp=use_gibbs)
        ion_ref_pd = PhaseDiagram(ion_ref_entries)

        ion_entries = self.get_ion_entries(ion_ref_pd, ion_ref_data=ion_data)
        pbx_entries = [
            PourbaixEntry(e, f"ion-{n}") for n, e in enumerate(ion_entries)
        ]

        # Construct the solid pourbaix entries from filtered ion_ref entries
        extra_elts = (set(ion_ref_elts) - {Element(s)
                                           for s in chemsys} -
                      {Element("H"), Element("O")})
        for entry in ion_ref_entries:
            entry_elts = set(entry.composition.elements)
            # Ensure no OH chemsys or extraneous elements from ion references
            if not (entry_elts <= {Element("H"), Element("O")}
                    or extra_elts.intersection(entry_elts)):
                # Create new computed entry
                form_e = ion_ref_pd.get_form_energy(entry)
                new_entry = ComputedEntry(entry.composition,
                                          form_e,
                                          entry_id=entry.entry_id)
                pbx_entry = PourbaixEntry(new_entry)
                pbx_entries.append(pbx_entry)

        return pbx_entries
Example #43
0
 def len_elts(entry):
     comp = Composition(
         entry[:-3]) if "(s)" in entry else Ion.from_formula(entry)
     return len(set(comp.elements) - {Element("H"), Element("O")})
Example #44
0
 def test_get_composition(self):
     comp = self.entry.composition
     expected_comp = Ion.from_formula('MnO4[-]')
     self.assertEqual(comp, expected_comp, "Wrong composition!")
Example #45
0
def plot_pourbaix_diagram(metastability=0.0, ion_concentration=1e-6, fmt='pdf'):
    """
    Creates a Pourbaix diagram for the material in the cwd.

    Args:
        metastability (float): desired metastable tolerance energy
            (meV/atom). <~50 is generally a sensible range to use.
        ion_concentration (float): in mol/kg. Sensible values are
            generally between 1e-8 and 1.
        fmt (str): matplotlib format style. Check the matplotlib
            docs for options.
    """

    # Create a ComputedEntry object for the 2D material.
    composition = Structure.from_file('POSCAR').composition
    energy = Vasprun('vasprun.xml').final_energy

    cmpd = ComputedEntry(composition, energy)

    # Define the chemsys that describes the 2D compound.
    chemsys = ['O', 'H'] + [elt.symbol for elt in composition.elements
                            if elt.symbol not in ['O', 'H']]

    # Experimental ionic energies
    # See ions.yaml for ion formation energies and references.
    exp_dict = ION_DATA['ExpFormEnergy']
    ion_correction = ION_DATA['IonCorrection']

    # Pick out the ions pertaining to the 2D compound.
    ion_dict = dict()
    for elt in chemsys:
        if elt not in ['O', 'H'] and exp_dict[elt]:
            ion_dict.update(exp_dict[elt])

    elements = [Element(elt) for elt in chemsys if elt not in ['O', 'H']]

    # Add "correction" for metastability
    cmpd.correction -= float(cmpd.composition.num_atoms)\
        * float(metastability) / 1000.0

    # Calculate formation energy of the compound from its end
    # members
    form_energy = cmpd.energy
    for elt in composition.as_dict():
        form_energy -= END_MEMBERS[elt] * cmpd.composition[elt]

    # Convert the compound entry to a pourbaix entry.
    # Default concentration for solid entries = 1
    pbx_cmpd = PourbaixEntry(cmpd)
    pbx_cmpd.g0_replace(form_energy)
    pbx_cmpd.reduced_entry()

    # Add corrected ionic entries to the pourbaix diagram
    # dft corrections for experimental ionic energies:
    # Persson et.al PHYSICAL REVIEW B 85, 235438 (2012)
    pbx_ion_entries = list()

    # Get PourbaixEntry corresponding to each ion.
    # Default concentration for ionic entries = 1e-6
    # ion_energy = ion_exp_energy + ion_correction * factor
    # where factor = fraction of element el in the ionic entry
    # compared to the reference entry
    for elt in elements:
        for key in ion_dict:
            comp = Ion.from_formula(key)
            if comp.composition[elt] != 0:
                factor = comp.composition[elt]
                energy = ion_dict[key]
                pbx_entry_ion = PourbaixEntry(IonEntry(comp, energy))
                pbx_entry_ion.correction = ion_correction[elt.symbol]\
                    * factor
                pbx_entry_ion.conc = ion_concentration
                pbx_entry_ion.name = key
                pbx_ion_entries.append(pbx_entry_ion)

    # Generate and plot Pourbaix diagram
    # Each bulk solid/ion has a free energy g of the form:
    # g = g0_ref + 0.0591 * log10(conc) - nO * mu_H2O +
    # (nH - 2nO) * pH + phi * (-nH + 2nO + q)

    all_entries = [pbx_cmpd] + pbx_ion_entries

    pourbaix = PourbaixDiagram(all_entries)

    # Analysis features
    panalyzer = PourbaixAnalyzer(pourbaix)
#    instability = panalyzer.get_e_above_hull(pbx_cmpd)

    plotter = PourbaixPlotter(pourbaix)
    plot = plotter.get_pourbaix_plot(limits=[[0, 14], [-2, 2]],
                                     label_domains=True)
    fig = plot.gcf()
    ax1 = fig.gca()

    # Add coloring to highlight the stability region for the 2D
    # material, if one exists.
    stable_entries = plotter.pourbaix_plot_data(
        limits=[[0, 14], [-2, 2]])[0]

    for entry in stable_entries:
        if entry == pbx_cmpd:
            col = plt.cm.Blues(0)
        else:
            col = plt.cm.rainbow(float(
                ION_COLORS[entry.composition.reduced_formula]))

        vertices = plotter.domain_vertices(entry)
        patch = Polygon(vertices, closed=True, fill=True, color=col)
        ax1.add_patch(patch)

    fig.set_size_inches((11.5, 9))
    plot.tight_layout(pad=1.09)

    # Save plot
    if metastability:
        plot.suptitle('Metastable Tolerance ='
                      ' {} meV/atom'.format(metastability),
                      fontsize=20)
        plot.savefig('{}_{}.{}'.format(
            composition.reduced_formula, ion_concentration, fmt),
            transparent=True)
    else:
        plot.savefig('{}_{}.{}'.format(composition.reduced_formula,
                                        ion_concentration, fmt),
                     transparent=True)

    plot.close()
Example #46
0
 def test_get_composition(self):
     comp = self.entry.composition
     expected_comp = Ion.from_formula('MnO4[-]')
     self.assertEquals(comp, expected_comp, "Wrong composition!")