Skip to content

SOF-8005: parse NWChem initial and final geometry - #165

Open
VsevolodX wants to merge 3 commits into
mainfrom
feature/SOF-8005
Open

SOF-8005: parse NWChem initial and final geometry#165
VsevolodX wants to merge 3 commits into
mainfrom
feature/SOF-8005

Conversation

@VsevolodX

Copy link
Copy Markdown
Member

This PR (express)

NwchemParser implemented none of IonicDataMixin's four geometry methods, whose bodies are bare pass. So lattice_basis_to_poscar raised on None, and every NWChem job ever run silently failed to publish its structuresextract_structures() runs app-agnostically for every modeling unit, and extract_property swallows the exception into a log.error on the compute node. That predates the relaxation work entirely.

One extractor keyed on the Output coordinates block, plus four thin delegates. Three things worth a reviewer's attention:

  • The block header carries the units the input declared, and they are not always angstrom. tests/fixtures/nwchem/test-001 is a geometry units au run that prints no angstrom block at all — the plan's instruction to key on the literal Output coordinates in angstroms would have returned None for it, leaving that silent failure open for every au-geometry job. The units word is read rather than assumed. An angstroms block is taken verbatim rather than rescaled through two Bohr radii that disagree in the last digits.
  • The cell is not ours to invent. made's calculate_padded_cell_simple_cubic, the same convention every non-periodic material on the platform gets. Initial and final therefore get different edges, as made would give them: 5.72171236 Å vs 3.05045924 Å for the H2O fixture. The basis takes its edge from _lattice_vectors so it is centered in the box that ships with it.
  • BASIS in tests/fixtures/nwchem/references.py was wrong and unused. It held test-001's printed a.u. geometry tagged "units": "angstrom" — added in the original NWChem PR and never referenced by any test, so nothing caught it. Corrected to that geometry converted and centered, and now actually asserted.

Tests: 22 passing (unit + integration), including a regression case for a numeric row that fits the column shape but has no element symbol — it is skipped rather than raising, because rupy would swallow the exception into a silently missing final_structure.

Pre-existing on main, untouched: 11 failures in tests/integration/parsers/test_structure.py, tests/unit/parsers/test_structure_parser.py and tests/integration/properties/test_material.py, all espresso structure parsing. Confirmed by stashing every change and re-running.


Part of SOF-8005[Standata] Relaxation SWF/WF for NWChem. One ticket, five repos; this is one of five PRs that land together.

The goal, stated once: relax a molecule the same way we relax a material.

total_energy computed HOMO/LUMO on whatever geometry was uploaded — nothing in the NWChem workflows relaxed anything, and get_relaxation_by_application("nwchem") returned {}, so Toggle Relaxation silently no-opped. These PRs close the four gaps between NWChem and what VASP/Espresso already do.

# Capability Repo
1 A relaxation subworkflow exists and Toggle Relaxation resolves it standata
2 The next unit computes on the relaxed geometry wode + esse + standata
3 The relaxed structure comes back as a Material express
4 A test proves it end to end web-app

Evidence

Acceptance test job-nwchem-relaxation-total-energy.feature passes against a live cluster — takes the unrelaxed H2O.poscar, toggles relaxation in the real UI, runs one job:

property value reference (NIST CCCBDB, B3LYP/6-31G)
homo_energy −7.880687 eV −7.880
lumo_energy 1.438531 eV 1.439

Tolerance is 0.02 eV, not the 0.1 eV the other nwchem features use: relaxed and unrelaxed HOMO differ by only 0.053 eV, so 0.1 would pass on an unrelaxed molecule — precisely what this test exists to catch. LUMO differs by 0.341 eV and is the discriminating assertion.

The sharpest evidence is not in the assertions. The relaxation unit reported total_energy = -2078.5728586862333 and the total-energy unit -2078.5728586879750agreeing to 2e-9 eV. The second unit reproduced the first's final energy, which it can only do by computing on the relaxed geometry; a start-from-scratch unit would have recomputed the original upload.

Manual checks

  • Rendered input of the total-energy unit says restart nwchem with no geometry block — read from a real job at job.workflow.subworkflows[].units[].input[0].rendered.
  • [~] The relaxation converged — HOMO/LUMO reproduce published equilibrium values to ~1 meV, which a truncated optimization would not. The literal Optimization converged string was not read; job files are in object storage behind the authenticated API.
  • A relaxed Material appears with a resolvable materialIdstill absent, and deploying express will not fully fix it. is_non_periodic appears nowhere in rupy and Material.__init__ defaults it to False, so the relaxed molecule builds through CrystalParser: cubic lattice type yes, isNonPeriodic no. Timur asked for exactly this on this exact file in express#55 (2020-12-08): "Are we able to retain the fact that this is an isolated molecule, rather than a periodic system…". Needs a rupy change; scoped out here and filed as a follow-up.
  • The generated box — 3.05045924 Å for relaxed water, 5.72171236 Å unrelaxed. Correct by made's convention; periodic images essentially touch, so it must be re-boxed before any periodic reuse.
  • Toggle Relaxation stamps a sane version/build{nwchem, 7.0.2, GNU, isDefaultVersion: true}.
  • npm and python standata both ship it — both lookups return Geometry Relaxation.
  • Frequency (nwchem) with no relaxation renders start nwchem, byte-identically to before.

