Volumetric data: cube and XSF files¶
vibe-qc writes the two interchange formats every solid-state / molecular viewer reads:
Gaussian cube (
.cube) — molecular electron densities, molecular orbitals, and ESP. Read by VMD, Avogadro, PyMOL, ChimeraX.XCrySDen XSF / BXSF (
.xsf,.bxsf) — periodic crystal structures, periodic volumetric data, and band data on a 3D k-mesh for Fermi-surface plots. Read by VESTA and XCrySDen.
Molecular: cube files¶
import vibeqc as vq
mol = vq.Molecule.from_xyz("h2o.xyz")
basis = vq.BasisSet(mol, "6-31g*")
res = vq.run_rhf(mol, basis)
# Density: ρ(r) = Σ D_μν χ_μ χ_ν
vq.write_cube_density("rho.cube", res.density, basis, mol)
# A single MO (zero-based index)
homo = mol.n_electrons() // 2 - 1
vq.write_cube_mo("homo.cube", res.mo_coeffs, homo, basis, mol)
# Multiple MOs in one file (multi-volume cube; viewer flips between them)
vq.write_cube_mos("mos.cube", res.mo_coeffs, [homo - 1, homo, homo + 1, homo + 2],
basis, mol)
The grid is built automatically: an axis-aligned bounding box around
the molecule plus 4 bohr of padding, with 0.2 bohr cubic voxels (~10⁶
voxels for a small molecule). Override with spacing= and
padding=, or pass a fully custom :class:vibeqc.CubeGrid:
grid = vq.make_uniform_grid(mol, spacing=0.1, padding=6.0)
vq.write_cube_density("rho-fine.cube", res.density, basis, mol, grid=grid)
For UHF / UKS pass D = D_alpha + D_beta (the total density).
A typical sanity check: integrating ρ over the box should return the
electron count to ~1% on the default grid, exact in the limit.
Periodic: XSF and BXSF¶
# Just the structure — useful for visualising the unit cell in VESTA.
vq.write_xsf_structure("crystal.xsf", system)
# Periodic volumetric data — pass a 3D ndarray sampled over one cell.
# (vibe-qc doesn't yet have a built-in periodic-density evaluator; this
# is the entry point for when you produce one yourself or via a
# user-side helper.)
vq.write_xsf_volume("rho.xsf", system, data=rho_grid_3d, name="density")
# Fermi-surface data — band energies on a regular MP-style mesh.
# Shape (nkx, nky, nkz, nbands), energies in Hartree, e_fermi in Hartree.
vq.write_bxsf("bands.bxsf", system, energies, e_fermi=e_f)
XSF files use ångström for all positions and eV in the case of BXSF — vibe-qc does the conversion internally so you keep working in atomic units everywhere else.
Validation¶
tests/test_cube.py checks:
the writer header parses back as expected (atom count, voxel vectors, grid shape, multi-value flag),
ρ(r) integrates to the electron count on a fine grid,
single-MO files have ⟨φ|φ⟩ ≈ 1.
tests/test_xsf.py checks:
ångström conversion is correct for lattice and atom positions,
XSF DATAGRID traversal order (first index runs fastest, then j, then k) — required by VESTA/XCrySDen,
BXSF conventions: 1/ångström reciprocal lattice and eV energies.