Skip to content

Migration of fundamental system and electronic properties for normalization to results#170

Merged
ndaelman-hu merged 54 commits into
developfrom
test-data-normalization
Jul 9, 2026
Merged

Migration of fundamental system and electronic properties for normalization to results#170
ndaelman-hu merged 54 commits into
developfrom
test-data-normalization

Conversation

@JFRudzinski

@JFRudzinski JFRudzinski commented Apr 8, 2026

Copy link
Copy Markdown
Collaborator

Executive Summary

This PR migrates fundamental system quantities and a subset of electronic properties (DOS, band structures, band gaps) for several simulation parsers in the nomad-simulation-parsers package to use nomad-simulations, implementing mapping annotations and ensuring compatibility with the downstream results normalizer.

Main Objectives

  1. Schema Migration: Transition from manual archive.run population to declarative add_mapping_annotation for electronic output properties
  2. Electronic Properties Coverage: Ensure all parsers map electronic DOS, band structures, and band gaps to outputs.electronic_* fields
  3. Test Coverage: Add comprehensive gate tests for normalizer-required payloads across all parsers
  4. Code Quality: Fix Ruff violations (import ordering, line length, unused imports, etc.)

Parsers Modified (18 total)

Core Electronic Property Migration

  • VASP (xml_parser.py, outcar_parser.py, parser.py)
  • Quantum ESPRESSO (pwscf/parser.py, parser.py)
  • ABINIT (parser.py)
  • Exciting (parser.py, info_parser.py)
  • Crystal (parser.py)
  • FHI-aims (parser.py) -- separated into another branch due to conflicts with Add KMesh support for FHI-aims parser #171
  • GPAW (parser.py, gpw_parser.py)
  • Octopus (parser.py)
  • Wannier90 (parser.py)
  • Yambo (parser.py)
  • AMS (parser.py)
  • H5MD (parser.py) - minimal updates

Notes

(Attempted, not necessarily universal) Coverage:

  • ✅ Electronic DOS (density of states)
  • ✅ Electronic band structures with k-paths
  • ✅ Electronic band gaps (direct/indirect)
  • ✅ Fermi energy extraction
  • ✅ Reference energy (highest occupied/lowest unoccupied)

Parser Logic Enhancements

  • Added helper functions for electronic property extraction:
    • get_dos(), get_band_structure(), get_band_gap()
    • Fermi energy detection from eigenvalue files
    • Reference energy (VBM/CBM) extraction
  • Improved output-to-system linking
  • Added robust k-path parsing (especially for Exciting, Yambo)
  • Created general utilities in parsers/utils/general.py:
    • link_output_to_final_representative_system() - robust output linking

Coverage by Parser:

  • VASP: +80 lines (electronic properties validation)
  • Quantum ESPRESSO: +72 lines (DOS/band structure tests)
  • Wannier90: +128 lines (hopping matrix, band structure, DOS)
  • Octopus: +103 lines (outputs validation)
  • GPAW: +85 lines (electronic properties)
  • Exciting: +116 lines (DOS/band structure with k-path)
  • FHI-aims: +86 lines -- separated into another branch due to conflicts with Add KMesh support for FHI-aims parser #171
  • AMS: +126 lines (convergence targets, outputs)
  • Yambo: +40 lines (band structure/energy ref)
  • ABINIT: +101 lines (geometry optimization workflow)
  • Crystal: +99 lines (DOS/band structure conditionals)

Migration Compliance

Adherence to Migration Rules

No runschema dependencies in parsers or schema packages
Pure nomad-simulations objects in archive.data
Mapping annotations for population (not manual archive assignment)
Legacy parity maintained where tested

Parser-Specific Notes

  1. VASP: Most comprehensive migration, includes OUTCAR band structure extraction
  2. Quantum ESPRESSO: Added DOS and band structure mapping from PWSCF auxiliary files
  3. Wannier90: Full hopping matrix and Wigner-Seitz cell mapping
  4. Exciting: Added k-path parsing for band structure visualization
  5. Yambo: Band structure mapping with energy reference points
  6. ABINIT: Geometry optimization workflow integration
  7. AMS: Convergence targets for geometry optimization
  8. Crystal: DOS/band structure conditionals (some TODOs remain)

Breaking Changes

None - this is additive migration maintaining backward compatibility.