Review

Two tb-reviewer passes. The first found two blockers — local file: tarball pins in web-app, and a jobHasParent || half that would have rendered a deck NWChem cannot run — both fixed. The second, grounded against the precedent corpus (12,340 comments / 73 repos), found that Toggle Relaxation had become reachable on Frequency (nwchem) while that template still declared its own geometry: it would have relaxed, discarded the result, and reported frequencies for the original molecule, silently. Fixed here.

Known and deliberately not fixed

  • rupy never passes is_non_periodic — above; needs a rupy change.
  • isRelaxed will read False on every NWChem relaxation once express is deployed. _is_relaxed() derives it from pressure and atomic_forces, and NwchemParser implements neither. Unreachable today because extraction fails first.
  • Relaxation resolves by application name alone (wode Workflow.ts:352), not by what the workflow computes. wode#100 already pointed this way.

🤖 Generated with Claude Code

NwchemParser implemented none of IonicDataMixin's four geometry methods,
whose bodies are bare `pass`, so lattice_basis_to_poscar raised on None
and every NWChem job silently failed to publish its structures --
extract_structures runs app-agnostically for every modeling unit, and
extract_property swallows the exception into a log.error.

One extractor keyed on the "Output coordinates" block, plus four thin
delegates. Notes:

  - The block header carries the units the input declared. test-001 is a
    `geometry units au` run and prints no angstrom block at all, so the
    units word is read rather than assumed; an angstroms block is taken
    verbatim rather than rescaled through two Bohr radii that disagree in
    the last digits.
  - The cell is not ours to invent: made's calculate_padded_cell_simple_cubic,
    the same convention every non-periodic material on the platform gets.
  - BASIS in tests/fixtures/nwchem/references.py held test-001's printed
    a.u. geometry mislabelled `"units": "angstrom"`. It was never used by
    any test, so nothing caught it. Corrected and now asserted.

Also returns those structures as molecules. NWChem works in the finite
molecular picture, but Material defaulted is_non_periodic to False and
rupy cannot correct it -- it only ever sees the material's _id, never the
material. So a relaxed molecule came back as a periodic crystal, with
volume/density where inchi/inchi_key belong. The parser now answers for
itself, an explicit kwarg still wins, and `isNonPeriodic` is serialized
so the flag survives to the platform. Timur asked for exactly this on
exactly this file in express#55 (2020-12-08).

Verified on a real job once deployed: H2O, CUB, inchi 1S/H2O/h1H2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
VsevolodX and others added 2 commits August 13, 2026 19:35
_lattice_vectors was derived per-block, so initial and final got
different boxes -- 5.72 A and 3.05 A for the same water molecule. The
final one shrink-wrapped the relaxed geometry: made's padding factor is
2x the max pairwise distance, which for water leaves ~0.8 A of vacuum a
side, and the two structures could not be compared.

An optimization moves atoms inside a fixed box; it does not resize the
box. The cell is now derived once, from the first geometry block, and
both structures share it -- fixed-cell semantics, and the relaxed molecule
sits in the roomier unrelaxed box instead of being wrapped tight.

Smaller than what it replaces: _lattice_vectors no longer takes `last`.
Still made's convention, unchanged; only how many times it is applied.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The cell was derived from the first geometry block alone, so a relaxation
that expands a molecule left atoms outside the box. Reproduced: a
diatomic going 0.2 -> 2.5 A landed at z = 4.0 and -1.0 in a 3.0 A box,
which reads as extra fragments and turns inchi 1S/H2O/h1H2 into
1S/H2O.2H2/h1H2;2*1H -- silently wrong chemistry, not a visible failure.

Still one cell for both, so the two structures stay comparable; it is now
sized to whichever geometry needs more room. Contracting relaxations,
the common case, are unaffected: the fixture still gives 5.72171236 for
both, and the H2O acceptance job still gives 3.163849.

Also drops four restatements of why the cell is shared, keeping the
explanation where the logic is. Found by review: the problem was
repetition, not volume -- express sits below this repo's docstring
density.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant