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)
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]))
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
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)
# - pbx_entry_ion.energy assert False # + jupyter={} # | - Ion Entries # Calculate DFT reference energy for ions (See Persson et al, PRB (2012)) pbx_ion_entries = [] for id in ion_dict: # Ion name-> Ion comp name (ex. Fe[3+] -> Ion: Fe1 +3) comp = Ion.from_formula(id['Name']) energy = id['Energy'] # + ion_correction * factor print(id['Name'], comp, energy) pbx_entry_ion = PourbaixEntry(IonEntry(comp, energy)) # AP pbx_entry_ion.name = id['Name'] pbx_entry_ion.conc = 1.0e-6 if pbx_entry_ion.name not in ['jfdksl']: # ['H2RuO2[2+]']: #["RuO4(aq)"]: pbx_ion_entries.append(pbx_entry_ion) #__| all_entries = pbx_solid_entries + pbx_ion_entries #__| # | - Save all_entries # Pickling data ###################################################### # import os; import pickle # directory = "out_data" # if not os.path.exists(directory): os.makedirs(directory)
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
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