Skip to content

Nexus: update PWscf analyzer - #6196

Open
jtkrogel wants to merge 34 commits into
QMCPACK:developfrom
jtkrogel:nx_pwscf_analyzer_dev2
Open

Nexus: update PWscf analyzer#6196
jtkrogel wants to merge 34 commits into
QMCPACK:developfrom
jtkrogel:nx_pwscf_analyzer_dev2

Conversation

@jtkrogel

@jtkrogel jtkrogel commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

This PR generalizes and hardens the PWscf analyzer class. Capabilities are limited to scf, nscf, and relax runs.

Many real input/output files from PWscf are included. These are there to support testing here and for the roll-forward.

GPT-5.6 Sol was used

@github-actions github-actions Bot added nexus documentation python Pull requests that update python code labels Sep 1, 2026
@prckent
prckent marked this pull request as draft September 1, 2026 21:12
@jtkrogel
jtkrogel marked this pull request as ready for review September 1, 2026 21:33
@prckent

prckent commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

What are the plans to document the analyzer?

@jtkrogel

jtkrogel commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

I plan to make a "Working with PWscf" manual section with analyzer use as a subsection. I can add a first version after this PR if desired. I also have a few expanding PRs to add on to this (code is done, just rolling out a portion at a time to aid review).

Let me know your preference.

@rcclay
rcclay self-requested a review September 2, 2026 16:06
rcclay
rcclay previously approved these changes Sep 2, 2026
# Conflicts:
#	nexus/nexus/examples/qmcpack/rsqmc_misc/excited/band.py
#	nexus/nexus/examples/quantum_espresso/relax_Ge_T_vs_kpoints/relax_vs_kpoints_example.py
#	nexus/nexus/pwscf_analyzer.py
rcclay
rcclay previously approved these changes Sep 2, 2026

@brockdyer03 brockdyer03 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another partial review since I don't have enough time to comb through the LLM's code. As I expected, there are very similar problems in this PR as in #6194.

Things like using obj for class attributes inherently precludes automatic documentation and makes this code harder to maintain.

As a general remark, I want to stress that more flexible code does not always mean the code is more robust.

Comment thread nexus/nexus/pwscf_analyzer.py Outdated
Comment thread nexus/nexus/pwscf_analyzer.py Outdated
Comment thread nexus/nexus/pwscf_analyzer.py Outdated
# ``the spin up/dw Fermi energies are 5.1 5.2 ev``. Missing eV units, three
# energies, ``highest occupied level``, and prose merely mentioning Fermi
# energy fail.
fermi_energies_pattern = (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only used in one place, move inline.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

for line in lines:
if not has_cell and line.strip().startswith('CELL_PARAMETERS'):
has_cell = True
if not has_bfgs and 'BFGS Geometry Optimization' in line:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use elif, avoids checking for each thing on lines that may have already had the preceding thing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will check

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The independent flags are intentionally cumulative

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That does not affect my comment. If a line has one of the strings then it does not have the other strings on it as well, so it should continue to the next line and not check the line for the other things.

self.read_energies(lines)
self.read_pressure(lines)
self.read_stress(lines)
self.read_forces(lines)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we read forces for scf but not structure, when clearly the forces rely on being able to parse a structure?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The structure is not changing from the already known input structure


def read_band_edges(self):
"""Add VBM and CBM energies to a parsed bands object."""
bands = self.bands

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why alias this? Just call self.bands

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will check

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local variable is clearer to read

unit.append(coordinates)
if not valid:
continue
self.kpoints_cart = np.array(cart,dtype=float)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, why are we storing these as their own objects instead of in a Structure? Then we don't need to store both cartesian and unit coordinate versions since Structure can do the conversion on its own.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reason as above

Comment thread nexus/nexus/pwscf_analyzer.py
Comment thread nexus/nexus/pwscf_analyzer.py Outdated
aforces = []
j = i+1
while j<len(lines):
match = re.search(atomic_force_pattern,lines[j])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we using regex for something that can be done with a simple string split?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will check

@jtkrogel

jtkrogel commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Comments addressed. Final changes made.

@brockdyer03 brockdyer03 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of problems and changes.

I am unsure of this code in its entirety. There are so many small problems and minute details that it would take ages to find everything.

float(value.lower().replace('d','e'))
for value in values
)
if len(fermi_energies)>0:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know, all successful QE calculations should have a Fermi energy printed. If this has zero, it should log an error/warning.

fermi_energies = []
for line in lines:
if 'Fermi energ' in line:
match = re.search(fermi_energies_pattern,line)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a regex here. The lines are printed as

     the Fermi energy is    10.1198 ev

A simple str.split() will work just fine.

if value is not None:
energy = value
if energy is not None:
self.E = energy

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implicit else part of this branch makes this is a silent failure. Add logging of some sort.

return time
#end def pwscf_time
calculation = 'scf'
self.calculation = calculation

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this defaults to scf if a known mode is not found. If QE adds any more run modes (not sure what that would be) this will silently assume scf, which is bad practice.

# Match the Fermi-energy result without collecting unrelated numbers earlier
# on the line. Real singular forms include ``the Fermi energy is 10.1198 ev``
# and ``the Fermi energy = -3.22772442 eV``; spin-polarized output may report
# ``the spin up/dw Fermi energies are 5.1 5.2 ev``. Missing eV units, three

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see no example files with the following formatting:

the spin up/dw Fermi energies are 5.1 5.2 ev

Why are we accepting that?

conf.axes = axes
i+=3
else:
conf = None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent failure.

)


def parse_float(text):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has the opportunity to fail silently every single time it is called, absolutely bad form. We should know if something is a float or not, and if this just fails silently then we are opening up the rest of the code to huge problems later on.

if len(aforces)>0 and (nat is None or len(aforces)==nat):
forces.append(aforces)
if len(forces)>0:
self.forces = np.array(forces,dtype=float)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent failure

]
unit.append(coordinates)
if not valid:
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent failure

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a lot of problems I am noticing in this parser.

The most egregious is the countless situations where a silent failure can occur. I've marked some of the more obvious ones, but there are just so many it's hard to even find them all.

The next problem is the copious use of obj. There are many, many situations where a data structure is created with obj that requires documenting it elsewhere. The code should largely be self-documenting, and this certainly is not. I let the obj slide in the RMG analyzer because I was, at the time, unaware of how much of a burden it would be.

Another issue is that every read_ function sets the internal variable instead of returning it. This makes the code extremely difficult, if not impossible, to unit test. Ideally the read_ functions would be static methods unless it is absolutely required that they have access to self, which many of them do not need.

I haven't even gotten to PwscfAnalyzer yet, so after discussion of the comments on PwscfOutData, I will review that next.

@jtkrogel jtkrogel Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The silent failures are not what you think. They are generally part of the design, i.e. read what is available and don't crash on parse. The physical quantities that are not available will return "None" when queried via the public/user-facing API. The exceptions raised on those relate to truly invalid requests on the part of the user; not failures in the code itself. The behavior of the read_* functions is intentional within this framework (parse/store/query).

I will remind you that obj is part of the Nexus codebase and in no way invalidates code committed within a PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation nexus python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants