Exemplo n.º 1
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
Exemplo n.º 2
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
Exemplo n.º 3
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)
Exemplo n.º 4
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
Exemplo n.º 5
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
Exemplo n.º 6
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