Testing Strategy

  1. Gate Tests: Each parser has tests verifying core sections exist and contain normalizer-required data
  2. Electronic Properties: Explicit validation that DOS/band structures/gaps are populated
  3. Regression Prevention: Existing tests maintained to ensure legacy behavior preserved
  4. **Full GUI Test (with normalizer module -- see test data HERE)

@JFRudzinski JFRudzinski force-pushed the test-data-normalization branch from 5d5cfe7 to f1076d7 Compare April 8, 2026 10:11
@coveralls

coveralls commented Apr 8, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 29024750646

Coverage decreased (-0.6%) to 75.887%

Details

  • Coverage decreased (-0.6%) from the base build.
  • Patch coverage: 611 uncovered changes across 26 files (1360 of 1971 lines covered, 69.0%).
  • 23 coverage regressions across 8 files.

Uncovered Changes

Top 10 Files by Coverage Impact Changed Covered %
src/nomad_simulation_parsers/parsers/quantumespresso/pwscf/parser.py 185 79 42.7%
src/nomad_simulation_parsers/parsers/vasp/xml_parser.py 196 126 64.29%
src/nomad_simulation_parsers/parsers/exciting/parser.py 116 47 40.52%
src/nomad_simulation_parsers/parsers/octopus/parser.py 60 21 35.0%
tests/parsers/test_exciting_parser.py 83 48 57.83%
src/nomad_simulation_parsers/parsers/ams/parser.py 89 59 66.29%
src/nomad_simulation_parsers/parsers/vasp/common.py 33 3 9.09%
tests/parsers/test_ams_parser.py 81 53 65.43%
tests/parsers/test_quantumespresso_parser.py 49 22 44.9%
tests/parsers/test_octopus_parser.py 65 41 63.08%
Total (40 files) 1971 1360 69.0%

Coverage Regressions

23 previously-covered lines in 8 files lost coverage.

File Lines Losing Coverage Coverage
src/nomad_simulation_parsers/parsers/ams/parser.py 9 64.93%
src/nomad_simulation_parsers/parsers/quantumespresso/parser.py 6 54.71%
tests/parsers/test_abinit_parser.py 3 87.83%
src/nomad_simulation_parsers/parsers/crystal/parser.py 1 74.55%
src/nomad_simulation_parsers/parsers/exciting/parser.py 1 71.91%
src/nomad_simulation_parsers/parsers/octopus/parser.py 1 58.9%
src/nomad_simulation_parsers/parsers/quantumespresso/pwscf/parser.py 1 60.93%
src/nomad_simulation_parsers/parsers/yambo/parser.py 1 39.81%

Coverage Stats

Coverage Status
Relevant Lines: 13507
Covered Lines: 10250
Line Coverage: 75.89%
Coverage Strength: 0.76 hits per line

💛 - Coveralls

@ndaelman-hu ndaelman-hu self-requested a review April 9, 2026 08:49
@ndaelman-hu ndaelman-hu marked this pull request as ready for review April 9, 2026 08:53
Comment thread src/nomad_simulation_parsers/parsers/vasp/xml_parser.py Outdated
Comment thread src/nomad_simulation_parsers/parsers/wannier90/parser.py Outdated
Comment thread src/nomad_simulation_parsers/parsers/wannier90/parser.py Outdated
Comment thread src/nomad_simulation_parsers/parsers/wannier90/parser.py Outdated
Comment thread src/nomad_simulation_parsers/parsers/wannier90/parser.py Outdated
Comment thread src/nomad_simulation_parsers/parsers/wannier90/parser.py Outdated
Comment thread src/nomad_simulation_parsers/parsers/yambo/parser.py Outdated
Comment thread src/nomad_simulation_parsers/parsers/yambo/parser.py Outdated
Comment thread src/nomad_simulation_parsers/parsers/yambo/parser.py Outdated
- Octopus: remove the dead `'._initial_system'` model_system annotation
  (immediately overwritten by the `get_systems` annotation on the same
  key), the writer-side `_initial_system` payload injection, and the
  unused `get_initial_system_for_mapping`; `get_systems` alone covers
  static calculations
- Octopus: move `append` out of the geometry-step `try` block and log
  unreadable step files once after the loop; same for the
  coordinate-file candidates
- Octopus: inline the single-caller `get_reference_energy` into
  `get_eigenvalues`
- Crystal: attach the label regex to `CrystalOutputParser` instead of
  module scope
- QE: pass `.atomic_structure.cell` as annotation argument to the XML
  `get_periodic_boundary_conditions` instead of resolving a `Path`
  inside the function
