QVF writer library guide¶
This guide covers building, linking, and calling the two reference writers in
this toolkit: the C++17 library (qvf::QvfWriter) and the Python
writer (qvf_writer.QvfWriter). Both implement the same producer contract
described in the QVF specification (spec/qvf-format-spec.md); pick whichever
matches your host code.
Contents¶
Which writer do I use?¶
C++ library |
Python writer |
|
|---|---|---|
File |
|
|
Dependencies |
C++17 standard library only |
Python stdlib + NumPy |
Best for |
linking into a C/C++ code (ORCA, Turbomole, …) |
scripts, post-processing, prototyping |
Compression |
ZIP DEFLATE (per-member, STORE if smaller) |
ZIP DEFLATE |
Both produce byte-for-byte-identical binary payloads (raw little-endian IEEE arrays) and semantically identical manifests; they interoperate with any QVF consumer, including vibe-view.
The C++ library¶
Building¶
cmake -S cpp -B build # no find_package calls; zero external deps
cmake --build build
ctest --test-dir build # runs the known-answer + smoke tests
./build/write_h2o out.qvf # the worked example
Requires only CMake ≥ 3.16 and a C++17 compiler. There is nothing to install and no library to find — the ZIP writer, CRC-32, SHA-256, and JSON emitter are all self-contained in the toolkit.
Linking into your project¶
The library is small enough to vendor directly. Two options:
A. add_subdirectory. Drop the qvf-writer/cpp tree into your source and:
add_subdirectory(third_party/qvf-writer/cpp)
target_link_libraries(your_target PRIVATE qvf::qvf)
B. Add the sources directly. Compile cpp/src/qvf.cpp into your build and
add cpp/include and cpp/src to the include path. That is the entire library
— one translation unit plus header-only helpers.
Then:
#include "qvf/qvf.hpp"
C. Single-header drop-in. For the lowest-friction vendoring, grab the one
amalgamated file cpp/qvf_single.hpp (STB-style). In exactly one translation
unit define QVF_IMPLEMENTATION before including it; elsewhere just include it:
// in one .cpp:
#define QVF_IMPLEMENTATION
#include "qvf_single.hpp"
// anywhere else:
#include "qvf_single.hpp"
No CMake, no separate compile step, no include-path juggling — one file, C++17.
The header is generated from the cpp/ sources by scripts/amalgamate.py (a
drift test keeps the committed copy current); see cpp/examples/write_single_header.cpp.
D. find_package / FetchContent. The library installs a CMake package, so
after cmake --install (or a system package) adopters can:
find_package(qvf CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE qvf::qvf)
or pull it straight from source without an install step:
include(FetchContent)
FetchContent_Declare(qvf GIT_REPOSITORY <url> SOURCE_SUBDIR qvf-writer/cpp)
FetchContent_MakeAvailable(qvf)
target_link_libraries(your_target PRIVATE qvf::qvf)
The QvfWriter API¶
Every add_* method returns the section id it created (useful for
volume.difference operands and reaction.waypoints references). Construct a
writer, add sections and root metadata in any order, then write():
#include "qvf/qvf.hpp"
using namespace qvf;
QvfWriter w({"my-code", "1.2.3", "job_name"}); // Source{program, version, calc}
// structure (positions in Ångström)
std::string sid = w.add_structure({
{"O", 8, {{0.0, 0.0, 0.117}}},
{"H", 1, {{0.0, 0.757, -0.469}}},
{"H", 1, {{0.0, -0.757, -0.469}}},
});
// a spectrum
Spectrum ir; ir.frequencies = {1595, 3657, 3756}; ir.intensities = {67, 5, 42};
w.add_spectrum("spectra.ir", ir);
// provenance at the manifest root
Json prov = Json::object();
prov.set("method", "RHF").set("basis", "STO-3G").set("scf_converged", true);
w.set_provenance(prov);
w.write("result.qvf");
Method families (see the header for exact signatures):
Structure:
add_structure,add_bonds,add_bond_orders.Volumes:
add_volume(kind, grid, data, …)forvolume.density/orbital/spin/elf/generic/potential/rdg;add_volume_difference;add_basis_ao.Wavefunction:
add_wavefunction_gto(WavefunctionGTO)(restricted + unrestricted; see the normalization note below).Spectra:
add_spectrum(kind, Spectrum)for 1-D spectra, oradd_spectrum(kind, Json)forspectra.nmrand other object payloads.Bands / DOS:
add_bands,add_dos_total,add_dos_projected,add_dos_coop_cohp.Trajectories / reactions / scans / vibrations:
add_trajectory,add_reaction_path,add_reaction_waypoints,add_scan_surface,add_vibrations.Analysis / periodic:
add_atom_properties,add_scf_history,add_citations,add_structure_symmetry,add_fermi_surface,add_phonon_bands,add_phonon_dos,add_equation_of_state,add_topology_qtaim.Root metadata:
set_provenance,set_thermochemistry,set_dipole_moment,set_constraints,set_extensions,set_viewer_defaults.Escape hatch:
add_vendor_section("x_<vendor>.*", members, critical?).
The wavefunction normalization flag¶
WavefunctionGTO::coeffs_are_libint_normalized is the one flag you must get
right (see spec Appendix A). If your engine stores contraction coefficients
already multiplied by the primitive norm N_i (libint / libcint / GBW-style
storage), set it to true and the writer divides them out so they apply to
unit-normalized primitives, as QVF requires. If your coefficients already apply
to unit-normalized primitives, leave it false.
Building JSON payloads¶
qvf::Json is a tiny ordered JSON value used for flexible members (spectra.nmr
payloads, EOS fit, DOS metadata, viewer defaults, vendor sections):
Json nmr = Json::object();
Json shifts = Json::array();
Json cs = Json::object();
cs.set("atom_index", 1).set("symbol", "H").set("isotropic_shift_ppm", 4.6);
shifts.push_back(cs);
nmr.set("isotope", "1H").set("reference", "TMS").set("chemical_shifts", shifts);
w.add_spectrum("spectra.nmr", nmr);
Helpers: Json::from_doubles(vec), Json::from_ints(vec),
Json::from_strings(vec), Json::array(), Json::object(), .set(key, value),
.push_back(value).
Binary tensors¶
Binary members (grids, coordinates, eigenvalues) are qvf::Tensor objects built
by the tensor_* helpers, which serialize little-endian regardless of host:
Tensor rho = tensor_f32(values, {nx, ny, nz}); // float32 3-D grid
Tensor coords = tensor_f64(flat_xyz, {n_frames, n_atoms, 3});
tensor_f32, tensor_f64, tensor_i32, tensor_i64 cover the common cases;
shape must multiply to the number of supplied values.
The Python writer¶
Same shape, no build step:
import numpy as np
from qvf_writer import QvfWriter
w = QvfWriter(program="my-code", version="1.2.3", calculation="job")
w.add_structure([{"symbol": "O", "position": [0, 0, 0.117], "atomic_number": 8}])
w.add_density(np.abs(rho), origin=[-8, -8, -8],
voxel_vectors=[[0.2, 0, 0], [0, 0.2, 0], [0, 0, 0.2]])
w.add_wavefunction_gto(shells, mo_coefficients=C, energies=eps, occupations=occ,
coeffs_are_libint_normalized=True)
w.write("result.qvf")
The Python API mirrors the C++ one method-for-method; payloads are plain lists /
dicts / NumPy arrays. See python/examples/write_h2o.py.
C and Fortran — the C ABI¶
For codes that can’t call C++ directly (C, Fortran via ISO_C_BINDING, Rust,
Julia, …), the library ships a stable C ABI in
cpp/include/qvf/qvf_c.h. All state lives in one
opaque qvf_writer handle; functions return 0 on success and non-zero on
error (qvf_last_error() gives the message). Nested payloads (provenance,
NMR/EPR spectra, vendor sections) are passed as JSON strings the caller
builds, so no JSON parser crosses the boundary.
#include "qvf/qvf_c.h"
qvf_writer* w = qvf_create("my-code", "1.0", "h2o/rhf");
const char* sym[3] = {"O", "H", "H"};
int Z[3] = {8, 1, 1};
double pos[9] = {0,0,0.117, 0,0.757,-0.469, 0,-0.757,-0.469};
qvf_add_structure(w, sym, Z, pos, 3);
double freq[3] = {1595, 3657, 3756}, inten[3] = {67, 5, 42};
qvf_add_spectrum_xy(w, "spectra.ir", freq, inten, 3);
qvf_set_provenance_json(w, "{\"method\":\"RHF\",\"basis\":\"STO-3G\"}");
qvf_write(w, "h2o.qvf");
qvf_destroy(w);
cpp/examples/write_h2o_c.c is a complete runnable C example (structure,
wavefunction, spectra, EPR, provenance, citations). The C ABI is part of the
same qvf library target — link it exactly as from C++, but with the C++ driver
(set_target_properties(<tgt> PROPERTIES LINKER_LANGUAGE CXX) in CMake, or link
-lstdc++ / -lc++) so the C++ runtime is pulled in. Fortran callers
declare the same entry points with bind(C) interfaces and pass
c_char/c_double arrays plus null-terminated strings.
Validating your output¶
Always validate what you write:
python python/qvf_reader.py result.qvf # exits non-zero on any error
The validator checks the archive against the spec § 6 semantics (sha256
integrity, byte sizing, id uniqueness, reference resolution, extension
governance) and — when jsonschema is installed — the full JSON Schema. In your
own test suite, call qvf_reader.validate_qvf(path) and assert report["ok"].
Reading QVF in C++¶
The toolkit also ships a reference reader (qvf/qvf_reader.hpp) that
completes the round-trip — it opens a .qvf, parses manifest.json, verifies
each member’s sha256, and decodes JSON and binary members. Like the writer it is
zero-dependency: the ZIP reader, INFLATE decompressor, JSON parser, and SHA-256
are all in the toolkit, and it reads archives from any producer (it handles
stored, fixed-, and dynamic-Huffman DEFLATE, so PySCF/zlib/miniz output is fine).
#include "qvf/qvf_reader.hpp"
using namespace qvf;
QvfReader r = QvfReader::open("result.qvf");
std::vector<std::string> errors;
if (!r.validate(errors)) { /* report errors */ }
// Decode a JSON member (resolved through the manifest by section id + role):
Json st = r.read_json_member("structure", "structure");
std::string sym = st.at("atoms").at(0).at("symbol").as_string();
// Decode a binary member (little-endian, with dtype + shape):
BinaryMember mo = r.read_binary_member("wf", "mo_coefficients");
std::vector<double> coeffs = mo.as_doubles(); // mo.dtype == "float64"
QvfReader verifies sha256 on every read by default (pass verify=false to
skip). The parsed Json supports at(key) / at(index) / as_string() /
as_double() / as_int() / as_bool() / contains() / items() /
elements(). The reader is included in the single-header amalgamation too.
Section-kind cheat sheet¶
To publish… |
Call |
Kind |
|---|---|---|
Geometry |
|
|
Basis + MOs (GBW) |
|
|
IR / Raman / UV-Vis / ECD / VCD |
|
|
NMR shifts/tensors |
|
|
Mulliken / Löwdin charges |
|
|
Mayer / Wiberg bond orders |
|
|
Electron density grid |
|
|
Electrostatic potential |
|
|
Optimization / IRC frames |
|
|
Normal modes |
|
|
SCF convergence trace |
|
|
References |
|
|
Band structure |
|
|
DOS / PDOS |
|
|
Anything not yet standard |
|
|
For the ORCA-specific mapping (GBW → wavefunction, spectra parsing, EPR), see
orca_integration.md.