QVF consumer reference: reading a .qvf file in Python

The manifest shape matches the vibe-view consumer (vibe-view/src/vibeview/qvf.py).

Important

Viewer support tracks writer support, with one rule. vibe-view renders every implemented writer kind it has a renderer for (see vibe-view/src/vibeview/kinds.py::SUPPORTED_KINDS). Unknown or vendor-namespace (x_<vendor>.*) sections, and any reserved kinds not yet wired into a renderer, are classified as “skipped, unsupported” by the viewer and do not prevent the archive from opening (§ 2.5 Rule 2 of design_qvf_format.md). The full writer / viewer matrix is in the content-kinds table of the design doc.

Use the manifest as the single source of truth for “what is in this archive”. The viewer’s status panel reports which of those sections are actually being rendered in the current session.

import zipfile, json, hashlib
import numpy as np

# ── Open ───────────────────────────────────────────────────────────────
path = "h2o.qvf"
zf = zipfile.ZipFile(path, "r")
manifest = json.loads(zf.read("manifest.json"))

print(f"QVF v{manifest['qvf_version']} from {manifest['source']['program']}")

# ── Verify sha256 of every member ──────────────────────────────────────
for section in manifest["sections"]:
    for _key, member in section.get("members", {}).items():
        sha = member.get("sha256")
        if sha is not None:
            got = hashlib.sha256(zf.read(member["path"])).hexdigest()
            assert got == sha, f"sha256 mismatch for {member['path']}"

# ── Structure → atoms ──────────────────────────────────────────────────
for s in manifest["sections"]:
    if s["kind"] == "structure":
        struct = json.loads(zf.read(s["members"]["structure"]["path"]))
        for a in struct["atoms"]:
            print(f"  {a['symbol']} at {a['position']}")
        print(f"  pbc={struct['pbc']}")
        if "lattice_vectors" in struct:
            print(f"  lattice={struct['lattice_vectors']}")

# ── Volume.density → numpy array ──────────────────────────────────────
for s in manifest["sections"]:
    if s["kind"] == "volume.density":
        dm = s["members"]["data"]
        raw = zf.read(dm["path"])
        grid_data = np.frombuffer(raw, dtype=np.float32).reshape(dm["shape"])
        # Grid descriptor is a JSON member
        g = json.loads(zf.read(s["members"]["grid"]["path"]))
        print(f"Density: {dm['shape']}, origin={g['origin']}")

# ── Vibrations ────────────────────────────────────────────────────────
for s in manifest["sections"]:
    if s["kind"] == "vibrations":
        meta = json.loads(zf.read(s["members"]["metadata"]["path"]))
        print(f"Frequencies: {len(meta['frequencies'])} modes")

zf.close()

Consumer contract (v1)

The canonical contract is the JSON Schema at python/vibeqc/output/formats/qvf_manifest.schema.json. Both the producer (vibe-qc) and the consumer (vibe-view) load the same file — the latter via a symlink — so this list is a human-readable summary, never a normative spec on its own.

  1. Kind strings must come from the registered v1 set (§ 1.4 of design_qvf_format.md) or the x_<vendor>.* namespace.

  2. Every member in members has path, format ("json" | "binary"), and sha256. Binary members additionally have dtype (a numpy dtype name) and shape (rank-N integer array).

  3. Structure has a JSON member "structure"; periodic structures also have lattice_vectors and pbc=[true,true,true].

  4. Volumes (volume.density, volume.orbital, volume.spin, volume.elf, volume.difference, volume.generic) have binary "data" + JSON "grid" members. The grid JSON carries origin, voxel_vectors, shape (bohr units per design § 1.3a; vibe-view converts to Å at its PyVista boundary). volume.difference may additionally carry operand_a and operand_b (string section ids that must resolve, per dependentRequired). volume.generic is an escape hatch for fields that don’t fit the purpose-built kinds — producers should prefer a more specific kind when one applies.

  5. Spectra (spectra.ir, spectra.raman, spectra.uvvis, spectra.ecd, spectra.vcd, spectra.nmr, spectra.generic) have a JSON "spectrum" member with frequencies and intensities.

  6. Vibrations have JSON "metadata" (with atoms and frequencies) + binary "displacements" (float64, [n_modes, n_atoms, 3]).

  7. Trajectory has JSON "metadata" (with atoms and energies)

    • binary "coords" (float64, [n_frames, n_atoms, 3], Å).

  8. reaction.path has the same binary layout as trajectory; the metadata JSON additionally carries waypoints (each with frame_index, label, kind {reactant, transition_state, intermediate, product, point}, optional energy_eh) and an optional reaction_coordinate array.

  9. reaction.waypoints carries one JSON "waypoints" member plus a section-level trajectory_ref string naming the trajectory section it annotates (validator-checked).

  10. Bands has JSON "kpath" (with fermi key) + binary "eigenvalues" (float64, [n_spin, n_kpoints, n_bands], eV).

  11. atom_properties carries one or more of mulliken_charge, loewdin_charge, spin_population, each float64 [n_atoms].

  12. citations carries a binary "references" member (BibTeX bytes, UTF-8).

  13. bonds carries one JSON "bonds" member with {"pairs": [{"i", "j", "order"}, ...]}.

  14. scf_history carries one JSON "iterations" member with {"iterations": [{"iter", "energy_eh", ...}, ...]}.

  15. structure.symmetry carries one JSON "data" member (spglib output).

  16. Wavefunction (wavefunction.gto) has JSON "basis" (with structure_ref, pure, n_ao, shells) + JSON "mo_metadata" (with spin, orbital_kind, energies / occupations either at top level for restricted or under alpha/beta for unrestricted) + binary "mo_coefficients" (restricted) or "mo_coefficients_alpha" + "mo_coefficients_beta" (unrestricted), each row-major float64 of shape [n_mo, n_ao]. Molecular and Gamma-point periodic in v1. Shell coefficients apply to normalized primitive Gaussians (see design doc Sec. 4.6 for the formula).

  17. Manifest root may carry viewer_defaults with auto_open (list of section ids), per-section render hints (isovalue, colormap, opacity, replication), and bookmarks (ordered list of {name, camera} using the VTK camera model).

  18. Unknown / vendor sections (Rule 2): consumers list them as “skipped, unsupported” and continue. They don’t crash the open.