- QE: key the PWSCF DOS payload cache by mainfile via class attributes;
  the previous `hasattr` guard leaked the payload of a previously
  parsed mainfile into subsequent parses on the shared parser instance
- QE: annotate `get_reference_energy` return type
_dos_cache_key: str | None = None
_cached_dos_payload: dict[str, Any] | None = None

def get_dos(self, source: dict[str, Any]) -> list[dict[str, Any]]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Strictly, I would suggest to make a new mapping parser for dos. Or at the very least, we add the reading of the dos data in def to_dict. This way we do not have to worry about caching.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Agreed that a dedicated parser (or folding the read into to_dict) is the cleaner end state — the current mainfile-keyed cache is correct but is compensating for structure. I'd take it together with the DOSCAR-parser refactor (#192) rather than grow this PR further; happy to open a dedicated issue if you prefer.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Tracked in #190 (added a note there covering the dedicated DOS mapping parser / folding the read into to_dict to drop the mainfile-keyed cache).

- Move the XML->OUTCAR electronic-outputs backfill from `VASPParser`
  into `XMLArchiveWriter`, keeping the TODO(mapping-migration) marker
- Extract the XC functional mapping table and resolution into
  `parsers/vasp/common.py`, shared by the OUTCAR and XML parsers;
  drop the `__import__` indirection
- Wire OUTCAR `get_atoms` through a mapping annotation with
  `ions_per_type` and `species` as sources; it was previously dead
  code, so OUTCAR archives now gain `particle_states` (additive);
  handle numpy-array species entries when extracting labels
- Pass sources via annotation arguments to the XML `get_atoms`
  (`modeling.atominfo.array`) and `get_positions` (positions and
  lattice vectors); drop the internal `Path` lookups and the now
  redundant `get_lattice_vectors`
- Promote `as_list` to `parsers/utils/general.py` and reuse it in the
  XML parser
- Move single-use module constants into their functions; nest the
  single-caller Fermi-energy helper inside `get_total_dos`
- Remove the dead `get_ediff_unit` in both parsers
…parsers

- h5md: guarantee parser handles close on conversion errors via
  try/finally in `write_to_archive` (per-call closing would tear down
  the shared h5 handle mid-parse)
- Octopus: check file existence before `ase.read` in the coordinate
  and geometry-step loops instead of relying on the exception path
- Wannier90: fix the `energy_fermi` quantity regex in `WInParser`
  (`\n\r` never matches), superseding the manual re-read fallback and
  the `get_reference_energy` accessor; keep writer-provided aux data
  (`k_path`, reference energies) in private parser attributes instead
  of injecting synthetic keys into parsed `data`
- Wannier90: replace the `getattr` workflow guard with an explicit
  `None` check on the resolved DFT archive
- Yambo: pass `valence_conduction`/`valence`/`conduction` as
  annotation arguments to `get_band_gaps`; drop `hasattr` guards and
  annotate the nested reference-energy helper
- GPAW: use `self.data_object` (the initialized file-parser handle)
  throughout `GPWParser`; move single-use constants into
  `get_eigenvalues`; normalize ULM band-path payloads to plain dicts
  in `GPWFileParser._get_band_paths` so the transformer sheds its
  callable/dict guards

Copilot AI 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.

Pull request overview

Copilot reviewed 44 out of 44 changed files in this pull request and generated 4 comments.

Comment thread src/nomad_simulation_parsers/parsers/ams/parser.py
Comment thread src/nomad_simulation_parsers/parsers/ams/parser.py
Comment thread src/nomad_simulation_parsers/parsers/wannier90/parser.py Outdated
Comment thread src/nomad_simulation_parsers/parsers/yambo/parser.py Outdated
…er90 wiring

- VASP XML: remove the cross-calculation fallback (_resolve_electronic_source);
  get_eigenvalues, get_band_gaps, and get_total_dos now operate strictly on the
  calculation subtree passed via the mapping annotation. Electronic payloads
  land only on the output whose calculation carries them.
- Yambo: propagate highest_occupied onto each eigenvalues entry so the mapped
  band-structure sections receive the reference energy.
- Wannier90: only include k_path in the band-structure payload when the win
  file provides one, avoiding a None entry.
- Update VASP and Yambo tests to the per-calculation semantics.
@ndaelman-hu

Copy link
Copy Markdown
Collaborator

@ladinesa pls let me know if you're fine with the open threads and close them if so. Ready to merge when you want.

