1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
From: =?UTF-8?q?Se=C3=A1n=20Kavanagh?= <51478689+kavanase@users.noreply.github.com>
Subject: Use `np.nan` instead of 0 for no uncertainty with `ufloat`, to avoid unnecessary warnings (#4400)
Origin: https://github.com/materialsproject/pymatgen/commit/22834931904cfd138206a57684fff20ba0469580
* Use `np.nan` instead of 0 for no uncertainty with `ufloat`, as recommended
* Update rxn uncertainty handling and typing fix
---------
Co-authored-by: Shyue Ping Ong <shyuep@users.noreply.github.com>
---
src/pymatgen/analysis/reaction_calculator.py | 13 +++++++---
src/pymatgen/entries/compatibility.py | 26 ++++++++++++--------
src/pymatgen/entries/computed_entries.py | 10 ++++----
tests/analysis/test_reaction_calculator.py | 2 +-
4 files changed, 31 insertions(+), 20 deletions(-)
--- a/src/pymatgen/analysis/reaction_calculator.py
+++ b/src/pymatgen/analysis/reaction_calculator.py
@@ -10,7 +10,7 @@
import numpy as np
from monty.fractions import gcd_float
from monty.json import MontyDecoder, MSONable
-from uncertainties import ufloat
+from uncertainties import UFloat, ufloat
from pymatgen.core.composition import Composition
from pymatgen.entries.computed_entries import ComputedEntry
@@ -100,7 +100,7 @@
__repr__ = __str__
@overload
- def calculate_energy(self, energies: dict[Composition, ufloat]) -> ufloat:
+ def calculate_energy(self, energies: dict[Composition, ufloat]) -> UFloat:
pass
@overload
@@ -485,10 +485,15 @@
for entry in self._reactant_entries + self._product_entries:
comp, factor = entry.composition.get_reduced_composition_and_factor()
- energy_ufloat = ufloat(entry.energy, entry.correction_uncertainty)
+ energy_ufloat = (
+ ufloat(entry.energy, entry.correction_uncertainty)
+ if entry.correction_uncertainty and not np.isnan(entry.correction_uncertainty)
+ else entry.energy
+ )
calc_energies[comp] = min(calc_energies.get(comp, float("inf")), energy_ufloat / factor)
- return self.calculate_energy(calc_energies).std_dev
+ ufloat_reaction_energy = self.calculate_energy(calc_energies)
+ return ufloat_reaction_energy.std_dev if isinstance(ufloat_reaction_energy, UFloat) else np.nan
def as_dict(self) -> dict:
"""
--- a/src/pymatgen/entries/compatibility.py
+++ b/src/pymatgen/entries/compatibility.py
@@ -121,16 +121,14 @@
"""
new_corr = self.get_correction(entry)
old_std_dev = entry.correction_uncertainty
- if np.isnan(old_std_dev):
- old_std_dev = 0
- old_corr = ufloat(entry.correction, old_std_dev)
+ old_corr = ufloat(entry.correction, 0 if np.isnan(old_std_dev) else old_std_dev)
updated_corr = new_corr + old_corr
# if there are no error values available for the corrections applied,
# set correction uncertainty to not a number
- uncertainty = np.nan if updated_corr.nominal_value != 0 and updated_corr.std_dev == 0 else updated_corr.std_dev
-
- entry.energy_adjustments.append(ConstantEnergyAdjustment(updated_corr.nominal_value, uncertainty))
+ entry.energy_adjustments.append(
+ ConstantEnergyAdjustment(updated_corr.nominal_value, updated_corr.std_dev or np.nan)
+ )
return entry
@@ -195,7 +193,7 @@
ufloat: 0.0 +/- 0.0 (from uncertainties package)
"""
if SETTINGS.get("PMG_POTCAR_CHECKS") is False or not self.check_potcar:
- return ufloat(0.0, 0.0)
+ return ufloat(0.0, np.nan)
potcar_spec = entry.parameters.get("potcar_spec")
if self.check_hash:
@@ -211,7 +209,7 @@
expected_psp = {self.valid_potcars.get(el.symbol) for el in entry.elements}
if expected_psp != psp_settings:
raise CompatibilityError(f"Incompatible POTCAR {psp_settings}, expected {expected_psp}")
- return ufloat(0.0, 0.0)
+ return ufloat(0.0, np.nan)
@cached_class
@@ -249,6 +247,8 @@
if rform in self.cpd_energies:
correction += self.cpd_energies[rform] * comp.num_atoms - entry.uncorrected_energy
+ if correction.std_dev == 0:
+ correction = ufloat(correction.nominal_value, np.nan) # set std_dev to nan if no uncertainty
return correction
@@ -286,7 +286,7 @@
"""
comp = entry.composition
if len(comp) == 1: # Skip element entry
- return ufloat(0.0, 0.0)
+ return ufloat(0.0, np.nan)
correction = ufloat(0.0, 0.0)
@@ -345,6 +345,8 @@
else:
correction += self.oxide_correction["oxide"] * comp["O"]
+ if correction.std_dev == 0:
+ correction = ufloat(correction.nominal_value, np.nan) # set std_dev to nan if no uncertainty
return correction
@@ -432,6 +434,8 @@
correction += ufloat(-1 * MU_H2O * nH2O, 0.0)
# correction += 0.5 * 2.46 * nH2O # this is the old way this correction was calculated
+ if correction.std_dev == 0:
+ correction = ufloat(correction.nominal_value, np.nan) # set std_dev to nan if no uncertainty
return correction
@@ -537,6 +541,8 @@
if sym in u_corr:
correction += ufloat(u_corr[sym], u_errors[sym]) * comp[el]
+ if correction.std_dev == 0:
+ correction = ufloat(correction.nominal_value, np.nan) # set std_dev to nan if no uncertainty
return correction
@@ -1077,7 +1083,7 @@
)
# check the POTCAR symbols
- # this should return ufloat(0, 0) or raise a CompatibilityError or ValueError
+ # this should return ufloat(0, np.nan) or raise a CompatibilityError or ValueError
if entry.parameters.get("software", "vasp") == "vasp":
pc = PotcarCorrection(
MPRelaxSet,
--- a/src/pymatgen/entries/computed_entries.py
+++ b/src/pymatgen/entries/computed_entries.py
@@ -363,8 +363,8 @@
Returns:
float: the total energy correction / adjustment applied to the entry in eV.
"""
- # adds to ufloat(0.0, 0.0) to ensure that no corrections still result in ufloat object
- corr = ufloat(0.0, 0.0) + sum(ufloat(ea.value, ea.uncertainty) for ea in self.energy_adjustments)
+ # either sum of adjustments or ufloat with nan std_dev, so that no corrections still result in ufloat object:
+ corr = sum(ufloat(ea.value, ea.uncertainty) for ea in self.energy_adjustments) or ufloat(0.0, np.nan)
return corr.nominal_value
@correction.setter
@@ -386,11 +386,11 @@
Returns:
float: the uncertainty of the energy adjustments applied to the entry in eV.
"""
- # adds to ufloat(0.0, 0.0) to ensure that no corrections still result in ufloat object
- unc = ufloat(0.0, 0.0) + sum(
+ # either sum of adjustments or ufloat with nan std_dev, so that no corrections still result in ufloat object:
+ unc = sum(
(ufloat(ea.value, ea.uncertainty) if not np.isnan(ea.uncertainty) else ufloat(ea.value, 0))
for ea in self.energy_adjustments
- )
+ ) or ufloat(0.0, np.nan)
if unc.nominal_value != 0 and unc.std_dev == 0:
return np.nan
--- a/tests/analysis/test_reaction_calculator.py
+++ b/tests/analysis/test_reaction_calculator.py
@@ -444,7 +444,7 @@
def test_calculated_reaction_energy_uncertainty_for_no_uncertainty(self):
# test that reaction_energy_uncertainty property doesn't cause errors
# when products/reactants have no uncertainties
- assert self.rxn.calculated_reaction_energy_uncertainty == 0
+ assert np.isnan(self.rxn.calculated_reaction_energy_uncertainty)
def test_calculated_reaction_energy_uncertainty_for_nan(self):
# test that reaction_energy_uncertainty property is nan when the uncertainty
|