Skip to content

Commit 5b3c57f

Browse files
committed
Merge branch 'master' of github.com:materialsproject/pymatgen
2 parents 873c3c6 + 757af92 commit 5b3c57f

File tree

10 files changed

+235
-93
lines changed

10 files changed

+235
-93
lines changed

src/pymatgen/analysis/reaction_calculator.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import numpy as np
1111
from monty.fractions import gcd_float
1212
from monty.json import MontyDecoder, MSONable
13-
from uncertainties import ufloat
13+
from uncertainties import UFloat, ufloat
1414

1515
from pymatgen.core.composition import Composition
1616
from pymatgen.entries.computed_entries import ComputedEntry
@@ -100,7 +100,7 @@ def __str__(self):
100100
__repr__ = __str__
101101

102102
@overload
103-
def calculate_energy(self, energies: dict[Composition, ufloat]) -> ufloat:
103+
def calculate_energy(self, energies: dict[Composition, ufloat]) -> UFloat:
104104
pass
105105

106106
@overload
@@ -485,10 +485,15 @@ def calculated_reaction_energy_uncertainty(self) -> float:
485485

486486
for entry in self._reactant_entries + self._product_entries:
487487
comp, factor = entry.composition.get_reduced_composition_and_factor()
488-
energy_ufloat = ufloat(entry.energy, entry.correction_uncertainty)
488+
energy_ufloat = (
489+
ufloat(entry.energy, entry.correction_uncertainty)
490+
if entry.correction_uncertainty and not np.isnan(entry.correction_uncertainty)
491+
else entry.energy
492+
)
489493
calc_energies[comp] = min(calc_energies.get(comp, float("inf")), energy_ufloat / factor)
490494

491-
return self.calculate_energy(calc_energies).std_dev
495+
ufloat_reaction_energy = self.calculate_energy(calc_energies)
496+
return ufloat_reaction_energy.std_dev if isinstance(ufloat_reaction_energy, UFloat) else np.nan
492497

493498
def as_dict(self) -> dict:
494499
"""

src/pymatgen/entries/compatibility.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,14 @@ def correct_entry(self, entry):
121121
"""
122122
new_corr = self.get_correction(entry)
123123
old_std_dev = entry.correction_uncertainty
124-
if np.isnan(old_std_dev):
125-
old_std_dev = 0
126-
old_corr = ufloat(entry.correction, old_std_dev)
124+
old_corr = ufloat(entry.correction, 0 if np.isnan(old_std_dev) else old_std_dev)
127125
updated_corr = new_corr + old_corr
128126

129127
# if there are no error values available for the corrections applied,
130128
# set correction uncertainty to not a number
131-
uncertainty = np.nan if updated_corr.nominal_value != 0 and updated_corr.std_dev == 0 else updated_corr.std_dev
132-
133-
entry.energy_adjustments.append(ConstantEnergyAdjustment(updated_corr.nominal_value, uncertainty))
129+
entry.energy_adjustments.append(
130+
ConstantEnergyAdjustment(updated_corr.nominal_value, updated_corr.std_dev or np.nan)
131+
)
134132

135133
return entry
136134

@@ -195,7 +193,7 @@ def get_correction(self, entry: AnyComputedEntry) -> ufloat:
195193
ufloat: 0.0 +/- 0.0 (from uncertainties package)
196194
"""
197195
if SETTINGS.get("PMG_POTCAR_CHECKS") is False or not self.check_potcar:
198-
return ufloat(0.0, 0.0)
196+
return ufloat(0.0, np.nan)
199197

200198
potcar_spec = entry.parameters.get("potcar_spec")
201199
if self.check_hash:
@@ -211,7 +209,7 @@ def get_correction(self, entry: AnyComputedEntry) -> ufloat:
211209
expected_psp = {self.valid_potcars.get(el.symbol) for el in entry.elements}
212210
if expected_psp != psp_settings:
213211
raise CompatibilityError(f"Incompatible POTCAR {psp_settings}, expected {expected_psp}")
214-
return ufloat(0.0, 0.0)
212+
return ufloat(0.0, np.nan)
215213

216214

217215
@cached_class
@@ -249,6 +247,8 @@ def get_correction(self, entry: ComputedEntry | ComputedStructureEntry) -> ufloa
249247
if rform in self.cpd_energies:
250248
correction += self.cpd_energies[rform] * comp.num_atoms - entry.uncorrected_energy
251249

250+
if correction.std_dev == 0:
251+
correction = ufloat(correction.nominal_value, np.nan) # set std_dev to nan if no uncertainty
252252
return correction
253253

254254

@@ -286,9 +286,11 @@ def get_correction(self, entry: ComputedEntry | ComputedStructureEntry) -> ufloa
286286
"""
287287
comp = entry.composition
288288
if len(comp) == 1: # Skip element entry
289-
return ufloat(0.0, 0.0)
289+
return ufloat(0.0, np.nan)
290290

