QVF writer — handover / status

Last updated: 2026-05-22 Branch: main Commit: (pending) Agent session: QVF writer — release-readiness pass

What this is

The vibe-qc producer half of the QVF (Quantum Visualisation Format) pipeline. Writes .qvf files from SCF results. The consumer half lives in vibe-view/ (see vibe-view/HANDOVER.md). Both halves share the design document at docs/design_qcv_format.md.

Status: COMPLETE — all 13 milestones delivered, release-ready.

Current progress

Milestone

Status

Tests

M1 — Core writer + all v1 section kinds

✅ done

11 per-kind unit

M2 — Validator + CLI

✅ done

10 validator + 1 CLI

M3 — Round-trip + E2E

✅ done

2 round-trip + 1 E2E

M4 — Guardrails

✅ done

4 guardrail

M5 — Runner integration (output_qvf=True)

✅ done

M6 — Plan integration (OutputRole.qvf)

✅ done

M7 — JSON Schema

✅ done

M8 — Consumer manifest alignment

✅ done

M9 — Grid helpers (P2)

✅ done

2 slow

M10 — Periodic runner integration (P3)

✅ done

M11 — viewer_defaults + volume.spin (P4)

✅ done

M12 — 5 remaining reserved kinds

✅ done

5 fast

M13 — Consumer reference reader (P2)

✅ done

Total

38 tests

Completed work

Files

File

Purpose

Lines

python/vibeqc/output/formats/qvf.py

Writer + validator + CLI

~1370

python/vibeqc/output/formats/qvf_manifest.schema.json

JSON Schema

312

tests/test_qvf_writer.py

Test suite

~1050

python/vibeqc/output/plan.py

OutputRole.qvf, OutputFormat.qvf

+15

python/vibeqc/runner.py

output_qvf kwarg, post-SCF emit

+56

python/vibeqc/output/__init__.py

Export write_qvf, validate_qvf

+2

python/vibeqc/output/formats/__init__.py

Re-export QVF symbols

+8

Section kinds implemented (v1)

Kind

Source data

Binary payloads

structure

molecule/system

atoms_Z.bin (int32), atoms_cart.bin/atoms_frac.bin (float64), lattice.bin (float64, periodic only), atoms_labels.json

volume.density

pre-computed 3D numpy

.dat (float32/64) + grid descriptor

volume.orbital

pre-computed 3D numpy

.dat (float32/64) + grid + orbital metadata

atom_properties

PopulationSummary

mulliken_charge.bin, loewdin_charge.bin (float64)

trajectory

list of Molecule frames

optimization.jsonl (JSONL)

vibrations

HessianResult

frequencies.json, displacements.bin (float64)

spectra.ir

HessianResult

ir.json

bands

BandStructure

eigenvalues.bin (float64), kpath.json

provenance

result + context

Embedded at manifest root (not in sections[])

citations

BibTeX string

references.bib

Producer rules enforced

  • Float32 default, float64 opt-in via volume_dtype="float64"

  • Deflate default, auto-detects zipfile-zstd if importable

  • manifest.json always ZIP_STORED

  • Every binary member carries sha256 hex digest in manifest

  • All kinds from the registry or x_<vendor>.* namespace

  • No unregistered kinds emitted

Guardrails

Guard

Level

Behavior

Bad plan type

write_qvf

TypeError

Volume > 1 Gvoxel

write_qvf

ValueError

Missing molecule/system

write_qvf

UserWarning

Empty .atoms

_write_structure_section

UserWarning, skip section

Zip bomb (>128 MiB/member)

validate_qvf

Validity error

Missing manifest.json

validate_qvf

Hard error

Unknown kind (non-vendor)

validate_qvf

Validity error

Vendor kind (x_*)

validate_qvf

⚠ warning, not validated

Reserved kind

validate_qvf

Noted, payloads skipped

sha256 mismatch

validate_qvf

Per-member error

Runner integration

result = run_job(mol, basis="sto-3g", method="rhf",
                 output="h2o", output_qvf=True)
# → h2o.qvf  (structure + atom_properties + citations)

The .qvf is recorded in the .system manifest via OutputWriter.record() so vq can fetch it alongside the .out / .molden / .xyz siblings.

Next steps (priority order)

None — all P0–P2 items are complete. The remaining reserved kinds (volume.potential, volume.difference, volume.generic, volume.orbital_projection, spectra.uvvis, spectra.ecd, spectra.vcd, spectra.nmr, spectra.generic, topology.qtaim, topology.elf_basins, projections.lcao, basis) all follow the same section-writer pattern (~50 lines each) when needed. The dispatcher adapter (P1 from previous revision) was deemed unnecessary — the aggregate-archive shape of QVF does not fit the per-file, per-role dispatcher model used by cube/molden/xyz writers.

Blockers

None.

Assumptions

  • Volumetric data is provided as pre-computed numpy arrays. The writer does not call grid evaluators internally — it’s the caller’s responsibility.

  • atoms_cart.bin is always present for molecular systems; atoms_frac.bin + lattice.bin for periodic. Both paths are disjoint — a section never has both cart and frac.

  • The structure section always has pbc and dimensionality fields for the consumer to decide on replication / unit-cell rendering.

Key decisions

  • Single-file module: qvf.py rather than a package. All section writers are private _write_<kind>_section() functions. If the file grows past ~2000 lines in v2, split into a package.

  • In-memory zip construction: all payloads are zf.writestr() rather than written to a temp dir first. Keeps the lifecycle simple.

  • Provence at manifest root, not in sections[]. Matches the design doc §7 informal example.

  • Band structure eigenvalues in eV, not Hartree. Matches the consumer expectation (per design doc §8.4).

  • Normal-mode displacements are mass-weighted eigenvectors, not Cartesian displacements. Consumer divides by sqrt(m_i) to animate.