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
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
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)
def aq_correction(entries): """ Applies the Materials Project Aqueous Compatibility scheme for mixing GGA and GGA+U to a list of entries. Removes entries which aren't compatible with the mixing scheme Args: entries: List of entries on which the correction will be applied """ #| - aq_correction from pymatgen.entries.compatibility import MaterialsProjectAqueousCompatibility def contains_entry(entry_list, ent): """ Helpful to filter duplicate entries, if entry is in entry_list, return True Args: entry_list: list of pymatgen entries to consider entry: entry which will be analyzed """ ent_id = ent.entry_id ent_E = ent.energy_per_atom ent_redfor = ent.composition.reduced_formula for e in entry_list: if e.entry_id == ent_id or (abs(ent_E - e.energy_per_atom) < 1e-6 and ent_redfor == e.composition.reduced_formula): return True aqcompat = MaterialsProjectAqueousCompatibility() #Implements the GGA/GGA+U mixing scheme, entries_aqcorr = list() for entry in entries: aq_corrected_entry = aqcompat.process_entry(entry) #Corrections, if none applicable, gets rid of entry if not contains_entry(entries_aqcorr, aq_corrected_entry): #If entry already in entries_aqcorr don't add entries_aqcorr.append(aq_corrected_entry) return entries_aqcorr
# for i,dummy in enumerate(ion_dict_S): # print ion_dict_S[i]['Name'], ion_dict_S[i]['Energy'] ion_dict = ion_dict_Co # + ion_dict_S #ion_dict = ion_dict_Co + ion_dict_S #__| #NOTE This line assumes that the every entry in the experimental ion energy # has the same ref. st. solid ref_state = str(ion_dict[0]['Reference Solid']) ref_dict = {ref_state: ion_dict[0]['Reference solid energy']} # Run aqueouscorrection on the entries # Entries without applicable corrections will be discarded # Implements the GGA/GGA+U mixing scheme aqcompat = MaterialsProjectAqueousCompatibility() entries_aqcorr = list() for entry in entries: # Applies corrections to entry, if none applicable it gets rid of entry aq_corrected_entry = aqcompat.process_entry(entry) # If entry already in entries_aqcorr then don't add to list if not contains_entry(entries_aqcorr, aq_corrected_entry): entries_aqcorr.append(aq_corrected_entry) # Generate a phase diagram to consider only solid entries stable in water. pd = PhaseDiagram(entries_aqcorr) stable_solids = pd.stable_entries stable_solids_minus_h2o = [ entry for entry in stable_solids if entry.composition.reduced_formula not in ["H2", "O2", "H2O", "H2O2"]
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
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