291-
correction = ufloat(0.0, 0.0)
291+
with warnings.catch_warnings():
292+
warnings.filterwarnings("ignore", message="Using UFloat")
293+
correction = ufloat(0.0, 0.0)
292294

293295
# Check for sulfide corrections
294296
if Element("S") in comp:
@@ -345,6 +347,8 @@ def get_correction(self, entry: ComputedEntry | ComputedStructureEntry) -> ufloa
345347
else:
346348
correction += self.oxide_correction["oxide"] * comp["O"]
347349

350+
if correction.std_dev == 0:
351+
correction = ufloat(correction.nominal_value, np.nan) # set std_dev to nan if no uncertainty
348352
return correction
349353

350354

@@ -432,6 +436,8 @@ def get_correction(self, entry: ComputedEntry | ComputedStructureEntry) -> ufloa
432436
correction += ufloat(-1 * MU_H2O * nH2O, 0.0)
433437
# correction += 0.5 * 2.46 * nH2O # this is the old way this correction was calculated
434438

439+
if correction.std_dev == 0:
440+
correction = ufloat(correction.nominal_value, np.nan) # set std_dev to nan if no uncertainty
435441
return correction
436442

437443

@@ -537,6 +543,8 @@ def get_correction(self, entry: ComputedEntry | ComputedStructureEntry) -> ufloa
537543
if sym in u_corr:
538544
correction += ufloat(u_corr[sym], u_errors[sym]) * comp[el]
539545

546+
if correction.std_dev == 0:
547+
correction = ufloat(correction.nominal_value, np.nan) # set std_dev to nan if no uncertainty
540548
return correction
541549

542550

@@ -1076,7 +1084,7 @@ def get_adjustments(self, entry: AnyComputedEntry) -> list[CompositionEnergyAdju
10761084
)
10771085

