Running a calculation as a QVF container

Note

QVF track, part 2 of 3. Read The QVF file format, end to end first for the section kinds; Adopting QVF in your own code follows this page. The whole section is indexed at QVF and vibe-view.

You will learn: how a .qvf archive can be the calculation rather than only its results: you build a pending container from a structure plus a job specification, run it, and get the same file back carrying the results, the full log, the citations and the system manifest, marked done. You will do it three ways: from Python, through the vq queue as a single-file payload, and straight from the vibe-viewer Job Manager.

This is the lifecycle companion to The QVF file format, end to end, which covers the section kinds themselves. Here the interest is the round trip.

Prerequisites:

  • vibeqc installed with a working SCF setup (any Fundamentals tutorial).

  • Optional: vq for the queue section, vibe-view for the GUI section.

Time to complete: about 10 minutes.

The repository includes two styles of runnable companion:

  • examples/input-qvf-container-roundtrip.py performs the molecular lifecycle end to end in one process and verifies the embedded log.

  • examples/qvf_containers/ separates construction, vibeqc run, and inspection so you can see the file change at each step. It includes both molecular and periodic jobs.

Why a container

A .qvf normally answers “what came out?”. A container also answers “what went in?” and “did it finish?”, so one file is the whole record:

section / field

carries

structure

the geometry (and lattice + pbc when periodic)

job.spec

the declarative request: method, basis, functional, charge, multiplicity, k-points, tasks

provenance.run_status

pending → running → converged / failed

run.record

the verbatim input, the full log, the .system manifest

citations

the BibTeX for the level of theory actually used

That matters for reproducibility (the input travels with the numbers), for transfer (one file each way instead of a directory), and for review (open the file and read the log that produced the plot).

1. Build a pending container

import vibeqc as vq

mol = vq.Molecule(
    [
        vq.Atom(8, [0.0, 0.0, 0.2217]),      # bohr
        vq.Atom(1, [0.0, 1.4309, -0.8867]),
        vq.Atom(1, [0.0, -1.4309, -0.8867]),
    ]
)

path = vq.write_pending_qvf(mol, "water_job", method="rhf", basis="sto-3g")
print(path)  # water_job.qvf

Nothing has run yet. The archive holds exactly two sections and says so:

import json, zipfile

manifest = json.loads(zipfile.ZipFile("water_job.qvf").read("manifest.json"))
print(manifest["provenance"]["run_status"])                     # pending
print(sorted({s["kind"] for s in manifest["sections"]}))        # ['job.spec', 'structure']

A consumer must not guess a status that is absent; QVF spec § 3.2 makes an omitted run_status mean “not stated”, which is why a pending container states it explicitly.

The request is declarative JSON data, not Python source. vibeqc run never executes a run.record.input member, so opening and viewing an archive is not one click away from running arbitrary embedded code.

2. Run it

$ vibeqc run water_job.qvf

The log streams to your terminal and to water_job.out exactly as a normal run, and the same archive is updated in place:

manifest = json.loads(zipfile.ZipFile("water_job.qvf").read("manifest.json"))
print(manifest["provenance"]["run_status"])              # converged
print(manifest["provenance"]["scf_energy"])              # {'value': -74.9630..., 'units': 'Eh'}
print(sorted({s["kind"] for s in manifest["sections"]}))
# ['atom_properties', 'bond_orders', 'citations', 'job.spec',
#  'run.record', 'scf_history', 'structure', 'wavefunction.gto']

The original job.spec is preserved byte-for-byte, so the settled archive still shows what was asked for alongside what came back. The embedded log is the complete .out; the .system manifest rides along as a run.record attachment.

Re-running a settled container is refused unless you pass --force, which appends a second, sequenced run.record rather than overwriting the first.

A calculation that fails settles too. A non-converged SCF makes the runner raise (it refuses to publish a failed energy as a result) and the container is finalized as failed with whatever log exists, so a consumer watching the job sees it end instead of a running snapshot frozen forever.

Inspect either the pending or settled state with the companion utility:

$ python examples/qvf_containers/inspect_job.py water_job.qvf

