Running vibe-qc — a tour¶
This walks through the main ways to use vibe-qc. Everything assumes you’ve finished installation.md and activated the virtualenv:
source .venv/bin/activate # bash/zsh
# or run commands as .venv/bin/python / .venv/bin/pytest
Any time you want to confirm which vibe-qc you’re running, print the banner:
from vibeqc import print_banner
print_banner()
╔════════════════════════════════════════════════════════════════════╗
║ vibe-qc 0.1.0 — Quantum chemistry for molecules and solids ║
║ © Michael F. Peintinger · MPL 2.0 ║
║ linked: libint 2.13.1 · libxc 7.0.0 · spglib 2.7.0 ║
╚════════════════════════════════════════════════════════════════════╝
The formatted SCF trace (vibeqc.format_scf_trace) also prepends this
banner by default, so a persisted log always carries its provenance.
0. The simple way: run_job¶
If you just want the classic QC-program experience — write an input
script, run it, read a text log — vibeqc.run_job dispatches everything
for you:
from vibeqc import Molecule, run_job
mol = Molecule.from_xyz("examples/h2o.xyz")
run_job(
mol,
basis="6-31g*",
method="rhf", # or "rks" / "uhf" / "uks" / "auto"
output="output-h2o-rhf", # file name stem
)
Produces:
output-h2o-rhf.out— banner, atom table, SCF iteration trace, energy components (for DFT), orbital-energy table with HOMO/LUMO markers, and the HOMO-LUMO gap.output-h2o-rhf.molden— molecular orbitals for any molden-aware viewer (Jmol, Avogadro, Molden, IQmol, MolView).
Geometry optimisation is one flag:
run_job(
mol, basis="6-31g*", method="rhf",
output="output-h2o-opt",
optimize=True, fmax=0.01, # eV/Å; uses ASE's BFGS under the hood
)
# Adds: output-h2o-opt.traj — per-step trajectory, animated via
# `ase gui output-h2o-opt.traj`.
Four runnable example inputs ship under examples/:
input-h2o-rhf.py, input-h2o-dft.py, input-h2o-opt.py,
input-oh-radical.py. Copy any of them as a template for your own
systems.
The remainder of this tour covers the programmatic API directly —
useful when run_job isn’t enough and you want to wire things up by
hand.
1. Load a molecule¶
From an XYZ file:
from vibeqc import Molecule, from_xyz
mol = from_xyz("examples/h2o.xyz") # uses defaults charge=0, mult=1
# or:
mol = from_xyz("examples/h2o.xyz", charge=-1, multiplicity=1) # hydroxide
Programmatic construction (positions in bohr, not Å):
from vibeqc import Atom, Molecule
mol = Molecule([
Atom(8, [0.0, 0.0, 0.0]),
Atom(1, [0.0, 1.5, -1.16]),
Atom(1, [0.0, -1.5, -1.16]),
])
Or via ASE (positions in Å), which also handles CIF, PDB, POSCAR, Gaussian inputs, and more:
from ase.io import read
atoms = read("examples/h2o.xyz") # .xyz / .cif / .pdb / .poscar ...
# use atoms.calc = VibeQC(...) pattern below
2. Hartree-Fock energy¶
from vibeqc import BasisSet, run_rhf, RHFOptions
basis = BasisSet(mol, "6-31g*")
result = run_rhf(mol, basis)
print(f"E(RHF/6-31G*) = {result.energy:.6f} Ha")
print(f"converged in {result.n_iter} iterations")
Key options (RHFOptions):
Field |
Default |
Meaning |
|---|---|---|
|
100 |
hard limit on SCF cycles |
|
1e-8 |
|ΔE| threshold (Hartree) |
|
1e-6 |
‖F·D·S − S·D·F‖ threshold |
|
0.5 |
fraction of previous density to mix in (0 = off, required ~0.5 for some systems) |
|
True |
Pulay DIIS extrapolation |
|
2 |
DIIS begins at this iteration |
|
8 |
history length |
|
|
|
result: RHFResult fields include energy, e_electronic, n_iter,
converged, mo_energies (ascending), mo_coeffs (AO-to-MO matrix,
columns are MOs), density, fock, scf_trace.
Open-shell HF (UHF)¶
from vibeqc import run_uhf, UHFOptions
mol = Molecule([Atom(1, [0, 0, 0])], multiplicity=2) # H atom (doublet)
result = run_uhf(mol, BasisSet(mol, "sto-3g"))
print(f"E(UHF) = {result.energy:.6f} Ha")
print(f"<S^2> = {result.s_squared:.4f} (ideal {result.s_squared_ideal:.4f})")
UHFResult has separate α/β attributes: mo_energies_alpha,
mo_energies_beta, mo_coeffs_alpha, mo_coeffs_beta, density_alpha,
density_beta.
3. Density Functional Theory (DFT)¶
Closed-shell Kohn-Sham:
from vibeqc import run_rks, RKSOptions
basis = BasisSet(mol, "6-31g*")
opts = RKSOptions()
opts.functional = "B3LYP"
# opts.grid.n_radial = 99 # optional — bump quality
result = run_rks(mol, basis, opts)
print(f"E(B3LYP/6-31G*) = {result.energy:.6f} Ha")
print(f" E_core = {result.e_electronic - result.e_coulomb - result.e_hf_exchange - result.e_xc:.4f}")
print(f" E_J = {result.e_coulomb:.4f}")
print(f" α·E_K = {result.e_hf_exchange:.4f} (= 0 for pure DFT)")
print(f" E_xc = {result.e_xc:.4f}")
print(f" E_nuc = {result.e_nuclear:.4f}")
Supported functional names:
Name |
Family |
HF-exchange fraction |
libxc components |
|---|---|---|---|
|
LDA |
0 |
Slater + VWN5 |
|
LDA |
0 |
Slater exchange only |
|
GGA |
0 |
XC_GGA_X_PBE + XC_GGA_C_PBE |
|
GGA |
0 |
XC_GGA_X_B88 + XC_GGA_C_LYP |
|
hybrid GGA |
0.20 |
XC_HYB_GGA_XC_B3LYP |
Any libxc ID or comma-separated list of IDs works too, e.g.
"1,7" (Slater + VWN5) or "101,130" (PBE).
DFT grid quality¶
opts = RKSOptions()
opts.functional = "PBE"
opts.grid.n_radial = 75 # Treutler-Ahlrichs radial points per atom (default)
opts.grid.n_theta = 17 # Gauss-Legendre θ points per radial shell
opts.grid.n_phi = 36 # uniform φ points per radial shell
opts.grid.becke_k = 3 # Becke cell smoothing iterations
# Total points per atom ≈ n_radial · n_theta · n_phi = 46k by default.
4. ASE Calculator — the easy path¶
from ase.build import molecule
from ase.optimize import BFGS
from vibeqc.ase import VibeQC
# Hartree-Fock single point
atoms = molecule("H2O")
atoms.calc = VibeQC(basis="6-31g*")
print(f"E = {atoms.get_potential_energy():.4f} eV")
# Geometry optimization (HF forces work)
BFGS(atoms).run(fmax=0.01) # fmax in eV/Å
print(f"Optimised: r(O-H) = {atoms.get_distance(0, 1):.4f} Å")
# DFT single point
atoms.calc = VibeQC(basis="6-31g*", functional="B3LYP")
print(f"E(B3LYP) = {atoms.get_potential_energy():.4f} eV")
# Open-shell HF (radical): multiplicity=2 → routes to UHF automatically
from ase import Atoms
oh = Atoms(symbols=["O", "H"], positions=[(0, 0, 0), (0.97, 0, 0)])
oh.calc = VibeQC(basis="sto-3g", multiplicity=2)
print(f"E(OH) = {oh.get_potential_energy():.4f} eV")
Method routing matrix¶
|
multiplicity |
driver |
forces? |
|---|---|---|---|
|
1 |
RHF |
✅ |
|
≥ 2 |
UHF |
✅ |
any functional string |
1 |
RKS |
✅ |
any functional string |
≥ 2 |
UKS |
✅ |
Reading arbitrary structure files¶
from ase.io import read
from vibeqc.ase import VibeQC
atoms = read("my_structure.cif") # .cif, .xyz, .poscar, .pdb, Gaussian .com, …
atoms.calc = VibeQC(basis="def2-tzvp", functional="PBE")
energy = atoms.get_potential_energy()
5. Logging the SCF¶
vibe-qc uses the stdlib logging module; the library is silent until you
configure it:
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
# or per-logger:
logging.getLogger("vibeqc.scf").setLevel(logging.DEBUG) # per-iteration table
Loggers:
vibeqc— top-level namespace (nothing emits directly)vibeqc.scf— SCF iteration tables (DEBUG) + convergence summary (INFO)vibeqc.ase— method header when the ASE Calculator fires
Post-hoc log of a result you already have:
from vibeqc import log_scf_trace, format_scf_trace
log_scf_trace(result) # emits via logging
print(format_scf_trace(result)) # returns a multi-line string
6. Geometry optimization + visualisation¶
from ase.optimize import BFGS
from ase.visualize import view
atoms.calc = VibeQC(basis="6-31g*")
opt = BFGS(atoms, trajectory="opt.traj") # writes every step
opt.run(fmax=1e-3) # eV/Å
# Replay:
from ase.io import read
frames = read("opt.traj", index=":")
view(frames) # opens the ASE GUI
7. Custom basis sets¶
Drop a Gaussian .g94 file into basis_library/custom/ and re-run
./scripts/setup_basis_library.sh. See
basis_library/README.md
for the file format.
cp my-basis.g94 basis_library/custom/my-basis.g94
./scripts/setup_basis_library.sh
basis = BasisSet(mol, "my-basis") # libint picks it up by name
A custom file with the same name as a standard basis (e.g.
basis_library/custom/6-31g.g94) overrides the standard one — useful
for replacing a contracted basis with a more accurate variant without
touching the shipped library.
8. Accessing the full API¶
The top-level vibeqc module exposes everything you’re likely to need:
import vibeqc
dir(vibeqc)
# Atom, Molecule, BasisSet, RHFOptions, RHFResult, UHFOptions, UHFResult,
# RKSOptions, RKSResult, Functional, Grid, GridOptions, SCFIteration,
# from_xyz, run_rhf, run_uhf, run_rks, compute_overlap, compute_kinetic,
# compute_nuclear, compute_eri, compute_gradient, compute_gradient_uhf,
# build_grid, evaluate_ao, evaluate_ao_with_gradient, log_scf_trace,
# format_scf_trace, hello, libint_version, ...
Everything returning matrices returns NumPy arrays (zero-copy views of Eigen data where possible).
9. Running the tests¶
.venv/bin/python -m pytest tests/ -v
By category:
pytest tests/test_rhf.py # HF energies vs PySCF
pytest tests/test_uhf.py # UHF vs PySCF
pytest tests/test_rks.py # DFT vs PySCF
pytest tests/test_gradient.py # analytic gradients vs PySCF
pytest tests/test_ase.py # ASE calculator + geometry opt