Skip to content

Commit 4f536fc

Browse files
authored
to_jdftxinfile method for JDFTXOutfile (#4408)
* `strip_structure_tags` method and testing for `JDFTXInfile` * `to_jdftxinfile` method for JDFTXOutfile and JDFTXOutfileSlice, JDFTXOutfile inherits its most recent slice's `infile`, TODO's for cleaning up outputs module * Although its default is printed as a float, providing a float instead of an int for this parameter will raise an error * Minimum value field for `FloatTag`s, which changes `FloatTag.write` to return an empty string if the requested value is not greater than the set minimum value. An empty string is chosen instead of raising an error as error-raising values are dumped for the internal infile by JDFTx, so raising an error here would gaurantee an initialization error for every out file without these fields specified in the in file. There are likely more float tags that need this field set. * todo
1 parent b572c38 commit 4f536fc

File tree

6 files changed

+101
-6
lines changed

6 files changed

+101
-6
lines changed

src/pymatgen/io/jdftx/generic_tags.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ class FloatTag(AbstractTag):
430430
"""
431431

432432
prec: int | None = None
433+
minval: float | None = None
433434

434435
def validate_value_type(self, tag: str, value: Any, try_auto_type_fix: bool = False) -> tuple[str, bool, Any]:
435436
"""Validate the type of the value for this tag.
@@ -472,6 +473,11 @@ def write(self, tag: str, value: Any) -> str:
472473
Returns:
473474
str: The tag and its value as a string.
474475
"""
476+
# Returning an empty string instead of raising an error as value == self.minval
477+
# will cause JDFTx to throw an error, but the internal infile dumps the value as
478+
# as the minval if not set by the user.
479+
if (self.minval is not None) and (not value > self.minval):
480+
return ""
475481
# pre-convert to string: self.prec+3 is minimum room for:
476482
# - sign, 1 integer left of decimal, decimal, and precision.
477483
# larger numbers auto add places to left of decimal

src/pymatgen/io/jdftx/inputs.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,17 @@ def validate_tags(
573573
warnmsg += "(Check earlier warnings for more details)\n"
574574
warnings.warn(warnmsg, stacklevel=2)
575575

576+
def strip_structure_tags(self) -> None:
577+
"""Strip all structural tags from the JDFTXInfile.
578+
579+
Strip all structural tags from the JDFTXInfile. This is useful for preparing a JDFTXInfile object
580+
from a pre-existing calculation for a new structure.
581+
"""
582+
strucural_tags = ["lattice", "ion", "lattice-scale", "coords-type"]
583+
for tag in strucural_tags:
584+
if tag in self:
585+
del self[tag]
586+
576587
def __setitem__(self, key: str, value: Any) -> None:
577588
"""Set an item in the JDFTXInfile.
578589

src/pymatgen/io/jdftx/jdftxinfile_ref_options.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -792,15 +792,15 @@
792792
"knormThreshold": FloatTag(),
793793
"linminMethod": StrTag(options=["CubicWolfe", "DirUpdateRecommended", "Quad", "Relax"]),
794794
"maxThreshold": BoolTag(),
795-
"nAlphaAdjustMax": FloatTag(),
795+
"nAlphaAdjustMax": IntTag(),
796796
"nEnergyDiff": IntTag(),
797797
"nIterations": IntTag(),
798798
"updateTestStepSize": BoolTag(),
799799
"wolfeEnergy": FloatTag(),
800800
"wolfeGradient": FloatTag(),
801801
}
802802
jdftxfluid_subtagdict = {
803-
"epsBulk": FloatTag(),
803+
"epsBulk": FloatTag(minval=1.0),
804804
"epsInf": FloatTag(),
805805
"epsLJ": FloatTag(),
806806
"Nnorm": FloatTag(),
@@ -814,12 +814,12 @@
814814
"A0": FloatTag(write_tagname=False, optional=False),
815815
},
816816
),
817-
"Pvap": FloatTag(),
817+
"Pvap": FloatTag(minval=0.0),
818818
"quad_nAlpha": FloatTag(),
819819
"quad_nBeta": FloatTag(),
820820
"quad_nGamma": FloatTag(),
821821
"representation": TagContainer(subtags={"MuEps": FloatTag(), "Pomega": FloatTag(), "PsiAlpha": FloatTag()}),
822-
"Res": FloatTag(),
822+
"Res": FloatTag(minval=0.0),
823823
"Rvdw": FloatTag(),
824824
"s2quadType": StrTag(
825825
options=[
@@ -844,7 +844,7 @@
844844
"Tetrahedron",
845845
]
846846
),
847-
"sigmaBulk": FloatTag(),
847+
"sigmaBulk": FloatTag(minval=0.0),
848848
"tauNuc": FloatTag(),
849849
"translation": StrTag(options=["ConstantSpline", "Fourier", "LinearSpline"]),
850850
}