10781086
# check the POTCAR symbols
1079-
# this should return ufloat(0, 0) or raise a CompatibilityError or ValueError
1087+
# this should return ufloat(0, np.nan) or raise a CompatibilityError or ValueError
10801088
if entry.parameters.get("software", "vasp") == "vasp":
10811089
pc = PotcarCorrection(
10821090
MPRelaxSet,

src/pymatgen/entries/computed_entries.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -363,8 +363,10 @@ def correction(self) -> float:
363363
Returns:
364364
float: the total energy correction / adjustment applied to the entry in eV.
365365
"""
366-
# adds to ufloat(0.0, 0.0) to ensure that no corrections still result in ufloat object
367-
corr = ufloat(0.0, 0.0) + sum(ufloat(ea.value, ea.uncertainty) for ea in self.energy_adjustments)
366+
# either sum of adjustments or ufloat with nan std_dev, so that no corrections still result in ufloat object:
367+
corr = sum(ufloat(ea.value, ea.uncertainty) for ea in self.energy_adjustments if ea.value) or ufloat(
368+
0.0, np.nan
369+
)
368370
return corr.nominal_value
369371

370372
@correction.setter
@@ -386,11 +388,11 @@ def correction_uncertainty(self) -> float:
386388
Returns:
387389
float: the uncertainty of the energy adjustments applied to the entry in eV.
388390
"""
389-
# adds to ufloat(0.0, 0.0) to ensure that no corrections still result in ufloat object
390-
unc = ufloat(0.0, 0.0) + sum(
391-
(ufloat(ea.value, ea.uncertainty) if not np.isnan(ea.uncertainty) else ufloat(ea.value, 0))
391+
# either sum of adjustments or ufloat with nan std_dev, so that no corrections still result in ufloat object:
392+
unc = sum(
393+
(ufloat(ea.value, ea.uncertainty) if not np.isnan(ea.uncertainty) and ea.value else ufloat(ea.value, 0))
392394
for ea in self.energy_adjustments
393-
)
395+
) or ufloat(0.0, np.nan)
394396

395397
if unc.nominal_value != 0 and unc.std_dev == 0:
396398
return np.nan

src/pymatgen/io/jdftx/inputs.py

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class is written.
2020
import scipy.constants as const
2121
from monty.json import MSONable
2222

23-
from pymatgen.core import Structure
23+
from pymatgen.core import Lattice, Structure
2424
from pymatgen.core.periodic_table import Element
2525
from pymatgen.core.units import bohr_to_ang
2626
from pymatgen.io.jdftx.generic_tags import AbstractTag, BoolTagContainer, DumpTagContainer, MultiformatTag, TagContainer
@@ -788,17 +788,8 @@ def from_jdftxinfile(cls, jdftxinfile: JDFTXInfile, sort_structure: bool = False
788788
Returns:
789789
JDFTXStructure: The created JDFTXStructure object.
790790
"""
791-
jl = jdftxinfile["lattice"]
792-
lattice = np.zeros([3, 3])
793-
for i in range(3):
794-
for j in range(3):
795-
lattice[i][j] += float(jl[f"R{i}{j}"])
796-
if "latt-scale" in jdftxinfile:
797-
latt_scale = np.array([jdftxinfile["latt-scale"][x] for x in ["s0", "s1", "s2"]])
798-
lattice *= latt_scale
799-
lattice = lattice.T # convert to row vector format
800-
lattice *= const.value("Bohr radius") * 10**10 # Bohr radius in Ang; convert to Ang
801791

792+
lattice = _infile_to_pmg_lattice(jdftxinfile)
802793
atomic_symbols = [x["species-id"] for x in jdftxinfile["ion"]]
803794
coords = np.array([[x["x0"], x["x1"], x["x2"]] for x in jdftxinfile["ion"]])
804795
coords *= const.value("Bohr radius") * 10**10 # Bohr radius in Ang; convert to Ang
@@ -1016,3 +1007,58 @@ def _multi_getattr(varbase: Any, varname: str):
10161007
for var in varlist:
10171008
varbase = getattr(varbase, var)
10181009
return varbase
1010+
1011+
1012+
def _infile_to_pmg_lattice(jdftxinfile: JDFTXInfile) -> Lattice:
1013+
jl = jdftxinfile["lattice"]
1014+
if "R00" not in jl:
1015+
return _infile_special_format_to_pmg_lattice(jdftxinfile)
1016+
return _infile_matrix_format_to_pmg_lattice(jdftxinfile)
1017+
1018+
1019+
def _infile_special_format_to_pmg_lattice(jdftxinfile: JDFTXInfile) -> Lattice:
1020+
jl = jdftxinfile["lattice"]
1021+
if "modification" in jl:
1022+
raise NotImplementedError("Special case lattices with modification not implemented yet")
1023+
# Second boolean to check if latt-scale was passed but does nothing
1024+
if ("latt-scale" in jl) and (not all(np.isclose(float(x), 1) in jl for x in ["s0", "s1", "s2"])):
1025+
raise NotImplementedError("latt-scale for special case lattices not implemented yet")
1026+
if "Monoclinic" in jl:
1027+
lattice = Lattice.monoclinic(jl["a"], jl["b"], jl["c"], jl["beta"])
1028+
elif "Orthorhombic" in jl:
1029+
lattice = Lattice.orthorhombic(jl["a"], jl["b"], jl["c"])
1030+
elif "Cubic" in jl:
1031+
lattice = Lattice.cubic(jl["a"])
1032+
elif "Hexagonal" in jl:
1033+
lattice = Lattice.hexagonal(jl["a"], jl["c"])
1034+
elif "Rhombohedral" in jl:
1035+
lattice = Lattice.rhombohedral(jl["a"], jl["alpha"])
1036+
elif "Tetragonal" in jl:
1037+
lattice = Lattice.tetragonal(jl["a"], jl["c"])
1038+
elif "Triclinic" in jl:
1039+
lattice = Lattice.from_parameters(jl["a"], jl["b"], jl["c"], jl["alpha"], jl["beta"], jl["gamma"])
1040+
else:
1041+
raise ValueError(f"Unable to convert JDFTX lattice tag {jl} to pymatgen Lattice object")
1042+
return lattice
1043+
1044+
1045+
def _infile_matrix_format_to_pmg_lattice(jdftxinfile: JDFTXInfile) -> Lattice:
1046+
"""Convert JDFTX lattice tag to pymatgen Lattice object.
1047+
1048+
Args:
1049+
jl (dict[str, Any]): JDFTX lattice tag.
1050+
1051+
Returns:
1052+
Lattice: Pymatgen Lattice object.
1053+
"""
1054+
_lattice = np.zeros([3, 3])
1055+
jl = jdftxinfile["lattice"]
1056+
for i in range(3):
1057+
for j in range(3):
1058+
_lattice[i][j] += float(jl[f"R{i}{j}"])
1059+
if "latt-scale" in jdftxinfile:
1060+
latt_scale = np.array([jdftxinfile["latt-scale"][x] for x in ["s0", "s1", "s2"]])
1061+
_lattice *= latt_scale
1062+
_lattice = _lattice.T # convert to row vector format
1063+
_lattice *= const.value("Bohr radius") * 10**10 # Bohr radius in Ang; convert to Ang
1064+
return Lattice(_lattice)

src/pymatgen/symmetry/analyzer.py

Lines changed: 40 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -771,95 +771,79 @@ def get_conventional_standard_structure(
771771
latt2 = Lattice([m[tp2[0]], m[tp2[1]], m[2]])
772772
lengths = latt2.lengths
773773
angles = latt2.angles
774-
if angles[0] > 90:
775-
# if the angle is > 90 we invert a and b to get
776-
# an angle < 90
777-
a, b, c, alpha, beta, gamma = Lattice([-m[tp2[0]], -m[tp2[1]], m[2]]).parameters
778-
transf = np.zeros(shape=(3, 3))
774+
alpha_degrees = angles[0]
775+
if alpha_degrees == 90:
776+
continue
777+
778+
transf = np.zeros(shape=(3, 3))
779+
transf[2][2] = 1
780+
781+
if alpha_degrees > 90:
782+
# if the angle is > 90 we invert a and b to get an angle < 90
783+
a, b, c, alpha_degrees, beta, gamma = Lattice([-m[tp2[0]], -m[tp2[1]], m[2]]).parameters
779784
transf[0][tp2[0]] = -1
780785
transf[1][tp2[1]] = -1
781-
transf[2][2] = 1
782-
alpha = math.pi * alpha / 180
783-
new_matrix = [
784-
[a, 0, 0],
785-
[0, b, 0],
786-
[0, c * cos(alpha), c * sin(alpha)],
787-
]
788-
continue
789786

790-
if angles[0] < 90:
791-
transf = np.zeros(shape=(3, 3))
787+
elif alpha_degrees < 90: # otherwise no inversion
792788
transf[0][tp2[0]] = 1
793789
transf[1][tp2[1]] = 1
794-
transf[2][2] = 1
795790
a, b, c = lengths
796-
alpha = math.pi * angles[0] / 180
797-
new_matrix = [
798-
[a, 0, 0],
799-
[0, b, 0],
800-
[0, c * cos(alpha), c * sin(alpha)],
801-
]
791+
alpha_degrees = angles[0]
792+
793+
alpha_radians = math.pi * alpha_degrees / 180
794+
new_matrix = [
795+
[a, 0, 0],
796+
[0, b, 0],
797+
[0, c * cos(alpha_radians), c * sin(alpha_radians)],
798+
]
802799

803800
if new_matrix is None:
804-
# this if is to treat the case
805-
# where alpha==90 (but we still have a monoclinic sg
801+
# this if is to treat the case where alpha==90 (but we still have a monoclinic sg), with C unchanged
802+
# transf is already defined as [[0,0,0],[0,0,0],[0,0,1]], and sorted_dic only contains a & b:
806803
new_matrix = [[a, 0, 0], [0, b, 0], [0, 0, c]]
807-
transf = np.zeros(shape=(3, 3))
808-
transf[2] = [0, 0, 1] # see issue #1929
809804
for idx, dct in enumerate(sorted_dic):
810805
transf[idx][dct["orig_index"]] = 1
806+
811807
# if not C-setting
812808
else:
813809
# try all permutations of the axis
814-
# keep the ones with the non-90 angle=alpha
815-
# and b<c
810+
# keep the ones with the non-90 angle=alpha and b<c
816811
new_matrix = None
817812

818813
for tp3 in itertools.permutations(list(range(3)), 3):
819814
m = lattice.matrix
820815
a, b, c, alpha, beta, gamma = Lattice([m[tp3[0]], m[tp3[1]], m[tp3[2]]]).parameters
816+
if alpha == 90 or b >= c:
817+
continue
818+
transf = np.zeros(shape=(3, 3))
819+
transf[2][tp3[2]] = 1
820+
821821
if alpha > 90 and b < c:
822822
a, b, c, alpha, beta, gamma = Lattice([-m[tp3[0]], -m[tp3[1]], m[tp3[2]]]).parameters
823-
transf = np.zeros(shape=(3, 3))
824823
transf[0][tp3[0]] = -1
825824
transf[1][tp3[1]] = -1
826-
transf[2][tp3[2]] = 1
827-
alpha = math.pi * alpha / 180
828-
new_matrix = [
829-
[a, 0, 0],
830-
[0, b, 0],
831-
[0, c * cos(alpha), c * sin(alpha)],
832-
]
833-
continue
834825

835-
if alpha < 90 and b < c:
836-
transf = np.zeros(shape=(3, 3))
826+
elif alpha < 90 and b < c:
837827
transf[0][tp3[0]] = 1
838828
transf[1][tp3[1]] = 1
839-
transf[2][tp3[2]] = 1
840-
alpha = math.pi * alpha / 180
841-
new_matrix = [
842-
[a, 0, 0],
843-
[0, b, 0],
844-
[0, c * cos(alpha), c * sin(alpha)],
845-
]
846829

847-
if new_matrix is None:
848-
# this if is to treat the case
849-
# where alpha==90 (but we still have a monoclinic sg
830+
alpha = math.pi * alpha / 180
850831
new_matrix = [
851-
[sorted_lengths[0], 0, 0],
852-
[0, sorted_lengths[1], 0],
853-
[0, 0, sorted_lengths[2]],
832+
[a, 0, 0],
833+
[0, b, 0],
834+
[0, c * cos(alpha), c * sin(alpha)],
854835
]
855-
transf = np.zeros(shape=(3, 3))
856-
for idx, dct in enumerate(sorted_dic):
836+
837+
if new_matrix is None:
838+
# this if is to treat the case where alpha==90 (but we still have a monoclinic sg)
839+
new_matrix = list(np.zeros(shape=(3, 3)))
840+
for idx, dct in enumerate(sorted_dic): # vectors sorted by length (a<=b<=c)
841+
new_matrix[idx] = dct["vec"]
857842
transf[idx][dct["orig_index"]] = 1
858843

859844
if international_monoclinic:
860845
# The above code makes alpha the non-right angle.
861-
# The following will convert to proper international convention
862-
# that beta is the non-right angle.
846+
# The following will convert to proper international convention that beta is the non-right angle
863847
op = [[0, 1, 0], [1, 0, 0], [0, 0, -1]]
864848
transf = np.dot(op, transf)
865849
new_matrix = np.dot(op, new_matrix)

0 commit comments

Comments
 (0)