Skip to content

[test] Add spin-orbit + spin-polarized Wien2k converter test (CaOs2) - #4

Merged
the-hampel merged 7 commits into
TRIQS:unstablefrom
krystophny:port/dmftproj-soc-harness
Oct 9, 2026
Merged

the-hampel merged 7 commits into
TRIQS:unstablefrom
krystophny:port/dmftproj-soc-harness

Conversation

@krystophny

@krystophny krystophny commented Jun 15, 2026 •

Copy link
Copy Markdown

Part of #7 (dmftproj Fortran-to-Python port).

Adds a spin-orbit + spin-polarized Wien2k converter test for CaOs2, with two equivalent correlated Os d shells and sixteen magnetic symmetry operations, eight containing time reversal. The fixture retains one k-point and covers the combined-spin block and the two-shell subspace. Local time-reversal rotation matrices are trivial in this fixture; the eight antiunitary operations are covered through their flags and symmetry matrices.

The native Fortran producer uses the original retained Wien2k projector inputs with a widened -2.0 to 3.0 Ry window, selecting bands 43–92 (50 bands) for twenty correlated spin orbitals. The test checks the stacked projector overlap against the identity independently of the reference archive, then compares conversion of the same fixture bytes at an explicit 1e-12 tolerance. The regenerated stacked overlap error is about 3.1e-8.

test/python/wien2k/CaOs2.README.md records the retained producer inputs and native regeneration commands. Compressed almblmup/almblmdn, indmftpr, dmftsym and the retained cubic-basis template are included. The original Wien2k version, initialization commands and parent k-mesh were not retained; the documented reproduction starts from those projector inputs.

Merged the current PR target unstable (43435dc); upstream has no branch named main. The PR base remains unstable.

Verification: fresh native CMake build with TRIQS 4.0.2, all 21 registered CTest tests passed. The new invariant rejects the original 17-band fixture. One Pi review and one Claude Opus review were performed against the frozen candidate.

Chris&AI

…CaOs2)

dftkit's Wien2k converter is only covered by the non-SOC SrVO3 case. This adds a
SOC + spin-polarized golden test: CaOs2, a cubic fluorite cell with two
symmetry-equivalent correlated Os atoms whose magnetic point group has 16
operations, 8 of them time-reversal. It exercises the combined-spin 'ud' block
and the time-reversal symmetry path of dmftproj + the converter on a small
8-k-point fixture, validated by h5diff against a reference produced by this
converter.

@the-hampel the-hampel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed as the first step of the #7 stack (#4 → #5 → #8 → #9 → #10 → #11 → #12 → #15 → #16 → #13/#19). Thanks for laying the harness down first — locking convert_dft_input before touching fortran/dmftproj is the right order, and the SOC/spin-polarized gap in the test suite is real.

Verification done locally

Rebased port/dmftproj-soc-harness onto current origin/unstable (20e6373) — conflict-free, delta unchanged at +1497 over 9 files. Configured and built in a fresh build directory and ran the full suite:

20/20 tests passed
 2/20 Test #2: Py_wien2k_soc_convert ... Passed  0.85 sec

(For anyone reproducing this: an existing build/ that has previously been configured on another branch can leave stray test_*.py in test/python/vasp/converter/, which run_suite.py then picks up and fails on. That is a stale-build-dir artifact, not this PR.)

Substantive: the fixture's projectors are not orthonormal

This is the one I'd like to resolve before the stack builds on it.

The fixture declares two correlated Os shells of dim = 10 each — 20 spin-orbitals — but the band window is bands 60–76, i.e. 17 bands, at a single k-point. 20 > 17, so the correlated subspace cannot fit in the window and dmftproj's Löwdin orthonormalization cannot succeed. Reading the shipped reference back:

per shell:            max |P P† − 1|          = 4.98e-01
stacked 20x17 block:  eig(P P†)               = {0 x5, 1 x15}   -> rank 15
                      trace(P P†)             = 15.000

For contrast, the same quantity on the existing SrVO3 reference is 2e-8 at every one of its 10 k-points, rank-full at each.