Integrates develop (Lobster parser #172/#187, VASP charge density #35,
FHI-aims SCF convergence criteria #177, nmr schema pin #179) with the
electronic-properties migration.

Conflict resolutions:
- schema_packages/vasp.py: union of develop's `ChargeDensity`/`VASPOutputs`
  and the migration's electronic-output classes (auto-merged, verified)
- schema_packages/fhiaims.py: kept #177 SCF-criteria alongside migration
- parsers/vasp/outcar_parser.py: kept both `CHGCARParser` and
  `get_xc_functionals` imports
- tests/parsers/test_vasp_parser.py: kept both import sets and both
  `test_chgcar` and the electronic-output regression tests
- pyproject.toml: kept nmr pin and the index-url change

Validated: full suite 196 passed / 9 skipped; snapshot additivity check
(pre-merge vs merged) shows no archive data loss.
The declarative `update_mode='append'` mapping on `VASPOutputs` silently
dropped `charge_density` on released nomad-lab (1.4.2): the CHGCAR section was
never appended, yielding 13 outputs instead of 14 and failing `test_chgcar`. It
only worked with the unreleased deterministic-inheriting-sections nomad-FAIR
fix, so the feature depended on an unreleased dependency.

Populate `charge_density` directly in `OutcarArchiveWriter.write_to_archive`
and drop the fragile CHGCAR mapping annotations. This is deterministic across
mapping-parser versions (verified on nomad-lab 1.4.2, 1.4.3rc1, and the local
dev build). A `TODO` tracks reverting to a declarative mapping once the parser
gains a deterministic append mode.
chgcar_parser = CHGCARParser()
chgcar_parser.filepath = self.mainfile
chgcar_parser.convert(archive_data_parser)
if (chgcar_file := chgcar_parser.load_file()) is not None:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is not the fix that is necessary. I am sure something changed in the schema that caused this to fail. Please investigate further.

# does not reliably create the section across mapping-parser versions,
# silently dropping charge density on released nomad-lab. This
# writer-side fallback is version-stable.
# TODO: revert to a declarative mapping once the mapping parser gains a

@ladinesa ladinesa Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The TODO note was for merge case, where the def is not updated but the fix is already in place in nomad-file-parser. append should work nonetheless.

The MPCDF GitLab registry was removed as index in ecbdc25 after CI hit
errors resolving from it, but that was a transient registry downage. Public
PyPI only carries stable nomad-lab (<=1.4.2), which lacks the mapping-parser
fixes that the CHGCAR `update_mode='append'` relies on, so `test_chgcar`
fails there. The MPCDF registry carries the nomad-lab pre-releases with those
fixes (1.4.3.dev/rc). Restoring it as the primary index lets the declarative
charge-density mapping work again, matching develop's resolution.

Verified: installing via the MPCDF index resolves nomad-lab 1.4.3rc1 and the
full VASP suite (incl. test_chgcar) passes with the declarative convert.

@ndaelman-hu ndaelman-hu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Extensively reviewed by @ladinesa and myself, using snapshots and copilot.

@ndaelman-hu

Copy link
Copy Markdown
Collaborator

@ladinesa Found it — not an annotation regression. The CHGCAR update_mode='append' needs the mapping-parser fix in the nomad-lab 1.4.3.dev/rc pre-releases (MPCDF registry); stable PyPI (<=1.4.2) lacks it. This branch had dropped the MPCDF index in ecbdc25 during a registry downage, so CI resolved 1.4.2 from PyPI and convert silently stopped appending. Unmodified develop fails the same way under 1.4.2.

Fix (b96c462): reattached MPCDF as the primary index; declarative convert unchanged. Verified test_chgcar passes with MPCDF-resolved 1.4.3rc1.

@ndaelman-hu ndaelman-hu merged commit 375dcd4 into develop Jul 9, 2026
3 checks passed
ndaelman-hu added a commit that referenced this pull request Jul 9, 2026
Catch up the branch to develop (#170 property-normalization migration) and
reapply the nomad_file_parser import swap on top. cli.py and chgcar_parser.py
remain on nomad.parsing.file_parser, matching the original migration scope.

Update the abinit geometry-optimization convergence test to identify targets
by threshold_type instead of section name: nomad_file_parser serializes the
polymorphic convergence targets as the base WorkflowConvergenceTarget, and the
EnergyConvergenceTarget/ForceConvergenceTarget subclasses add no quantities of
their own, so no data is lost.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

electronic parser enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants