Skip to content

Reorganization energy: persist results, compare geometries, compare λ - #44

Merged
NCCU-Schultz-Lab merged 3 commits into
mainfrom
reorg-outputs
Aug 5, 2026
Merged

Reorganization energy: persist results, compare geometries, compare λ#44
NCCU-Schultz-Lab merged 3 commits into
mainfrom
reorg-outputs

Conversation

@NCCU-Schultz-Lab

@NCCU-Schultz-Lab NCCU-Schultz-Lab commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Fixes a student-reported bug and closes most of M-REORG.

The bug

Reorganization-energy results reloaded from History came back missing the numbers the calculation exists to produce.

It presented as a display bug and was not one. save_result wrote a fixed schema of scalar fields with no channels key, so the λ breakdown was never persisted — the live card rendered from an object still in memory, and the History card had nothing behind it. Repairing the formatter could not have worked, and would have been the obvious first thing to try.

Both cards now render from the same plain-dict payload, so "renders live" implies "renders after reload" by construction rather than by two implementations happening to agree. That drift was the bug, so removing the possibility of drift is the fix.

⚠️ Forward-only. Results saved before this cannot be recovered — λ is two optimizations and four SCF energies. Their cards say so and name the remedy, detected by absence of the payload, not by version or timestamp (a re-saved or imported result would defeat a version cutoff).

What a user gets

  • λ survives History, with the four-point energies, λ₁/λ₂, and geometry RMSD + largest atom shift.
  • A Geometries panel — step through the distinct geometries, or overlay them with displacement arrows.
  • λ comparison across History results in the Compare tab, one column per channel.

Reviewer notes

The overlay's first version was unreadable. Two solid ball-and-stick models in equal weight, for structures that nearly coincide — which is most of them, since λ is usually a small relaxation. Rebuilt so the hierarchy matches the question: thin grey reference, thin coloured relaxed geometry, and arrows carrying the signal. An arrow has direction and length, which is what a displacement is; two overlapping solids have neither.

The arrow-scale control (×1–×10) scales arrows only, never structures — atoms always sit at computed positions and the legend states the factor, so nothing displayed is fictional. No Kabsch alignment either: both geometries came from optimizations seeded identically, so superimposing would rotate away part of what λ measures.

The Analysis tab was empty for these runsreorganization_energy had no _PANEL_REGISTRY entry at all. Now Energies → Geometries → Isosurface, satisfying both constraints: Energies precedes Isosurface (it loads the orbital state Isosurface checks) and Geometries is the first auto_select.

A miss the tests couldn't catch. The accordion was built, registered, and never added to the Analysis VBox's children — so it could never render. Every unit test passed because each checked one layer; only a screenshot caught it. The new test is therefore structural: it walks every _PANEL_META entry and asserts the accordion is a child of the tab, so the next panel is covered without anyone remembering to. That was the third "registered but not wired" gap in this session, so the guard belongs at the seam.

Still open

The internal optimization trajectories are still discarded, so the Trajectory panel doesn't light up for these runs — small now that the geometries are retained. XYZ and PNG export moved to a new milestone.

37 reorg tests. Full suite: 2153 passed, 23 skipped.

Generated with Claude Code

NCCU-Schultz-Lab and others added 3 commits August 5, 2026 14:48
Student report: reorg results reloaded from History came back without the
numbers the calculation exists to produce.

It presented as a display bug and was not one. save_result wrote a fixed
schema of scalar fields with no `channels` key, so the λ breakdown was
never persisted. The live card rendered from an object still in memory;
the History card had nothing behind it. Repairing the formatter could not
have worked, and would have been the obvious first thing to try.

── The structural fix ──────────────────────────────────────────────────

Both cards now render from the SAME plain-dict payload. The live path
converts its result objects into the shape the saved file holds, so
"renders live" implies "renders after reload" by construction rather than
by two implementations happening to agree. That drift is the bug, so
removing the possibility of drift is the fix.

