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.
Kind strings must come from the registered v1 set (§ 1.4 of
design_qvf_format.md) or thex_<vendor>.*namespace.Every member in
membershaspath,format("json"|"binary"), andsha256. Binary members additionally havedtype(a numpy dtype name) andshape(rank-N integer array).Structure has a JSON member
"structure"; periodic structures also havelattice_vectorsandpbc=[true,true,true].Volumes (
volume.density,volume.orbital,volume.spin,volume.elf,volume.difference,volume.generic) have binary"data"+ JSON"grid"members. The grid JSON carriesorigin,voxel_vectors,shape(bohr units per design § 1.3a; vibe-view converts to Å at its PyVista boundary).volume.differencemay additionally carryoperand_aandoperand_b(string section ids that must resolve, perdependentRequired).volume.genericis an escape hatch for fields that don’t fit the purpose-built kinds, producers should prefer a more specific kind when one applies.Spectra (
spectra.ir,spectra.raman,spectra.uvvis,spectra.ecd,spectra.vcd,spectra.nmr,spectra.generic) have a JSON"spectrum"member withfrequenciesandintensities.Vibrations have JSON
"metadata"(withatomsandfrequencies) + binary"displacements"(float64,[n_modes, n_atoms, 3]).Trajectory has JSON
"metadata"(withatomsandenergies)binary
"coords"(float64,[n_frames, n_atoms, 3], Å).
reaction.pathhas the same binary layout astrajectory; the metadata JSON additionally carrieswaypoints(each withframe_index,label,kind ∈ {reactant, transition_state, intermediate, product, point}, optionalenergy_eh) and an optionalreaction_coordinatearray.reaction.waypointscarries one JSON"waypoints"member plus a section-leveltrajectory_refstring naming thetrajectorysection it annotates (validator-checked).Bands has JSON
"kpath"(withfermikey) + binary"eigenvalues"(float64,[n_spin, n_kpoints, n_bands], eV).atom_propertiescarries one or more ofmulliken_charge,loewdin_charge,spin_population, eachfloat64 [n_atoms].citationscarries a binary"references"member (BibTeX bytes, UTF-8).bondscarries one JSON"bonds"member with{"pairs": [{"i", "j", "order"}, ...]}.scf_historycarries one JSON"iterations"member with{"iterations": [{"iter", "energy_eh", ...}, ...]}.structure.symmetrycarries one JSON"data"member (spglib output).Wavefunction (
wavefunction.gto) has JSON"basis"(withstructure_ref,pure,n_ao,shells) + JSON"mo_metadata"(withspin,orbital_kind, energies / occupations either at top level forrestrictedor underalpha/betaforunrestricted) + binary"mo_coefficients"(restricted) or"mo_coefficients_alpha"+"mo_coefficients_beta"(unrestricted), each row-majorfloat64of 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).Manifest root may carry
viewer_defaultswithauto_open(list of section ids), per-section render hints (isovalue, colormap, opacity, replication), andbookmarks(ordered list of{name, camera}using the VTK camera model).Unknown / vendor sections (Rule 2): consumers list them as “skipped, unsupported” and continue. They don’t crash the open.
Live / streaming checkpoints¶
A producer can rewrite a QVF while the job runs so a viewer can
hot-reload it (SCF convergence climbing, an optimization trajectory
growing, the geometry morphing to the relaxed structure). vibe-qc’s
runners do this on demand: pass checkpoint_qvf=<path> +
checkpoint_every=N to run_job / run_periodic_job. A consumer
watching that path reads three optional fields:
provenance.run_statusis"running"while the job is in flight, then"converged"(success) or"failed"(crash). Watch until it is no longer"running", then read the settled archive.provenance.checkpointis{"seq": int, "wall_time_s": float, "written_at": ISO-8601}(mid-SCF snapshots may also carry"scf_iteration"and"energy_eh").seqis monotonic: use it to tell a fresh snapshot from a stale one and to drop out-of-order reads without diffing bytes.per-section
partialistrueon a section still growing (e.g. an optimizationtrajectory),absent/falsewhen settled.
These live under the open provenance block and on sections, so they
validate against the current v1 manifest schema: a v1 consumer that
ignores them still opens the file correctly. Every checkpoint write is
atomic (temp file + os.replace), so a reader never observes a
half-written zip; a plain “reload on mtime change” watcher is safe.
Cadence is route-dependent. Per-iteration frames (checkpoint_every=N)
appear on the periodic routes that stream through the shared progress
logger (Ewald, GDF, BIPOLE, and GPW), which refresh every N SCF cycles.
Only the molecular single-point SCF runs its loop in compiled C++ with no
per-iteration Python hook, so molecular jobs emit a start + terminal
frame only. A consumer should therefore treat the frame count as
informative, not guaranteed, and rely on run_status for lifecycle.