So five of the twenty correlated directions are exactly null. As a pure converter characterization test that is survivable — the converter is reshaping and I/O, and it will reshape a rank-deficient block as faithfully as a good one. But the PR sells this as the harness that proves the port "leaves the result unchanged", and #7 itself says the .ctqmcout orthonormalization "fixes the numerical contract for everything downstream". Pinning that contract on a singular overlap matrix is the worst place to pin it: eigenvector choice in the null space is arbitrary, so a Python Löwdin can legitimately differ from the Fortran one there for reasons that say nothing about correctness — and conversely a genuine regression in the well-conditioned part can hide behind it.

Concretely I'd suggest either widening the energy window in case.indmftpr until the window holds at least 20 bands and regenerating the fixture, or dropping to one correlated Os atom if the two-atom permutation symmetry is not what you're after. Ideally the test then also asserts orthonormality directly, so the harness fails loudly rather than silently locking a degenerate case:

P = ar['dft_input']['proj_mat'][ik, 0, icrsh, :dim, :n_orb]
assert np.abs(P @ P.conj().T - np.eye(dim)).max() < 1e-6

Also worth fixing

n_k = 1, not 8. The description says "a small (8 k-point) dmftproj output fixture". CaOs2.ctqmcout line 2 is 1, and the reference has n_k = 1, bz_weights = [1.0]. So no k-dependence is locked at all, which matters for a harness meant to catch a per-k projector regression. (The other half of the claim is accurate: 16 symmetry operations with time_inv = [0]*8 + [1]*8, and 48 rot_symmetries from .outputs.) Either fix the sentence or, better, ship a few k-points.

Reference provenance is undocumented. The fixture cannot be regenerated from what is in the tree: case.indmftpr and case.almblm* only arrive in #5 and later. Since this reference governs the whole port, a short header comment or README recording the Wien2k version, the init_lapw / x lapw1 -so / x lapwso / dmftproj invocations, the k-mesh and the window would pay for itself many times over.

h5diff tolerance is loose. h5diff(a, b) defaults to precision=1e-6. For a characterization test whose whole job is to show a reimplementation is a no-op — reading the same fixture bytes, so cross-platform drift is at 1e-15 — that is about six orders of magnitude of slack. Please pass an explicit tight tolerance.

CaOs2.oubwindn is dead. It is byte-identical to CaOs2.oubwinup (same blob), and for SP == 1 and SO == 1 convert_misc_input takes up and never looks at dn. Drop it, or make it non-identical if the intent is to cover the dn fallback.

Nits

  • wien2k_soc_convert.ref.h5 in the file(COPY …) list is redundant: test/python/CMakeLists.txt already GLOB_RECURSE-copies every *.h5 into the binary tree. The SrVO3 block right above correctly omits its own reference — worth matching.
  • The 2011 GPL boilerplate header on a new 2026 file is copied from wien2k_convert.py; recently added tests in this repo (e.g. test/python/vasp/converter/test_converter_svo_bands.py) carry none.
  • from h5 import * is unused in wien2k_soc_convert.py.
  • Fixtures are shipped uncompressed here while the later PRs in the stack gzip them; consistency would be nice, though 630 KB is not a problem on its own.

Note for the rest of the stack

The test only calls convert_dft_input. convert_parproj_input and convert_transport_input are never exercised, so dft_parproj_input and dft_symmpar_input stay unlocked for the SOC path — which is exactly what #10 and #11 port. Worth extending the harness here (it needs CaOs2.sympar / CaOs2.parproj fixtures) rather than leaving those two PRs to validate themselves.

None of the "also" or "nits" items block; the orthonormality question does, in the sense that fixing it later means regenerating the reference and rebasing everything above.


Attribution: this review was produced by Claude (Opus 5) running in Claude Code, driven by @the-hampel.

@the-hampel
the-hampel force-pushed the port/dmftproj-soc-harness branch from 830cb84 to 12dbb9b Compare September 10, 2026 09:16
@the-hampel

Copy link
Copy Markdown
Member

Heads-up: I force-pushed the rebase onto current unstable to this branch (830cb84 → 12dbb9b, base now 20e6373). It was a clean replay — the tree is identical, no conflicts — but git pull --rebase / re-fetch before you touch it locally.

Note this rebases only #4; #5 and everything above still carry the old 830cb84 as their base, so they'll each need the same treatment as we work up the stack.