λ is a first-class schema field, not an entry in the generic `extras`
bag: it is the headline number of this calculation type, and burying it
would leave it undiscoverable and unqueryable.

── Geometries, retained and used ───────────────────────────────────────

The ion-optimized geometry was computed and thrown away — it existed only
as a local. Without it the four energies cannot be interpreted after the
fact: λ measures how far the molecule relaxed on becoming an ion, and
there was nothing to compare R_neutral against.

It is now kept and persisted, which makes RMSD and largest-atom-shift a
few lines of numpy. Those are computed at DISPLAY time from the stored
coordinates rather than persisted as separate numbers that could drift
out of step with the geometry they describe.

The neutral geometry rides inside the channel payload rather than being
read from the top level, which stores atom_symbols but not coordinates —
so a history card had no reliable way to reach R_neutral at all.

Also corrected a misconception in the original request, with the user's
agreement: the Marcus scheme is four energies on TWO geometries per
channel (three across both channels), not four geometries. A four-way
comparison would show each geometry twice and imply all four differ.

── Old results ─────────────────────────────────────────────────────────

They cannot be recovered — λ is two optimizations and four SCF energies —
so the card says so and names the remedy instead of rendering an empty
section that reads like a rendering failure. Detected by ABSENCE of the
payload, not by version or timestamp: a result re-saved or imported from
elsewhere would defeat a version cutoff.

── Tests ───────────────────────────────────────────────────────────────

15 new, exercising the real save → load → render path rather than either
formatter — inspecting a formatter is exactly what would have missed
this, since the live one was always correct. The load-bearing test
asserts both cards render the same channel content.

Remaining in M-REORG: the geometry stepper, the overlay view, and wiring
the calc type into the Analysis tab (it has no _PANEL_REGISTRY entry at
all, so that tab is currently blank for these runs). The data foundation
they need is what this commit lands.

Full suite: 2131 passed, 23 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Builds on the persistence work: with the geometries retained, the four
energies can finally be inspected rather than only summarised.

── The distinct geometries, not four ───────────────────────────────────

The Marcus scheme evaluates four energies on TWO geometries per channel —
E_ion(R_neutral) shares its geometry with E_neutral(R_neutral), likewise
for R_ion — so a hole+electron run has three distinct structures, not
six and not the four the name suggests. The stepper shows each once and
labels it with which energies were evaluated on it, so the picture stays
connected to λ. Not animated: λ is a comparison between states, not a
trajectory through them, and looping would imply a path never computed.

Built on _frame_stepper_controls rather than a second stepper, so camera
handling, offline loading and control styling match the trajectory viewer.

── The overlay, rebuilt after seeing it ────────────────────────────────

First version drew two solid ball-and-stick models in equal weight. For
structures that nearly coincide — which is most of them, since λ is
usually a small relaxation — that is an unreadable blob. Reported
immediately, and correctly.

The hierarchy now matches the question: reference is a thin grey
wireframe (context), the relaxed geometry a thin coloured one, and
ARROWS from each atom's reference position to its relaxed one carry the
signal. An arrow has direction and length, which is what a displacement
is; two overlapping solids have neither. Atoms that did not move get no
arrow — a zero-length arrow is a dot and reads as noise.

Added an arrow-scale control (x1/x3/x5/x10) because λ relaxations are
often hundredths of an Angstrom: real, invisible at 1:1. It scales the
ARROWS ONLY, never the structures, so every atom stays at its computed
position and the legend states the factor. Nothing displayed is
fictional. No Kabsch alignment either — both geometries came from
optimizations seeded identically, so the displacement is physical and
superimposing would rotate away part of what λ measures.

── The Analysis tab existed but was empty ──────────────────────────────

reorganization_energy had NO _PANEL_REGISTRY entry, so that tab populated
nothing for these runs. Now registered as Energies -> Geometries ->
Isosurface, which satisfies both constraints: Energies precedes
Isosurface (it loads the orbital state Isosurface checks) and Geometries
is the first auto_select so it opens by default.

