Band structure and density of states

For periodic systems vibe-qc can sample a real-space Fock matrix on a user-specified k-path (band structure) or on a Monkhorst–Pack mesh (density of states), and the matching plotters in vibeqc.plot give you publication-style figures with one call.

What you need

A real-space lattice Fock matrix F_real and overlap S_real (vibeqc.LatticeMatrixSet). For non-interacting bands (Hcore = T + V) helper functions build these for you, so the minimal recipe is two lines:

import vibeqc as vq

system = vq.PeriodicSystem(
    1,                                                      # 1D
    [[6.0, 0, 0], [0, 30, 0], [0, 0, 30]],                  # lattice (bohr)
    [vq.Atom(1, [0, 0, 0]), vq.Atom(1, [1.4, 0, 0])],       # H2 per cell
)
basis = vq.BasisSet(system.unit_cell_molecule(), "sto-3g")

kpath = vq.kpath_from_segments(
    system,
    [((0.0, 0, 0), "Γ", (0.5, 0, 0), "X")],
    points_per_segment=40,
)
bands = vq.band_structure_hcore(system, basis, kpath, n_electrons_per_cell=2)
dos   = vq.density_of_states_hcore(system, basis, [80, 1, 1],
                                   sigma=0.02, n_electrons_per_cell=2)

bands.energies is a (n_kpoints, n_bands) numpy array; dos.dos is a 1D array on the auto-chosen energy grid dos.energies. Both objects remember their Fermi level for the convenience of plotters.

Plotting

vibeqc.plot is a thin matplotlib layer (lazy import — vibe-qc itself doesn’t depend on matplotlib):

from vibeqc.plot import bands_dos_figure

fig = bands_dos_figure(bands, dos, title="H2 chain (STO-3G)")
fig.savefig("h-chain.png", dpi=150)

You can also call band_structure_figure(bands) and dos_figure(dos) on their own. All plotters accept units="eV" (default) or "Hartree", and shift_to_fermi=True (default) puts E_F at zero.

Interacting bands from a converged SCF

band_structure_hcore only knows about T + V. For a self-consistent Fock matrix you’d build F_real = T + V + J(D) ½K(D) + V_xc(D) yourself (vibe-qc doesn’t yet persist the converged real-space Fock on PeriodicRHFResult/PeriodicKSResult) and pass it to band_structure(F_real, S_real, kpath, …). Saving the converged Fock is on the roadmap.

What’s exposed

vq.kpath_from_segments(system, segments, points_per_segment=30)
vq.band_structure(F_real, S_real, kpath, *, n_electrons_per_cell=None)
vq.band_structure_hcore(system, basis, kpath, *, n_electrons_per_cell=None)
vq.density_of_states(F_real, S_real, kmesh, *, sigma=0.01,
                     energy_grid=None, n_grid=401, pad=5.0,
                     n_electrons_per_cell=None)
vq.density_of_states_hcore(system, basis, mesh, *, sigma=0.01,
                           n_electrons_per_cell=None, )

vq.BandStructure       # dataclass: kpath, energies, e_fermi, …
vq.DensityOfStates     # dataclass: energies, dos, sigma, e_fermi
vq.KPath               # dataclass: kpoints_cart/frac, distances, labels

vq.plot.band_structure_figure(bs, )
vq.plot.dos_figure(dos, )
vq.plot.bands_dos_figure(bs, dos, )

A complete worked example is in examples/input-h-chain-bands.py.

Validation

tests/test_bands.py checks that:

  • the DOS integrates to n_bands (each Gaussian has unit area, weights sum to one),

  • band_structure returns sorted eigenvalues at every k,

  • the k-path tracks correct cumulative distances and that segments joined at shared endpoints don’t double-count points,

  • e_fermi is set only for closed-shell electron counts (open-shell band-structure interpretation requires α/β channels and is deferred).