Skip to content

to_jdftxinfile method for JDFTXOutfile #4408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/pymatgen/io/jdftx/generic_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ class FloatTag(AbstractTag):
"""

prec: int | None = None
minval: float | None = None

def validate_value_type(self, tag: str, value: Any, try_auto_type_fix: bool = False) -> tuple[str, bool, Any]:
"""Validate the type of the value for this tag.
Expand Down Expand Up @@ -472,6 +473,11 @@ def write(self, tag: str, value: Any) -> str:
Returns:
str: The tag and its value as a string.
"""
# Returning an empty string instead of raising an error as value == self.minval
# will cause JDFTx to throw an error, but the internal infile dumps the value as
# as the minval if not set by the user.
if (self.minval is not None) and (not value > self.minval):
return ""
# pre-convert to string: self.prec+3 is minimum room for:
# - sign, 1 integer left of decimal, decimal, and precision.
# larger numbers auto add places to left of decimal
Expand Down
11 changes: 11 additions & 0 deletions src/pymatgen/io/jdftx/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,17 @@ def validate_tags(
warnmsg += "(Check earlier warnings for more details)\n"
warnings.warn(warnmsg, stacklevel=2)

def strip_structure_tags(self) -> None:
"""Strip all structural tags from the JDFTXInfile.

Strip all structural tags from the JDFTXInfile. This is useful for preparing a JDFTXInfile object
from a pre-existing calculation for a new structure.
"""
strucural_tags = ["lattice", "ion", "lattice-scale", "coords-type"]
for tag in strucural_tags:
if tag in self:
del self[tag]

def __setitem__(self, key: str, value: Any) -> None:
"""Set an item in the JDFTXInfile.

Expand Down
10 changes: 5 additions & 5 deletions src/pymatgen/io/jdftx/jdftxinfile_ref_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -792,15 +792,15 @@
"knormThreshold": FloatTag(),
"linminMethod": StrTag(options=["CubicWolfe", "DirUpdateRecommended", "Quad", "Relax"]),
"maxThreshold": BoolTag(),
"nAlphaAdjustMax": FloatTag(),
"nAlphaAdjustMax": IntTag(),
"nEnergyDiff": IntTag(),
"nIterations": IntTag(),
"updateTestStepSize": BoolTag(),
"wolfeEnergy": FloatTag(),
"wolfeGradient": FloatTag(),
}
jdftxfluid_subtagdict = {
"epsBulk": FloatTag(),
"epsBulk": FloatTag(minval=1.0),
"epsInf": FloatTag(),
"epsLJ": FloatTag(),
"Nnorm": FloatTag(),
Expand All @@ -814,12 +814,12 @@
"A0": FloatTag(write_tagname=False, optional=False),
},
),
"Pvap": FloatTag(),
"Pvap": FloatTag(minval=0.0),
"quad_nAlpha": FloatTag(),
"quad_nBeta": FloatTag(),
"quad_nGamma": FloatTag(),
"representation": TagContainer(subtags={"MuEps": FloatTag(), "Pomega": FloatTag(), "PsiAlpha": FloatTag()}),
"Res": FloatTag(),
"Res": FloatTag(minval=0.0),
"Rvdw": FloatTag(),
"s2quadType": StrTag(
options=[
Expand All @@ -844,7 +844,7 @@
"Tetrahedron",
]
),
"sigmaBulk": FloatTag(),
"sigmaBulk": FloatTag(minval=0.0),
"tauNuc": FloatTag(),
"translation": StrTag(options=["ConstantSpline", "Fourier", "LinearSpline"]),
}
23 changes: 22 additions & 1 deletion src/pymatgen/io/jdftx/jdftxoutfileslice.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,10 @@ def _set_internal_infile(self, text: list[str]) -> None:
Args:
text (list[str]): Output of read_file for out file.
"""
start_line_idx = find_key("Input parsed successfully", text) + 2
start_line_idx = find_key("Input parsed successfully", text)
if start_line_idx is None:
raise ValueError("JDFTx input parsing failed on most recent call.")
start_line_idx += 2
end_line_idx = None
for i in range(start_line_idx, len(text)):
if not len(text[i].strip()):
Expand Down Expand Up @@ -1157,6 +1160,24 @@ def determine_is_metal(self) -> bool | None:
raise ValueError("Cannot determine if system is metal - self.nspin undefined")
return None

def to_jdftxinfile(self) -> JDFTXInfile:
"""
Convert the JDFTXOutfile object to a JDFTXInfile object with the most recent structure.
If the input structure is desired, simply fetch JDFTXOutfile.infile