── A miss my own tests could not catch ─────────────────────────────────

The accordion was built, registered in _PANEL_META, and never added to
the Analysis VBox's children — so it could never render. Every unit test
passed because each checked one layer; nothing checked the layers were
connected. Only a screenshot caught it.

The new test is therefore structural rather than about this panel: it
walks every _PANEL_META entry and asserts the accordion is a child of the
tab. The next panel added is covered without anyone remembering to. That
is the third "registered but not wired" gap this session, so the guard
belongs at the seam rather than on the instance.

Full suite: 2147 passed, 23 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
── λ comparison ────────────────────────────────────────────────────────

Selecting several reorganization-energy results in the Compare tab now
yields a λ table beneath the general one, with a column per channel.
This is the workflow the calculation type exists for — screening
candidates by how much they reorganize — and a single λ is hard to judge
without others beside it.

A SEPARATE table rather than extra columns on the general comparison: λ
is per-channel, so folding it into a one-row-per-result grid would mean
either duplicating rows or inventing a combined number with no physical
meaning.

Results predating λ persistence are listed with "re-run to compare"
rather than skipped — silently omitting them would look like they were
never selected. The table also states that lower λ is favourable for
charge transport, since a bare number invites the wrong reading and this
is one of the few quantities where the direction is unambiguous.

── Sticky background ───────────────────────────────────────────────────

The geometry viewers were missing from rerender_3d_scenes_for_theme.
Same bake-in as every other py3Dmol viewer — bgcolor is painted into the
scene at render time — and cheap to redraw here, since the geometries are
a few dozen coordinates already in memory.

── A test of yours caught me ───────────────────────────────────────────

test_retired_border_greys_are_gone_from_in_app_chrome, written during
THEME.5, flagged that the new comparison table used #cbd5e1 and #e2e8f0.
Those are exactly the greys that vanish under dark mode's whole-page
inversion. Now theme.BORDER. Without that test this would have shipped as
"the λ table has no visible rules in dark mode" and surfaced weeks later.

Full suite: 2153 passed, 23 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@NCCU-Schultz-Lab
NCCU-Schultz-Lab merged commit a7329df into main Aug 5, 2026
5 checks passed
@NCCU-Schultz-Lab
NCCU-Schultz-Lab deleted the reorg-outputs branch August 5, 2026 21:22
NCCU-Schultz-Lab added a commit that referenced this pull request Aug 5, 2026
The first real H200 allocation came back 7/7 green with a clean CPU->GPU
crossover, so the pairing that worked is now written down where the next
build can compare against it: driver 580.126.20, compute capability 9.0,
partition gpu, cuda12x wheels. Notably CuPy reports driver API 13000
against runtime 12090 — exactly the backward-compatible combination that
made cuda12x the right call over cuda13x, which would have hard-failed on
the 570-series driver the hardware notes originally listed.

The diagnostic notebook now reports the CPU AFFINITY MASK, not just the
core count. Slurm can grant 6 cores two ways: a cgroup quota (the mask
still shows all 192, so OpenMP spawns 192 threads and thrashes them) or a
cpuset (the mask shrinks and an unset OMP_NUM_THREADS is harmless). Same
request, same cpu_count, opposite consequences — so the number the
notebook was printing could not distinguish them. It now states plainly
when CPU timings cannot be trusted, and gives the one-line fix.

On this run the mask was 6, so the measured crossover stands. It is
recorded with its CPU allocation, because a speedup without its
denominator is the first thing an audience challenges — and the node has
~12 cores per GPU, so 6 is less than a proportional share.

Version 0.6.1: this release is the reorganization-energy work merged in
#44 plus these docs. Patch rather than minor — nothing here changes an
API or removes a capability.

Full suite: 2153 passed, 23 skipped.

Co-Authored-By: Claude Opus 5 <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