It validates the archive and prints its spec, status, section kinds, and sequenced run history. A terminal container counts as complete only when its status is converged or failed and its latest run.record contains both input and the full, possibly empty, log.

The same file is directly viewable without a display server:

$ vibe-viewer show water_job.qvf --info
$ vibe-viewer show water_job.qvf --plain
$ vibe-viewer tui water_job.qvf

Use vibe-viewer open water_job.qvf for the graphical section browser.

3. Try the periodic route

The periodic companion builds one helium atom in an eight-bohr cubic cell and requests the basis-free SCC-DFTB route at Gamma. It is intentionally small so this remains a container tutorial rather than a solid-state benchmark:

$ python examples/qvf_containers/build_periodic_he_job.py
$ vibeqc run examples/qvf_containers/runs/he-periodic-scc.qvf
$ python examples/qvf_containers/inspect_job.py \
    examples/qvf_containers/runs/he-periodic-scc.qvf

write_pending_qvf accepts a PeriodicSystem in the same way as a Molecule, with kpoints=[n1, n2, n3] in the declarative spec. Native constructors use bohr while QVF stores structure coordinates and lattice vectors in angstrom; the writer and reader perform that round trip.

4. Send it through the queue

vq treats a .qvf payload as a first-class job: it recognizes the suffix, resolves the pinned runtime to its installed CLI, and runs vibeqc run job.qvf. Only the container moves.

$ vq submit HOST water_job.qvf --program vibeqc-dev
$ vq fetch HOST JOBID --name water_job.qvf -o results/

fetch --name publishes that one file atomically and leaves the operational logs server-side. Because the update is in place, what you fetch is the same archive you submitted, settled.

The managed runtime’s full Git SHA is snapshotted automatically and checked again before dispatch, so routine submissions need no version or SHA flag. Use --expected-sha FULL_40_HEX_SHA only as an extra assertion for a historical reproduction. While the job runs:

$ vq status HOST JOBID --json
$ vq tail HOST JOBID --name water_job.out

The JSON status includes qvf_lifecycle; its done field uses the same terminal-status plus complete-run-record rule described above. A settled container is refused through vq too unless submission explicitly includes --qvf-force.

5. Submit one from vibe-viewer

Open any structure in vibe-viewer, click Submit to vq cluster, and leave Submit as QVF container on. The viewer writes the pending container from the structure on screen plus the method / basis fields, submits it as the whole payload, and tags the job qvf-container. When it finishes, the Job Manager’s open action loads the settled archive directly, including its Run Record and Job Spec panels.

../_images/11-qvf-container-submit.png

The submit dialog in container mode. Live checkpoints are greyed out because a container settles in place rather than emitting a separate checkpoint archive, and the .py input field is hidden: there is no script in this mode.

The switch is disabled when vibeqc is not installed next to vibe-view: writing a pending archive is a producer action, and vibe-viewer is a consumer that does not depend on the producer. In that case the viewer submits its generated Python script instead, exactly as before.

The panel itself is documented in vibe-viewer’s own user guide (vq Job Manager); see also vibe-viewer here.

What you built

../_images/12-qvf-container-settled.png

The settled container open in vibe-view. The lifecycle chip beside the filename reads converged, Run Info leads with the status, and the sidebar carries Job Spec and Run Record alongside the results, with the Job Spec panel showing the request the archive was run from.

One file now holds the request, the results, the log, the references and the system manifest, and states its own lifecycle.

Next in the track: Adopting QVF in your own code, the producer’s view from outside vibe-qc: the manifest schema, the section-kind vocabulary, the sha256 verify-before-use contract, the vendor namespace, and the conformance corpus to certify against.

Sideways from here: The QVF file format, end to end for the section kinds in depth, or vq: running a remote job for the queue in general.

Reproducible Hessian stencils

A molecular Hessian request uses tasks=["hessian"] and options={"hessian_step_bohr": 0.0025} to select the central displacement in bohr. The default remains 0.005. Zero, negative, nonfinite and nonnumeric values are refused before calculation. The same keyword is available in run_job(hessian=True, hessian_step_bohr=0.0025).

