QVF format specification (v1)¶
Status: Normative. This document specifies the QVF v1 container format and its producer/consumer contract. The machine-readable contract is the JSON Schema
qvf_manifest.schema.json(shipped alongside this file); where prose and schema disagree, the schema wins. This is a self-contained copy of the specification maintained by the vibe-qc project, distributed under Apache-2.0 so that any code can implement a conforming producer or consumer.
QVF (.qvf, “Quantum Visualization Format”) is a ZIP-based container for
quantum-chemistry visualization and analysis data. It keeps one calculation’s
structure, scalar fields, spectra, bands, trajectories, wavefunctions,
provenance, and viewer hints in a single random-access file, instead of
scattering them across Cube, XYZ, Molden, XSF, log, and program-specific
sidecar files.
The format is intended for the whole quantum-chemistry ecosystem. A QVF producer may be any quantum-chemistry code. A QVF consumer may be a structure viewer, a band plotter, a spectrum tool, a validator, a notebook script, or a GPU viewer.
QVF v1 is deliberately not a restart format. It does not encode integral
caches, SCF internal state, engine-internal grids, or every possible
wavefunction representation. The optional wavefunction.gto section is a
visualization-oriented, Molden-like molecular-orbital carrier, not a universal
restart record.
The keywords MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, and MAY are used as in RFC 2119.
1. Design goals¶
One shareable artifact. A calculation’s visualization data travels as one
.qvffile.Random access. A consumer reads only the members it needs.
Typed payloads. Every member declares format, dtype, shape, and a sha256 checksum where applicable.
Stable common vocabulary. Common data types use canonical section kinds.
Partial support. A consumer can support a subset of kinds and still open the file. Unsupported vendor sections are reported, not misinterpreted.
Producer neutrality.
source.programcan name any code; core kinds are not producer-specific.
2. Archive model¶
A QVF file is a ZIP archive. The only mandatory member is manifest.json at
the archive root. Every other member is named from the manifest — member paths
are part of the manifest contract, not hard-coded by the container.
A typical archive:
example.qvf
├── manifest.json (REQUIRED — the only member read by name)
├── structure/
│ └── structure.json
├── wavefunction/
│ ├── basis.json
│ ├── mo_metadata.json
│ └── mo_coefficients.dat
├── volumes/
│ ├── density.dat
│ └── density_grid.json
├── spectra/
│ └── ir.json
└── citations/
└── references.bib
Path layout is not semantic. Consumers MUST follow the path fields in
manifest.json and MUST NOT assume ZIP entry order or directory names.
Producers SHOULD use stable, readable directory names for debuggability.
2.1 Byte-level container requirements¶
The archive MUST be a valid ZIP (PKZIP APPNOTE, deflate/store methods).
manifest.jsonMUST be present at the archive root (path exactlymanifest.json, no leading directory).Producers SHOULD store
manifest.jsonuncompressed (STORE method) so a consumer can read the table of contents cheaply; other members SHOULD be DEFLATE-compressed. Neither is mandatory — a consumer decompresses via the ZIP method recorded in the central directory regardless.Producers SHOULD write
manifest.jsonlast (or ensure its recorded checksums match the already-written member bytes). Because each member’s sha256 is embedded in the manifest, all member bytes must be finalized before the manifest is serialized.Member paths MUST use forward slashes, MUST NOT begin with
/, and MUST NOT contain any..path segment. (Schema:ZipPath.)A consumer SHOULD enforce a per-member uncompressed-size cap to guard against zip-bomb inputs. The vibe-qc reference tools cap at 8 GiB (1 gigavoxel of float64) per member.
2.2 Endianness¶
All binary members are stored in little-endian byte order. QVF v1 dtype names (§ 4.2) do not carry an explicit byte-order mark; little-endian is assumed. Producers on big-endian platforms MUST byte-swap to little-endian before writing. (An explicit byte-order tag is a candidate for a future minor revision; see § 9.)
3. The manifest¶
3.1 Root object¶
{
"qvf_version": 1,
"schema_uri": "https://vibe-qc.org/spec/qvf/1/manifest.schema.json",
"source": {
"program": "my-code",
"version": "1.2.3",
"calculation": "h2o_rhf_sto3g"
},
"sections": [ /* … */ ]
}
Root field |
Required |
Meaning |
|---|---|---|
|
yes |
Integer format version. v1 = |
|
yes |
|
|
yes |
Array of section objects (§ 4). |
|
no |
Canonical URI of the JSON Schema. Informational. |
|
no |
Calculation-level metadata (§ 3.2). |
|
no |
Viewer hints: camera bookmarks, per-section render defaults, |
|
no |
Root thermochemistry block (§ 3.3). |
|
no |
Root dipole block (§ 3.3). |
|
no |
Root geometry-constraint block (§ 3.3). |
|
no |
Extension governance block (§ 7). |
qvf_version MUST be 1. A consumer that only understands v1 MUST
refuse to open a file whose qvf_version is greater than 1.
3.2 provenance¶
Optional, open (additionalProperties: true). Recognized fields include
method, functional, basis (strings); charge, multiplicity,
n_electrons, n_scf_iterations (integers); scf_converged (bool);
scf_energy, fermi_energy (each a {value, units} object); wall_seconds
(number); hostname (string); dimensionality (0–3). A producer emits only
what it knows.
3.3 Root metadata blocks¶
These describe the calculation as a whole (not binary payloads), so they live at the manifest root rather than as sections.
thermochemistry—zpve_eh,enthalpy_eh,entropy_cal_mol_k,gibbs_free_energy_eh(Hartree / cal·mol⁻¹·K⁻¹),temperature_k,pressure_atm. All optional.dipole_moment—total_debye,vector_debye(3-vector, Debye),origin("center_of_mass","center_of_nuclear_charge", or"origin").constraints—frozen_atoms(0-based indices),distance_constraints(each{atoms: [i, j], target_angstrom}).
4. Sections and members¶
4.1 Section object¶
{
"id": "vol_dens_0",
"kind": "volume.density",
"label": "Electron density",
"members": { /* role -> member spec */ }
}
Field |
Required |
Meaning |
|---|---|---|
|
yes |
Unique within the archive. |
|
yes |
Canonical kind (§ 5) or |
|
yes |
Map from member role to a JSON or binary member spec. |
|
no |
Human-readable display label. |
|
no |
Component hint (e.g. for |
|
no |
If |
|
no |
URI of a JSON Schema for a vendor section’s members. |
Some kinds add kind-specific peer fields on the section object (e.g.
volume.difference carries operand_a/operand_b; reaction.waypoints
carries trajectory_ref). These are documented per kind in § 5.
4.2 Member specs¶
JSON member:
{ "path": "structure/structure.json", "format": "json", "sha256": "<64 hex>" }
Binary member:
{
"path": "volumes/density.dat",
"format": "binary",
"dtype": "float32",
"shape": [80, 80, 80],
"sha256": "<64 hex>"
}
formatis"json"or"binary".sha256is the lowercase hex sha256 of the exact bytes stored for that member (the uncompressed payload). Consumers MUST verify it before use.JSON members MUST parse as UTF-8 JSON.
Binary members are raw, C-contiguous, little-endian arrays.
dtypeis one of:int8,int16,int32,int64,uint8,uint16,uint32,uint64,float32,float64. The stored byte length MUST equalitemsize(dtype) × ∏(shape).citations.referencesis the one binary member with nodtype/shape(it is opaque UTF-8 BibTeX bytes).
4.3 Units and numeric conventions¶
Producers convert to these units at write time. Consumers MUST treat on-disk units as the contract (they may convert for display).
Field |
Unit / convention |
|---|---|
|
Ångström |
|
Ångström, row vectors |
|
bohr |
|
bohr per grid step |
|
e / bohr³ (electron density) |
|
orbital amplitude on a bohr grid |
|
hartree / e (atomic units of potential) |
|
dimensionless, s(r) = |∇ρ| / (2(3π²)^⅓ ρ^⅔) |
|
Ångström |
|
Hartree |
|
eV |
|
cm⁻¹ / km·mol⁻¹ |
|
eV |
|
cm⁻¹ |
|
bohr⁻² |
|
Hartree |
|
eV (referenced to E_F at 0.0) |
|
eV (signed: E(k) − E_F) |
|
cm⁻¹ |
|
ų / eV |
|
GPa |
When adding fields, prefer unit-bearing names (energy_eh, energy_ev,
frequency_cm) or a {value, units} object.
5. Canonical section kinds¶
Every kind below is a oneOf branch in the schema. A section’s kind selects
its required member set. The table lists each kind and its required members;
subsections give the payload shapes that matter for interoperability.
Kind |
Required members (roles) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
≥1 of |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
any mix of valid JSON/binary members |
5.1 structure and bonds¶
structure.json:
{
"atoms": [
{"symbol": "O", "position": [0.0, 0.0, 0.1173], "atomic_number": 8},
{"symbol": "H", "position": [0.0, 0.7572, -0.4692], "atomic_number": 1}
],
"pbc": [false, false, false],
"lattice_vectors": null
}
For periodic systems, lattice_vectors is a [3,3] array of row vectors in
Å and pbc marks periodic axes; the section object MAY also carry
dimensionality (0–3). Explicit connectivity is a separate bonds section
({"pairs": [{"i": 0, "j": 1, "order": 1.0}, …]}). If absent, a viewer may infer
bonds from covalent radii.
The structure section object MAY carry optional biomolecule metadata as
peer fields (chains, residues, secondary_structure) for cartoon rendering;
consumers that don’t understand them ignore them.
5.2 volume.*¶
All volume kinds share the same members: a JSON grid and a 3-D data binary
(float32 default, float64 opt-in). Grid JSON:
{
"origin": [-8.0, -8.0, -8.0],
"voxel_vectors": [[0.2, 0, 0], [0, 0.2, 0], [0, 0, 0.2]],
"shape": [80, 80, 80]
}
voxel_vectors are three per-voxel step vectors in bohr. For data index
(i, j, k) the point position is origin + i·v_i + j·v_j + k·v_k. This is
point-centered data: the rendered grid has exactly shape points.
Non-orthogonal grids use non-zero off-diagonal components.
volume.difference may add operand_a and operand_b (section ids); if one is
present both MUST be. Sign convention: data = operand_a − operand_b.
5.3 wavefunction.gto¶
Carries an atom-centered Gaussian basis and MO coefficient matrices for molecular wavefunctions and the Γ-point (k = 0) crystalline orbitals of periodic systems. See Appendix A for the AO-ordering and primitive-normalization contract — this is the single most error-prone part of implementing a producer, and getting it wrong silently corrupts every rendered orbital.
{
"kind": "wavefunction.gto",
"members": {
"basis": {"path": "wavefunction/basis.json", "format": "json", "sha256": "…"},
"mo_metadata": {"path": "wavefunction/mo_metadata.json", "format": "json", "sha256": "…"},
"mo_coefficients": {
"path": "wavefunction/mo_coefficients.dat", "format": "binary",
"dtype": "float64", "shape": [41, 41], "sha256": "…"
}
}
}
mo_coefficients shape is [n_mo, n_ao] (rows are MOs). Unrestricted
wavefunctions replace it with mo_coefficients_alpha + mo_coefficients_beta.
basis.json conventions: structure_ref names the structure section;
shells[].center is a 0-based atom index; l is the shell angular momentum;
exponents are in bohr⁻²; pure: true = spherical harmonics, false =
Cartesian. mo_metadata carries spin
("restricted"/"unrestricted"), orbital_kind
("canonical"/"natural"/"localized"), energies (Hartree), occupations
(electron count per MO), and optionally k_point (fractional; absent or
[0,0,0] = Γ).
5.4 Spectra¶
spectra.ir/raman/uvvis/ecd/vcd/generic each carry one JSON spectrum member
of the form {"frequencies": [...], "intensities": [...]}; units depend on kind
(§ 4.3). spectra.nmr is different — its spectrum is an object with
isotope, reference, solvent, chemical_shifts (each
{atom_index, symbol, isotropic_shift_ppm}), shielding_tensors, and
j_couplings. The NMR payload is intentionally loose in v1.
spectra.epr follows the same object-shaped pattern for electron paramagnetic
resonance. Its spectrum is an object carrying the g_tensor (with matrix /
isotropic / principal), a hyperfine list (per-nucleus a_iso_mhz /
a_tensor_mhz), and a zero_field_splitting (d_mhz / e_mhz). g-values are
dimensionless; hyperfine and zero-field-splitting parameters are in MHz. Like
NMR, the payload shape is intentionally loose in v1.
5.5 Trajectories, reactions, vibrations¶
trajectory: JSON metadata (atom identities + optional energies) + coords
float64 [n_frames, n_atoms, 3] in Å. reaction.path uses the same layout and
adds waypoint records ({frame_index, label, kind, energy_eh?}, kind ∈
reactant/transition_state/intermediate/product/point). reaction.waypoints
carries only annotations and references an existing trajectory section via the
section-level trajectory_ref. vibrations: JSON metadata (frequencies in
cm⁻¹) + displacements float64 [n_modes, n_atoms, 3].
5.6 Bands, DOS, and periodic kinds¶
bands: JSON kpath + eigenvalues float64 [n_spin, n_k, n_bands] in eV;
optional projections [n_k, n_bands, n_channels] for fat bands (with a
channels array in kpath). dos.total: energies + dos. dos.projected:
energies + projections [n_channels, n_points] (+ meta.channels).
fermi_surface: JSON mesh + energies [nk1, nk2, nk3, n_bands].
phonon_bands: JSON qpath + frequencies [n_q, n_modes] (cm⁻¹).
phonon_dos: meta + frequencies + dos. equation_of_state: volumes +
energies + JSON fit (model ∈ birch_murnaghan/murnaghan/vinet, V0, E0,
B0, B0_prime, …).
5.7 Analysis kinds¶
atom_properties: one or more of mulliken_charge, loewdin_charge,
spin_population, each float64 [n_atoms]. bond_orders: JSON
{method, pairs: [{i, j, order, distance_ang?, symbol_i?, symbol_j?}]}.
topology.qtaim: JSON {points: [{type, position, rho, laplacian, …}], bond_paths?: […]} with type ∈ bcp/rcp/ccp/ncp. structure.symmetry: JSON
data (spglib-style summary). scf_history: JSON
{iterations: [{iter, energy_eh, delta_e, diis_error}]}. citations: one
binary references member of UTF-8 BibTeX bytes. basis.ao: a per-AO grid +
ao_metadata (quantum numbers + provenance).
6. Validation semantics¶
A conforming validator checks:
ZIP readability and a per-member uncompressed-size cap.
manifest.jsonexists and parses; conforms to the JSON Schema.Section
idvalues are unique.Every declared member
pathexists in the archive.Every declared member
sha256matches the stored bytes.Every JSON member parses as UTF-8 JSON.
Every binary member’s byte length equals
itemsize × ∏(shape).volume.difference.operand_a/operand_bresolve when present.reaction.waypoints.trajectory_refresolves to atrajectorysection.Extension governance (§ 7): a
critical: truesection whose kind is neither canonical nor a declared vendor namespace is an error.
7. Extension and versioning model¶
7.1 qvf_version¶
Integer. v1 = 1. A value > 1 signals breaking changes; a v1-only consumer
MUST refuse such files. Minor, backward-compatible growth (new optional
kinds, new optional members) does not bump the integer — capability is
detected via supported-kind checking and the extensions block.
7.2 Canonical kinds¶
Canonical kinds appear in the schema and § 5. They may grow in minor revisions; no canonical kind may be removed or have its required members changed without a major version bump.
7.3 Vendor namespace x_<vendor>.*¶
Any kind matching x_<vendor>.<specific> is a vendor extension. <vendor>
SHOULD be a DNS-like identifier (x_orca, x_pyscf, x_vasp). Vendor
members must be valid JSON/binary member specs. A consumer MUST list vendor
sections as present-but-unsupported unless the extension is critical: true.
Unknown non-vendor kinds are not valid QVF.
7.4 Root extensions block and per-section critical¶
The manifest root may declare vendor extensions:
{ "extensions": { "x_orca": { "version": "1.0", "schema_uri": "…", "critical": false } } }
Each section may carry critical (default false). If critical: true and the
consumer does not support the kind, the consumer MUST refuse to open the
file rather than silently drop the section. Use critical: true only when the
section changes correct interpretation of the rest of the file (e.g. an ECP that
alters the effective electron count).
7.5 Promoting a vendor kind to canonical¶
The kind is used in production by ≥ 2 independent producers for ≥ 1 minor cycle.
A spec PR adds it to § 5, the schema
oneOfbranches, and the validator.Its member contract is stable across the two producers.
At least one reference consumer renders it.
8. Conformance¶
8.1 Producer conformance¶
A conforming producer MUST:
write a valid ZIP with
manifest.jsonat the root;set
qvf_versionto1;include
source.program,source.version,source.calculation;use unique section ids matching
^[A-Za-z0-9_.-]+$;use canonical kinds or
x_<vendor>.*;include only member paths that exist in the ZIP, with no
..segments;compute sha256 over the exact stored bytes of each member;
write numeric fields in the units of § 4.3;
write binary payloads matching the declared dtype and shape, little-endian;
ensure JSON members parse as UTF-8 JSON;
declare each
x_<vendor>namespace in rootextensionswhen any section in that namespace iscritical: true;set
critical: trueonly when consumer support is truly required.
A conforming producer SHOULD include provenance when known; include
citations when results depend on citable methods/bases/functionals; prefer a
specific volume.* kind over volume.generic; use a vendor namespace for
experimental payloads; and run a validator on its own output in tests.
8.2 Consumer conformance¶
A conforming consumer MUST:
read
manifest.jsonby name, not by ZIP entry order;sanity-check
qvf_version,source, andsections;verify sha256 before using a member’s bytes;
interpret only kinds it supports;
report unsupported vendor sections without corrupting or misinterpreting them;
respect declared dtype, shape, and units;
honor the per-section
criticalflag and the rootextensionscriticalflags, refusing to open when an unsupported extension is critical.
A consumer MAY require specific kinds for a given operation (a band plotter
may refuse a file without bands; a structure viewer may open it and ignore all
scalar fields).
9. Known open items (informative)¶
These are acknowledged limitations of v1, not defects:
Endianness tag. dtype names do not carry byte order; v1 assumes little-endian. A future minor revision may add an explicit prefix.
k-resolved Bloch wavefunctions.
wavefunction.gtocovers molecular systems and the Γ-point only. A canonical cross-consumer k-resolved section is future work; producers may usex_<vendor>.*meanwhile.NMR schema tightening.
spectra.nmrpayload shape is intentionally loose in v1.Fermi-surface meshes. v1 assumes a Γ-centered uniform Monkhorst-Pack grid.
10. Governance and stewardship¶
QVF is an open standard. It was created by and is stewarded by the
vibe-qc project — that origin is explicit — but the format
is producer-neutral by design (§ 1) and its specification and reference
implementations are Apache-2.0, so any code may implement a conforming producer
or consumer, including a proprietary one, with no obligation back to vibe-qc.
vibe-qc’s own non-standard data lives under the x_vibeqc.* vendor namespace,
exactly like any other adopter’s.
The governance model, versioning policy, change process, and the
vendor→canonical promotion process are described in GOVERNANCE.md; the
machine-readable list of canonical kinds and registered vendor namespaces is in
registry.json (kept in lock-step with the schema). Stewardship is intended to
broaden as independent producers adopt the format.
Appendix A. AO ordering and primitive normalization (REQUIRED for wavefunction.gto)¶
A wavefunction.gto section is only correct if the MO coefficients, the AO
ordering, and the primitive normalization all agree with this appendix. This is
the part most producers get subtly wrong.
A.1 Contraction and the normalization convention¶
Each contracted basis function is reconstructed as
χ(r) = Σ_i c_i · φ_i(r)
where each primitive φ_i is unit-normalized:
φ_i(r) = N_i · (x−A_x)^{l_x} (y−A_y)^{l_y} (z−A_z)^{l_z} · exp(−α_i r²)
N_i = (2 α_i / π)^{3/4} · (4 α_i)^{l/2} / √((2l − 1)!!), l = l_x + l_y + l_z
The coefficients c_i written into basis.json MUST apply to these
unit-normalized primitives.
The trap. Integral libraries such as libint and libcint (used by PySCF, and by many other codes including patterns ORCA developers will recognize from GBW-style storage) store contraction coefficients already pre-multiplied by
N_i— i.e. the stored coefficient isc_i · N_i, so it multiplies an un-normalized primitive. If your engine stores coefficients that way, you MUST divide each stored coefficient byN_ibefore writing it intobasis.json. Writing the engine-native (pre-scaled) coefficients produces orbitals that look plausible but are quantitatively wrong, especially for high-land tightly contracted shells.
Reference implementation of N_i (Python; the C++ toolkit has the identical
routine as qvf::primitive_norm):
import math
def primitive_norm(alpha: float, l: int) -> float:
radial = (2.0 * alpha / math.pi) ** 0.75
angular = (4.0 * alpha) ** (l / 2.0)
df = 1.0
for k in range(1, 2 * l, 2): # (2l-1)!!
df *= k
return radial * angular / math.sqrt(df)
Both reference writers expose a flag: pass your coefficients as-is if they are
already on unit-normalized primitives, or ask the writer to divide by N_i if
they are libint/libcint-style. When in doubt, verify against a known orbital: the
HOMO of H₂O/STO-3G evaluated on a grid should integrate to ~1 electron per spin.
A.2 AO ordering within a shell¶
AO order is part of the contract and MUST match the pure flag:
Spherical (
pure: true): functions are orderedm = −l, −l+1, …, +l.Cartesian (
pure: false): libint lexicographic ordering of(l_x, l_y, l_z)withl_x + l_y + l_z = l, decreasingl_xfirst, thenl_y.
The row/column index of an AO in mo_coefficients is its position in the
concatenation of all shells (shell order = shells array order), each shell
contributing 2l+1 (spherical) or (l+1)(l+2)/2 (Cartesian) functions.
If your engine uses a different in-shell order (common differences: p as
x,y,z vs m = −1,0,+1; d as xx,yy,zz,xy,xz,yz vs m = −2…+2), you
MUST permute the coefficient rows into QVF order before writing.