Returns:
JDFTXInfile: A JDFTXInfile object representing the input parameters of the JDFTXOutfile.
"""
# Use internal infile as a reference for calculation parameters
base_infile = self.infile.copy()
# Strip references to the input
base_infile.strip_structure_tags()
if self.structure is None:
return base_infile
infile = JDFTXInfile.from_structure(self.structure)
infile += base_infile
return infile

def _check_solvation(self) -> bool:
"""Check for implicit solvation.

Expand Down
49 changes: 49 additions & 0 deletions src/pymatgen/io/jdftx/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class is written.
if TYPE_CHECKING:
from pymatgen.core.structure import Structure
from pymatgen.core.trajectory import Trajectory
from pymatgen.io.jdftx.inputs import JDFTXInfile
from pymatgen.io.jdftx.jelstep import JElSteps
from pymatgen.io.jdftx.jminsettings import (
JMinSettingsElectronic,
Expand Down Expand Up @@ -287,8 +288,43 @@ def _store_eigenvals(self):
"electronic_output",
"t_s",
"ecomponents",
"infile",
)

# TODO: Remove references to the deprecated 'jsettings_*' attributes in `JDFTXOutfile` and `JDFTXOutfileSlice`
# TODO: JDFTXOutfile and JDFTXOutfileSlice are VERY bloated. The following attributes are redundant
# to fields of the `Structure` object and their removal likely won't cause any confusion.
# - lattice
# - lattice_final
# - atom_coords
# - atom_coords_final
# - a/b/c
# The following attributes are redundant to the internal `JDFTXInfile` object, but since the JDFTXInfile
# object is not a standard pymatgen object, it may be better to decorate them with a deprecated decorator
# until end of year. Additionally, it should be made certain which of these are dumped in the outfile's
# input section regardless of what was set in the input file - for those that are not, they need to be kept.
# - lattice_initial
# - atom_coords_initial
# - broadening
# - broadening_type
# - kgrid
# - truncation_type
# - truncation_radius
# - fluid
# - pwcut
# - rhocut
# The following are attributes that come from the electronic/fluid/ionic/lattice optimization step
# logs. I am not sure if it is actually helpful to have access to these from the `JDFTXOutfile` object,
# as they are only informative for analyzing optimization convergence. Additionally, the fields are
# less universal than I thought, and can change depending on the optimization settings, so it might
# be smarter to store them in a dictionary of arbitrary keys instead within the contained substructures.
# - grad_k
# - alpha
# - linmin
# - elec_grad_k
# - elec_alpha
# - elec_linmin


@dataclass
class JDFTXOutfile:
Expand Down Expand Up @@ -420,6 +456,7 @@ class JDFTXOutfile:
elec_alpha (float): The step size of the final electronic step in the most recent JDFTx call.
elec_linmin (float): The final normalized projection of the electronic step direction onto the gradient for the
most recent JDFTx call.
infile (JDFTXInfile): The JDFTXInfile object representing the input parameters of the JDFTXOutfile.

Magic Methods:
__getitem__(key: str | int) -> Any: Decides behavior of how JDFTXOutfile objects are indexed. If the key is a
Expand Down Expand Up @@ -506,6 +543,7 @@ class JDFTXOutfile:
elec_alpha: float = field(init=False)
elec_linmin: float = field(init=False)
electronic_output: float = field(init=False)
infile: JDFTXInfile = field(init=False)

@classmethod
def from_calc_dir(
Expand Down Expand Up @@ -558,6 +596,17 @@ def from_file(
]
return cls(slices=slices)

# TODO: Write testing for this function
def to_jdftxinfile(self) -> JDFTXInfile:
"""
Convert the JDFTXOutfile object to a JDFTXInfile object with the most recent structure.
If the input structure is desired, simply fetch JDFTXOutfile.infile

Returns:
JDFTXInfile: A JDFTXInfile object representing the input parameters of the JDFTXOutfile.
"""
return self.slices[-1].to_jdftxinfile()

def __post_init__(self):
last_slice = None
for slc in self.slices[::-1]:
Expand Down
8 changes: 8 additions & 0 deletions tests/io/jdftx/test_jdftxinfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@ def test_JDFTXInfile_expected_exceptions():
jif[1.2] = 3.4


def test_JDFTXInfile_strip_structure():
jif = JDFTXInfile.from_file(ex_infile1_fname)
structural_tags = ["lattice", "ion", "coords-type"]
assert all(tag in jif for tag in structural_tags)
jif.strip_structure_tags()
assert all(tag not in jif for tag in structural_tags)


def test_JDFTXInfile_niche_cases():
jif = JDFTXInfile.from_file(ex_infile1_fname)
tag_object, tag, value = jif._preprocess_line("dump-only")
Expand Down