For a stencil comparison, submit two separate containers from the same accepted geometry and identical method, basis, grid, dispersion and SCF settings, changing only hessian_step_bohr. Retain both originals and results. Read the executed displacement from surface.step_bohr and surface.stencil in the result’s vibrations metadata. The embedded .system attachment records the same values in [hessian]. The original job.spec remains the requested input; it is not proof of successful execution. Check SCF and vibrational acceptance separately.

Molecular endpoint data and attempt identity

For a DLPNO-CCSD or DLPNO-CCSD(T) result, root energies.total is the correlated total in hartree and energies.method identifies its method. provenance.scf_energy remains the RHF or UHF reference energy; its method is provenance.method. Never substitute it for a missing correlated total. energies.components contains x_reference, x_ccsd_correlation and x_triples separately, plus correlation for the correlation including triples. Components overlap and must not be summed to recover the total. The by_method array restates the reference and correlated totals.

provenance.correlation_converged is independent of scf_converged. A failed local CC solve settles the container as failed, even if SCF converged. provenance.verdict retains both reference-stability channels, their reasons and eigenvalues, reference_usable and reference_contract. These electronic convergence records do not certify an optimized minimum or a scientifically accepted reaction. Preserve the explicit review of those criteria with the hashes of the archives reviewed.

provenance.correlation_settings records the executed method, orbital and auxiliary bases, frozen-core policy and spatial-orbital count, threshold preset, resolved thresholds, unsupported/inactive threshold names, triples mode and algorithm, dispersion, SCF settings and solver controls. Compare the common frozen-core policy across species; keep the actual frozen_core_count separately because it varies with composition. provenance.resolved_settings.scf.grid holds the resolved molecular grid controls; grid_level_requested retains the requested preset. These are observations of the options used by the driver, not reconstructed defaults. provenance.runtime copies the job-start package/native-core identity, Python version, libraries and platform. An unknown source SHA stays unknown; retain the release/package receipt that resolves it rather than guessing.

The current result is bound by provenance.result_structure_ref and provenance.result_run_ref to the generated structure and run sections. The input drawing may refer to a different structure. Consumers must use these result references when associating energies with geometry.

Each run.record.x_input_archive_sha256 hashes the exact submitted archive bytes before execution. A forced retry appends a new record hashing its immediate input; earlier records keep their original hashes. Retain the submitted archives for retrieval. attachment.input_manifest and attachment.result_manifest preserve exact manifest snapshots, with member hashes and a files index. Snapshot section IDs belong to their original archives; current root result references use the merged archive’s IDs. Snapshots do not replace the original input archive. New attempts clear stale root energy/convergence fields before publishing their current result. For explicitly referenced inputs, optional unrelated sections and drawing members remain byte-for-byte intact. Legacy unreferenced inputs retain one current structure; their original geometry and manifest are attached to the run record. Use an explicit structure_ref for a drawing workflow. Unsupported critical sections or extensions are refused before execution.

A reproducible staged procedure is:

  1. Save and hash the editable reaction QVF and each prepared input. Review charge, multiplicity, atom mapping and explicit hydrogen coordinates.

  2. Produce one pending QVF per stage using the accepted predecessor QVF’s referenced result structure. Keep the drawing, species identity and immutable parent archive hash. Put every numerical setting in job.spec.

  3. Submit the QVF through vq submit HOST stage.qvf --program PROGRAM, using an immutable runtime satisfying the selected release family. Fetch the settled archive with vq fetch HOST JOBID --name stage.qvf -o results/. Retain queue receipts and both input/output hashes.

  4. Validate the QVF, inspect its run and result references, and check the required scientific acceptance before submitting a descendant. Preserve failed attempts; never hide a script/XYZ fallback as QVF interchange.

  5. In the viewer, inspect optimization forces and trajectories, compare the same-surface Hessian stencils and select energies.total only after checking the correlated method, convergence and reference contract. Aggregate a reaction only after all required species and matching method definitions have passed the explicit review.

This is the interchange procedure and output contract. It does not assert that a particular release has completed a particular reaction campaign.