Attribution: this note was produced by Claude (Opus 5) running in Claude Code, driven by @the-hampel.

@the-hampel

Copy link
Copy Markdown
Member

Hi @krystophny ,

First of all sorry for the delay working on this. I simply did not get to this until now. I reviewed the code work now with Claude and would be happy to work through all PR's if you are still available. Please let me know if you have any questions.

Best,
Alex

@krystophny

Copy link
Copy Markdown
Author

Hi @the-hampel , thanks for that! I will be back from holiday beginning of October, so will take a bit more time also. Then let's check the details. Best, Chris

@krystophny

Copy link
Copy Markdown
Author

Merged the current PR target unstable (43435dc; upstream has no main) and regenerated CaOs2 with the original cubic basis and a wider -2..3 Ry window. The 20 spin orbitals now fit in 50 bands; the stacked overlap error is 3.05e-8.

Added independent overlap, shell-permutation and symmetry-unitarity checks, tightened the golden comparison to 1e-12, shipped the native producer inputs/template, and corrected the description to one k-point. The README documents regeneration and the missing original Wien2k setup provenance; local time-reversal rotations remain trivial.

Fresh native CMake build: 21/21 CTest tests passed. The original 17-band fixture fails the new gate. Exactly one Pi and one Claude Opus review completed without blockers; their relevant findings are addressed.

Chris&AI

triqs.utility.h5diff forwards its precision argument to Green's
functions only; numpy arrays are compared with the 1e-6 default of
assert_arrays_are_close. h5diff(..., precision=1e-12) therefore checked
proj_mat and the symmetry matrices at 1e-6, and a 1e-10 change in
CaOs2.ctqmcout went undetected.

Compare the archive recursively with an explicit absolute tolerance
instead. A 1e-10 perturbation of one ctqmcout projector entry now fails
at /dft_input/proj_mat.
The SOC + spin-polarized path of convert_parproj_input and the sympar
symmetry reader had no reference, while the dmftproj port replaces the
Fortran producers of exactly these files (case.parproj, case.sympar).

CaOs2.parproj comes from the native dmftproj run documented in the
README; the same run reproduces the shipped ctqmcout to 2e-15 and
symqmc/oubwinup byte for byte. dmftproj writes CaOs2.sympar
byte-identical to CaOs2.symqmc here, because the partial shells are the
correlated shells, so CMake copies symqmc instead of shipping a duplicate.

The reference gains dft_parproj_input and dft_symmpar_input; all
existing datasets are unchanged bit for bit. Transport is not covered:
case.pmat needs Wien2k optic and cannot be produced from the retained
inputs.
@the-hampel

Copy link
Copy Markdown
Member

Thanks @krystophny, this addresses everything from the review. We approve the PR in its current form.

We pushed two small commits on top:

  • 8b6386d – strict golden comparison. triqs.utility.h5diff does not forward precision to numpy arrays (it calls assert_arrays_are_close(a, b), i.e. the 1e-6 default), so precision=1e-12 was not in effect: a 1e-10 change in CaOs2.ctqmcout passed. The test now compares the archive recursively at atol=1e-12 without h5diff. We will report the h5diff issue in TRIQS separately.
  • db2eda9 – convert_parproj_input coverage. Added CaOs2.parproj from the native dmftproj run in your README (the same run reproduces your ctqmcout to 2e-15 and symqmc/oubwinup byte for byte). CaOs2.sympar is byte-identical to .symqmc here, so CMake copies it instead of shipping a duplicate. The reference gains dft_parproj_input/dft_symmpar_input; all existing datasets are unchanged. Transport stays uncovered, since case.pmat needs Wien2k optic.

Fresh build, 21/21 tests pass locally. If you're happy with the two commits, we'll go ahead with a squash-merge. The PRs above #4 will need a rebase onto the new fixtures afterwards.

Attribution: this comment was produced by Claude (Opus 5.5) running in Claude Code, driven by @the-hampel.

@krystophny

Copy link
Copy Markdown
Author

@the-hampel I am happy after one final check and fix. Thank you!

@the-hampel
the-hampel merged commit c2f0576 into TRIQS:unstable Oct 9, 2026
4 checks passed
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.

2 participants