def test_composition_energy_adjustment(): ea = CompositionEnergyAdjustment(2, 2, uncertainty_per_atom=0, name="H") assert ea.name == "H" assert ea.value == 4 assert ea.explain == "Composition-based energy adjustment (2.000 eV/atom x 2 atoms)" ead = ea.as_dict() ea2 = CompositionEnergyAdjustment.from_dict(ead) assert str(ead) == str(ea2.as_dict())
def test_composition_energy_adjustment(): ea = CompositionEnergyAdjustment(2, 2, uncertainty_per_atom=0, name="H") assert ea.name == "H" assert ea.value == 4 assert ( ea.description == "Composition-based energy adjustment (2.000 eV/atom x 2 atoms)" )
def test_per_atom_props(self): entry = ComputedEntry("Fe6O9", 6.9) entry.energy_adjustments.append(CompositionEnergyAdjustment(-0.5, 9, uncertainty_per_atom=0.1, name="O")) self.assertAlmostEqual(entry.energy, 2.4) self.assertAlmostEqual(entry.energy_per_atom, 2.4 / 15) self.assertAlmostEqual(entry.uncorrected_energy, 6.9) self.assertAlmostEqual(entry.uncorrected_energy_per_atom, 6.9 / 15) self.assertAlmostEqual(entry.correction, -4.5) self.assertAlmostEqual(entry.correction_per_atom, -4.5 / 15) self.assertAlmostEqual(entry.correction_uncertainty, 0.9) self.assertAlmostEqual(entry.correction_uncertainty_per_atom, 0.9 / 15)
def test_normalize_energy_adjustments(self): ealist = [ManualEnergyAdjustment(5), ConstantEnergyAdjustment(5), CompositionEnergyAdjustment(1, 5, uncertainty_per_atom=0, name="Na"), TemperatureEnergyAdjustment(0.005, 100, 10, uncertainty_per_degK=0) ] entry = ComputedEntry("Na5Cl5", 6.9, energy_adjustments=ealist) assert entry.correction == 20 entry.normalize() assert entry.correction == 4 for ea in entry.energy_adjustments: assert ea.value == 1
def test_normalize_not_in_place(self): ealist = [ ManualEnergyAdjustment(5), ConstantEnergyAdjustment(5), CompositionEnergyAdjustment(1, 5, uncertainty_per_atom=0, name="Na"), TemperatureEnergyAdjustment(0.005, 100, 10, uncertainty_per_deg=0), ] entry = ComputedEntry("Na5Cl5", 6.9, energy_adjustments=ealist) normed_entry = entry.normalize(inplace=False) entry.normalize() self.assertEqual(normed_entry.as_dict(), entry.as_dict())
def get_adjustments(self, entry: ComputedEntry): """ Returns the corrections applied to a particular entry. Args: entry: A ComputedEntry object. Returns: [EnergyAdjustment]: Energy adjustments to be applied to entry. Raises: CompatibilityError if the required O2 and H2O energies have not been provided to MaterialsProjectAqueousCompatibility during init or in the list of entries passed to process_entries. """ adjustments = [] if self.o2_energy is None or self.h2o_energy is None or self.h2o_adjustments is None: raise CompatibilityError( "You did not provide the required O2 and H2O energies. " "{} needs these energies in order to compute " "the appropriate energy adjustments. Either specify the energies as arguments " "to {}.__init__ or run process_entries on a list that includes ComputedEntry for " "the ground state of O2 and H2O.".format( type(self).__name__, type(self).__name__)) # compute the free energies of H2 and H2O (eV/atom) to guarantee that the # formationfree energy of H2O is equal to -2.4583 eV/H2O from experiments # (MU_H2O from pourbaix module) # Free energy of H2 in eV/atom, fitted using Eq. 40 of Persson et al. PRB 2012 85(23) # for this calculation ONLY, we need the (corrected) DFT energy of water self.h2_energy = round( 0.5 * (3 * (self.h2o_energy - self.cpd_entropies["H2O"]) - (self.o2_energy - self.cpd_entropies["O2"]) - MU_H2O), 6) # Free energy of H2O, fitted for consistency with the O2 and H2 energies. self.fit_h2o_energy = round( (2 * self.h2_energy + (self.o2_energy - self.cpd_entropies["O2"]) + MU_H2O) / 3, 6) comp = entry.composition rform = comp.reduced_formula # pin the energy of all H2 entries to h2_energy if rform == "H2": adjustments.append( ConstantEnergyAdjustment( self.h2_energy * comp.num_atoms - entry.energy, name="MP Aqueous H2 / H2O referencing", cls=self.as_dict(), description= "Adjusts the H2 and H2O energy to reproduce the experimental " "Gibbs formation free energy of H2O, based on the DFT energy " "of Oxygen")) # pin the energy of all H2O entries to fit_h2o_energy elif rform == "H2O": adjustments.append( ConstantEnergyAdjustment( self.fit_h2o_energy * comp.num_atoms - entry.energy, name="MP Aqueous H2 / H2O referencing", cls=self.as_dict(), description= "Adjusts the H2 and H2O energy to reproduce the experimental " "Gibbs formation free energy of H2O, based on the DFT energy " "of Oxygen")) # add minus T delta S to the DFT energy (enthalpy) of compounds that are # molecular-like at room temperature elif rform in self.cpd_entropies and rform != "H2O": adjustments.append( TemperatureEnergyAdjustment( -1 * self.cpd_entropies[rform] / 298, 298, comp.num_atoms, name="Compound entropy at room temperature", cls=self.as_dict(), description= "Adds the entropy (T delta S) to energies of compounds that " "are gaseous or liquid at standard state")) # TODO - detection of embedded water molecules is not very sophisticated # Should be replaced with some kind of actual structure detection # For any compound except water, check to see if it is a hydrate (contains) # H2O in its structure. If so, adjust the energy to remove MU_H2O ev per # embedded water molecule. # in other words, we assume that the DFT energy of such a compound is really # a superposition of the "real" solid DFT energy (FeO in this case) and the free # energy of some water molecules # e.g. that E_FeO.nH2O = E_FeO + n * g_H2O # so, to get the most accurate gibbs free energy, we want to replace # g_FeO.nH2O = E_FeO.nH2O + dE_Fe + (n+1) * dE_O + 2n dE_H # with # g_FeO = E_FeO.nH2O + dE_Fe + dE_O + n g_H2O # where E is DFT energy, dE is an energy correction, and g is gibbs free energy # This means we have to 1) remove energy corrections associated with H and O in water # and then 2) remove the free energy of the water molecules if not rform == "H2O": # count the number of whole water molecules in the composition nH2O = int(min(comp["H"] / 2.0, comp["O"])) if nH2O > 0: # first, remove any H or O corrections already applied to H2O in the # formation energy so that we don't double count them # next, remove MU_H2O for each water molecule present hydrate_adjustment = -1 * (self.h2o_adjustments * 3 + MU_H2O) adjustments.append( CompositionEnergyAdjustment( hydrate_adjustment, nH2O, name="MP Aqueous hydrate", cls=self.as_dict(), description= "Adjust the energy of solid hydrate compounds (compounds " "containing H2O molecules in their structure) so that the " "free energies of embedded H2O molecules match the experimental" " value enforced by the MP Aqueous energy referencing scheme." )) return adjustments