src/pymatgen/io/jdftx/jdftxoutfileslice.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,10 @@ def _set_internal_infile(self, text: list[str]) -> None:
391391
Args:
392392
text (list[str]): Output of read_file for out file.
393393
"""
394-
start_line_idx = find_key("Input parsed successfully", text) + 2
394+
start_line_idx = find_key("Input parsed successfully", text)
395+
if start_line_idx is None:
396+
raise ValueError("JDFTx input parsing failed on most recent call.")
397+
start_line_idx += 2
395398
end_line_idx = None
396399
for i in range(start_line_idx, len(text)):
397400
if not len(text[i].strip()):
@@ -1157,6 +1160,24 @@ def determine_is_metal(self) -> bool | None:
11571160
raise ValueError("Cannot determine if system is metal - self.nspin undefined")
11581161
return None
11591162

1163+
def to_jdftxinfile(self) -> JDFTXInfile:
1164+
"""
1165+
Convert the JDFTXOutfile object to a JDFTXInfile object with the most recent structure.
1166+
If the input structure is desired, simply fetch JDFTXOutfile.infile
1167+
1168+
Returns:
1169+
JDFTXInfile: A JDFTXInfile object representing the input parameters of the JDFTXOutfile.
1170+
"""
1171+
# Use internal infile as a reference for calculation parameters
1172+
base_infile = self.infile.copy()
1173+
# Strip references to the input
1174+
base_infile.strip_structure_tags()
1175+
if self.structure is None:
1176+
return base_infile
1177+
infile = JDFTXInfile.from_structure(self.structure)
1178+
infile += base_infile
1179+
return infile
1180+
11601181
def _check_solvation(self) -> bool:
11611182
"""Check for implicit solvation.
11621183

src/pymatgen/io/jdftx/outputs.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class is written.
2727
if TYPE_CHECKING:
2828
from pymatgen.core.structure import Structure
2929
from pymatgen.core.trajectory import Trajectory
30+
from pymatgen.io.jdftx.inputs import JDFTXInfile
3031
from pymatgen.io.jdftx.jelstep import JElSteps
3132
from pymatgen.io.jdftx.jminsettings import (
3233
JMinSettingsElectronic,
@@ -287,8 +288,43 @@ def _store_eigenvals(self):
287288
"electronic_output",
288289
"t_s",
289290
"ecomponents",
291+
"infile",
290292
)
291293

294+
# TODO: Remove references to the deprecated 'jsettings_*' attributes in `JDFTXOutfile` and `JDFTXOutfileSlice`
295+
# TODO: JDFTXOutfile and JDFTXOutfileSlice are VERY bloated. The following attributes are redundant
296+
# to fields of the `Structure` object and their removal likely won't cause any confusion.
297+
# - lattice
298+
# - lattice_final
299+
# - atom_coords
300+
# - atom_coords_final
301+
# - a/b/c
302+
# The following attributes are redundant to the internal `JDFTXInfile` object, but since the JDFTXInfile
303+
# object is not a standard pymatgen object, it may be better to decorate them with a deprecated decorator
304+
# until end of year. Additionally, it should be made certain which of these are dumped in the outfile's
305+
# input section regardless of what was set in the input file - for those that are not, they need to be kept.
306+
# - lattice_initial
307+
# - atom_coords_initial
308+
# - broadening
309+
# - broadening_type
310+
# - kgrid
311+
# - truncation_type
312+
# - truncation_radius
313+
# - fluid
314+
# - pwcut
315+
# - rhocut
316+
# The following are attributes that come from the electronic/fluid/ionic/lattice optimization step
317+
# logs. I am not sure if it is actually helpful to have access to these from the `JDFTXOutfile` object,
318+
# as they are only informative for analyzing optimization convergence. Additionally, the fields are
319+
# less universal than I thought, and can change depending on the optimization settings, so it might
320+
# be smarter to store them in a dictionary of arbitrary keys instead within the contained substructures.
321+
# - grad_k
322+
# - alpha
323+
# - linmin
324+
# - elec_grad_k
325+
# - elec_alpha
326+
# - elec_linmin
327+
292328

293329
@dataclass
294330
class JDFTXOutfile:
@@ -420,6 +456,7 @@ class JDFTXOutfile:
420456
elec_alpha (float): The step size of the final electronic step in the most recent JDFTx call.
421457
elec_linmin (float): The final normalized projection of the electronic step direction onto the gradient for the
422458
most recent JDFTx call.
459+
infile (JDFTXInfile): The JDFTXInfile object representing the input parameters of the JDFTXOutfile.
423460
424461
Magic Methods:
425462
__getitem__(key: str | int) -> Any: Decides behavior of how JDFTXOutfile objects are indexed. If the key is a
@@ -506,6 +543,7 @@ class JDFTXOutfile:
506543
elec_alpha: float = field(init=False)
507544
elec_linmin: float = field(init=False)
508545
electronic_output: float = field(init=False)
546+
infile: JDFTXInfile = field(init=False)
509547

510548
@classmethod
511549
def from_calc_dir(
@@ -558,6 +596,17 @@ def from_file(
558596
]
559597
return cls(slices=slices)
560598

599+
# TODO: Write testing for this function
600+
def to_jdftxinfile(self) -> JDFTXInfile:
601+
"""
602+
Convert the JDFTXOutfile object to a JDFTXInfile object with the most recent structure.
603+
If the input structure is desired, simply fetch JDFTXOutfile.infile
604+
605+
Returns:
606+
JDFTXInfile: A JDFTXInfile object representing the input parameters of the JDFTXOutfile.
607+
"""
608+
return self.slices[-1].to_jdftxinfile()
609+
561610
def __post_init__(self):
562611
last_slice = None
563612
for slc in self.slices[::-1]:

tests/io/jdftx/test_jdftxinfile.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,14 @@ def test_JDFTXInfile_expected_exceptions():
186186
jif[1.2] = 3.4
187187

188188

189+
def test_JDFTXInfile_strip_structure():
190+
jif = JDFTXInfile.from_file(ex_infile1_fname)
191+
structural_tags = ["lattice", "ion", "coords-type"]
192+
assert all(tag in jif for tag in structural_tags)
193+
jif.strip_structure_tags()
194+
assert all(tag not in jif for tag in structural_tags)
195+
196+
189197
def test_JDFTXInfile_niche_cases():
190198
jif = JDFTXInfile.from_file(ex_infile1_fname)
191199
tag_object, tag, value = jif._preprocess_line("dump-only")

0 commit comments

Comments
 (0)