ASE integration¶
ASE (Atomic Simulation Environment) is the de-facto standard Python toolbox for atomic-scale simulation: geometry optimization, molecular dynamics, vibrational analysis, NEB, basin hopping, structure I/O, periodic-cell handling. ASE’s Calculator interface is calculator-agnostic — anything that can return energy + forces (and optionally stress / dipole / Hessian / polarizability / magmoms) plugs in directly.
vibeqc.ase.VibeQC is vibe-qc’s ASE Calculator. With it, every
ASE workflow that works for VASP / GPAW / ORCA / PySCF works for
vibe-qc by changing one line:
atoms.calc = VibeQC(basis="6-31g*", functional="PBE")
This page is the reference for what the calculator exposes, how to
build / read / constrain structures via ASE, how to bridge to
vqfetch and the vq queue, and how cross-validation against
external codes (PySCF / ORCA / CRYSTAL) works through the
subprocess-runner pattern (per
CLAUDE.md § 10;
external programs are run out-of-process, not imported as backends).
Page layout — feel free to jump:
Quickstart — minimal one-liner usage
Constraints — frozen atoms / planes
Workflow examples — opt / vibrations / NEB / MD / surface
ASE +
vqfetch— fetched-structure → SCFASE +
vqqueue — remote dispatch
Quickstart¶
from ase.build import molecule
from ase.optimize import BFGS
from vibeqc.ase import VibeQC
atoms = molecule("H2O")
atoms.calc = VibeQC(basis="6-31g*", functional="PBE")
BFGS(atoms).run(fmax=0.01) # ASE's BFGS, vibe-qc forces
print(atoms.get_potential_energy()) # eV
print(atoms.get_dipole_moment()) # eÅ — Phase A (v0.5)
Supported methods¶
The driver is selected automatically from the calculator’s
functional and the molecule’s multiplicity:
|
|
Driver |
|---|---|---|
|
1 |
RHF ( |
|
> 1 |
UHF ( |
set |
1 |
RKS ( |
set |
> 1 |
UKS ( |
You don’t pass a method= argument — the calculator infers it.
This keeps every RHF / UHF / RKS / UKS run on the same Molecule
consistent (the multiplicity is the single source of truth).
# Closed-shell HF
VibeQC(basis="6-31g*")
# Open-shell HF — multiplicity comes from atoms.info["multiplicity"]
# or atoms.get_initial_magnetic_moments(); set explicitly via the
# `multiplicity=` kwarg to be unambiguous.
VibeQC(basis="6-31g*", multiplicity=2)
# Closed-shell DFT
VibeQC(basis="def2-tzvp", functional="B3LYP")
# Open-shell DFT
VibeQC(basis="def2-tzvp", functional="B3LYP", multiplicity=3)
Property surface¶
As of v0.5, VibeQC implements the following ASE properties. The
ASE accessor (left column) triggers the calculator only on first
call; subsequent calls return the cached result until the geometry
or other input changes.
ASE accessor |
Property |
Method coverage |
Notes |
|---|---|---|---|
|
|
RHF / UHF / RKS / UKS |
+ D3(BJ) when |
|
|
All |
Alias for |
|
|
All |
Analytic nuclear gradients. |
|
|
All |
3-vector. Phase A (v0.5). |
|
|
RHF / UHF / RKS analytic; UKS via FD |
3N×3N matrix. RHF uses Phase 17b-3 CPHF; UHF uses Phase 17c per-spin CPHF; RKS uses 17c+; UKS falls back to |
|
|
RHF only (CPHF, Phase 17b-1) |
3×3 tensor. UHF / KS variants on the roadmap. |
Properties not yet implemented (raise PropertyNotImplementedError):
magmoms: per-atom spin density. The data exists on the result (density_alpha - density_beta) but vibe-qc has no exposed Mulliken-spin-population API yet; tracked as a tiny engineering ask.Periodic energy / forces / stress through the
ase.Atomsinterface. The underlying machinery is all shipped: Phase G1 analytic gradients for periodic RHF / UHF / RKS / UKS viacompute_gradient_periodic_*_gammaand the multi-k / open-shell variants, plus the Phase G2 stress tensor viacompute_stress_tensor. Only thease.AtomstoPeriodicSystemadapter on the ASE-calculator side is missing, so the molecular calculator above does not yet driverun_*_periodic_scffrom anase.Atomswithpbc=True. Translate to aPeriodicSystemmanually for periodic work; see tutorial 4.
Building structures with ASE¶
ASE’s structure-builder utilities (ase.build.*,
ase.spacegroup.crystal) produce Atoms objects you can pass
straight to VibeQC (molecular path) or convert to a
PeriodicSystem for the periodic path. The combination removes
hand-rolling of lattice vectors + fractional coordinates.
Molecules: ase.build.molecule¶
from ase.build import molecule
from vibeqc.ase import VibeQC
# ASE knows ~120 common small molecules by name (G2 / G3 / G2/97 set):
atoms = molecule("H2O")
atoms.calc = VibeQC(basis="6-31g*", functional="PBE")
atoms.get_potential_energy()
Try molecule("benzene"), molecule("NH3"), molecule("urea"),
molecule("naphthalene"), etc. — the full list is in
ase.collections.g2.
Periodic bulk: ase.build.bulk¶
from ase.build import bulk
import vibeqc as vq
# ASE knows common crystal prototypes by name. Pass through to
# vibe-qc's PeriodicSystem via the ase↔vibeqc bridge:
si = bulk("Si", "diamond", a=5.431) # ase.Atoms (PBC=True)
sysp = vq.ase.from_atoms(si) # → PeriodicSystem
basis = vq.BasisSet(sysp, "pob-tzvp")
result = vq.run_krhf_periodic_gdf(sysp, basis, kmesh=(1, 1, 1))
The bulk(...) helper covers "fcc", "bcc", "hcp",
"diamond", "zincblende", "rocksalt", "cesiumchloride", …
— the same set documented at
crystal_lattices.md.
Spacegroup-driven: ase.spacegroup.crystal¶
For more exotic lattices, give the spacegroup + basis explicitly:
from ase.spacegroup import crystal
import vibeqc as vq
# Rutile TiO2 (P4_2/mnm, #136):
tio2 = crystal(
symbols=["Ti", "O"],
basis=[(0.0, 0.0, 0.0), (0.305, 0.305, 0.0)],
spacegroup=136,
cellpar=[4.594, 4.594, 2.959, 90, 90, 90],
)
sysp = vq.ase.from_atoms(tio2)
basis = vq.BasisSet(sysp, "pob-tzvp")
This pattern produces every example shipped under
examples/periodic/<system>/ — the rutile, corundum, α-quartz,
wurtzite, perovskite scripts use exactly this idiom.
Surfaces: ase.build.surface¶
from ase.build import bulk, surface
import vibeqc as vq
cu = bulk("Cu", "fcc", a=3.615)
slab = surface(cu, (1, 1, 1), layers=4, vacuum=10.0)
sysp = vq.ase.from_atoms(slab)
# Periodic-G1a-e pure-DFT forces flow through the molecular
# VibeQC calculator today (HF + hybrid K-gradient queued for
# v0.6.x). Surface relaxation in pure DFT works:
from ase.optimize import BFGS
slab.calc = VibeQC(basis="pob-dzvp-rev2", functional="PBE")
# BFGS(slab).run(fmax=0.05) # uncomment for full opt
See crystal_lattices.md for the full
lattice catalogue + visualisations.
Reading structures via ASE I/O¶
ASE reads ~30 structure formats out of the box. Read once via ASE, then forward to vibe-qc:
from ase.io import read
import vibeqc as vq
# CIF (from COD / Materials Project / your own):
atoms = read("MgO.cif")
sysp = vq.ase.from_atoms(atoms)
# POSCAR (VASP 5):
atoms = read("POSCAR", format="vasp")
sysp = vq.ase.from_atoms(atoms)
# XYZ molecular:
atoms = read("water.xyz")
atoms.calc = vq.ase.VibeQC(basis="sto-3g")
# Multi-frame trajectories (.traj, .xyz):
frames = read("optimization.traj", index=":") # all frames
vibe-qc itself reads POSCAR / XYZ natively
(vq.read_poscar, vq.Molecule.from_xyz); the ASE path is
the right pick when the file format is exotic (CIF, FHI-aims
geometry, Gaussian input, Q-Chem input, etc.) or when you
want ASE’s lattice-cell handling.
Constraints¶
ASE’s constraint system works unchanged with vibe-qc — they project forces during the BFGS / FIRE / NEB step:
from ase.build import molecule
from ase.constraints import FixAtoms, FixBondLength, FixedPlane
from ase.optimize import BFGS
from vibeqc.ase import VibeQC
water = molecule("H2O")
water.calc = VibeQC(basis="6-31g*", functional="PBE")
# Freeze the oxygen atom — H atoms relax around it:
water.set_constraint(FixAtoms(indices=[0])) # atom 0 = O
BFGS(water).run(fmax=0.01)
# Or fix a bond length:
water.set_constraint(FixBondLength(1, 2)) # H1-H2 distance
# Or constrain motion to a plane (e.g. a slab atom in z):
water.set_constraint(FixedPlane(indices=[0], direction=[0, 0, 1]))
Same pattern works for periodic geometry optimisation
(atoms.pbc = True, pure-DFT only at v0.8.0 per the
periodic section below).
ASE + vqfetch¶
vqfetch (the external-structures
fetcher) emits both a vibe-qc input script and a regression
PeriodicSpec. For ASE workflows, the SPEC’s lattice + atom
list converts trivially to an ase.Atoms:
# Pull a structure from Materials Project:
# $ vqfetch mp --id mp-1265 → examples/regression/systems/periodic/mp_mp-1265.py
from examples.regression.systems.periodic.mp_mp_1265 import mp_mp_1265
import vibeqc as vq
# Build a PeriodicSystem from the SPEC; then bridge to ASE:
sysp = vq.PeriodicSystem.from_spec(mp_mp_1265) # vibe-qc native
atoms = vq.ase.to_atoms(sysp) # → ase.Atoms with PBC
print(atoms.get_chemical_formula()) # "Mg4O4"
print(atoms.cell) # cell + pbc preserved
# Or import the spec's provenance for citation tracking:
print(mp_mp_1265.provenance)
# Provenance(source_db='materials_project', source_id='mp-1265', ...)
The full provenance bundle (source DB, ID, DOI, license) is preserved through both round-trip directions — citation information stays attached to whatever you compute.
ASE + vq queue¶
ASE-driven workflows (BFGS opt, NEB, MD, basin hopping) are
batch-shaped and run well on a remote compute box via the
vq queue. Write the workflow as a normal
Python script + submit:
# my_opt.py — standalone script
from ase.build import molecule
from ase.optimize import BFGS
from vibeqc.ase import VibeQC
atoms = molecule("naphthalene")
atoms.calc = VibeQC(basis="def2-tzvp", functional="b3lyp",
dispersion="d3bj")
BFGS(atoms, trajectory="naphthalene.traj").run(fmax=0.01)
# Submit via vq:
vq submit my_opt.py --cpus 8 --mem-mb 16000 --wall-time-seconds 7200
# Watch the SCF trace + BFGS step output:
vq watch <jobid>
# Pull the .traj file back when done:
vq fetch <jobid> ./outputs/
ase gui ./outputs/naphthalene.traj
This is the recommended pattern for long ASE workflows —
laptop runs Jupyter for development; remote runs vq for the
real batch. See queue.md for the full vq story.
Dispersion (D3-BJ)¶
Pass dispersion= to fold Grimme D3-BJ dispersion into the
energy + forces:
# Use the SCF functional's D3-BJ parameters automatically.
atoms.calc = VibeQC(basis="6-31g*", functional="PBE",
dispersion="d3bj")
# Or specify a different functional's parameters explicitly:
atoms.calc = VibeQC(basis="6-31g*", functional="PBE",
dispersion="b3lyp") # uses B3LYP's D3-BJ params
# Or pass a vibeqc.D3BJParams object directly for fine control.
The dispersion energy is logged at INFO level on the
vibeqc.ase logger, and the per-component breakdown is exposed
on the calculator’s results dict:
atoms.get_potential_energy()
print(atoms.calc.results["e_scf"]) # SCF energy in eV
print(atoms.calc.results["e_dispersion"]) # D3(BJ) correction in eV
See tutorial 14: dispersion for the full theory + variants.
Workflow examples¶
The examples/ase_workflows/ directory ships runnable scripts for
the common ASE patterns, all using VibeQC as the backing
calculator:
Workflow |
Script |
Demonstrates |
|---|---|---|
Geometry optimization |
ASE’s |
|
Vibrational analysis |
|
|
NEB transition state |
Climbing-image NEB on NH₃ umbrella inversion |
|
NVE molecular dynamics |
Velocity-Verlet conservation diagnostic — drift < 10 meV / 25 fs is the force-energy consistency test |
|
Surface adsorption setup |
Pt(111) slab + H₂ atop placeholder. Periodic forces ship via Phase G1 for the full RHF / UHF / RKS / UKS family; the G2 stress tensor is now wired through |
Tip
For ASE’s standard tutorials, see the ASE workflow-tutorials parity matrix. Most molecular tutorials work with vibe-qc as a one-line calculator swap. Periodic atomic-force-driven workflows work via G1 and stress-driven ones (EOS, lattice relaxation) via G2, but both currently require driving the periodic-system path directly from Python until the ASE-calculator adapter for periodic systems lands.
Cross-validation against external codes¶
vibe-qc’s policy (CLAUDE.md § 10):
other QC programs (PySCF, ORCA, Psi4, CRYSTAL, …) are external
programs run out-of-process, not imported as backends. The
subprocess-runner pattern at
examples/regression/runner_*.py
spawns each external program, parses its output, and produces
a comparison artefact alongside the vibe-qc result.
# examples/regression/run_suite.py walks a curated test set:
# - vibe-qc runs in-process (it's *our* code).
# - PySCF / ORCA / CRYSTAL run as subprocesses (out-of-process).
# - All parity numbers are captured + tabled in summary.md.
# Single-system smoke check, command-line:
python -m examples.regression.run_suite \
--include water_6_31gs \
--references pyscf orca \
--output-md ./parity-report.md
For an ASE-driven idiom (the same Atoms against vibe-qc +
external runner subprocess output), use the regression suite’s
runner modules directly:
from ase.build import molecule
from vibeqc.ase import VibeQC
from examples.regression.core import runner_pyscf, runner_orca
atoms = molecule("H2O")
atoms.calc = VibeQC(basis="6-31g*")
e_vibeqc = atoms.get_potential_energy()
# PySCF as an external program — spawned, parsed, never imported:
pyscf_result = runner_pyscf.run_rhf(atoms, basis="6-31g*")
e_pyscf = pyscf_result.energy_ev # parsed from PySCF's stdout
# ORCA likewise:
orca_result = runner_orca.run_rhf(atoms,
orcasimpleinput="HF 6-31G* EnGrad")
e_orca = orca_result.energy_ev
print(f"vibe-qc: {e_vibeqc:.6f} eV")
print(f"PySCF: {e_pyscf:.6f} eV Δ = {1000*(e_vibeqc - e_pyscf):.3f} meV")
print(f"ORCA: {e_orca:.6f} eV Δ = {1000*(e_vibeqc - e_orca):.3f} meV")
The runner modules handle the program-specific bits:
PySCF runner — writes a minimal Python driver script, spawns
python <script>(with the PySCF venv on PATH), parses energy / forces / dipole from JSON output.ORCA runner — emits the input file (
! <orcasimpleinput>coordinate block), spawns
orca input.inp, parses.engradfor forces and the.outfile for energy / dipole.
CRYSTAL runner — emits the
.d12input file (lattice + basis + SCF block), spawns Pcrystal via thecontrib/run-crystal.shwrapper, parsesout.outfor energy.
Expected output for the H₂O / RHF / 6-31G* example (verified end-to-end via the runner-subprocess pattern):
vibe-qc: -2068.294643 eV
PySCF: -2068.294643 eV Δ = 0.000 meV
ORCA: -2068.294643 eV Δ = 0.000 meV
The runners gracefully skip when an external program isn’t
available (PySCF not in any venv; orca not on $PATH).
Missing rows show as (unavailable) in the report — the
non-skipped rows still produce parity numbers.
Note
Retirement of the in-process vibeqc.benchmark.PySCFCalculator /
make_psi4_calculator shims. Earlier vibe-qc versions exposed
ASE-style calculators that imported pyscf / psi4 inside
vibe-qc’s process. The CLAUDE.md sec 10 policy (commits
545db04 / 7ab9384 / d451b84 for PySCF; 6910ce5d for the
final PySCF / Psi4 retirement in v0.8.0) moves all external
programs out-of-process. The shims are gone from
vibeqc.benchmark and import will fail; migrate to the
subprocess runner pattern above. make_orca_calculator survives:
ORCA’s ASE wrapper is itself a subprocess driver, so it does not
violate the in-process boundary.
See:
cross-validation tutorial — H₂O / NaCl / water-dimer worked walkthrough, now using the subprocess runners.
external codes user guide — installing ORCA / Psi4 / CRYSTAL14 alongside vibe-qc, plus licensing realities.
examples/regression/— full regression-suite runner directory.
ASE option translation¶
The VibeQC calculator accepts both ASE-style kwargs and vibe-qc
options objects. The ASE-style ones map to common defaults; the
vibe-qc options structs let you reach every knob.
# Common cases via kwargs:
VibeQC(basis="def2-tzvp") # RHF defaults
VibeQC(basis="def2-tzvp", functional="PBE") # RKS defaults
VibeQC(basis="def2-tzvp", functional="PBE",
charge=-1, multiplicity=2) # OH⁻ doublet
VibeQC(basis="def2-tzvp", dispersion="d3bj",
functional="PBE") # PBE-D3(BJ)
# Full SCF-options control:
import vibeqc as vq
opts = vq.RHFOptions()
opts.max_iter = 200
opts.level_shift = 0.4
opts.diis_history = 20
VibeQC(basis="def2-tzvp", rhf_options=opts)
Available options structs: rhf_options=, uhf_options=,
rks_options=, uks_options=. Pass whichever matches the method
the calculator will dispatch to.
Logging¶
The ASE calculator logs progress on the vibeqc.ase logger.
Configure as you would any Python logger:
import logging
logging.basicConfig(level=logging.INFO)
# Now atoms.get_potential_energy() prints:
# INFO:vibeqc.ase:RHF / sto-3g n_atoms=3 n_electrons=10 charge=0 mult=1
# INFO:vibeqc.scf:SCF converged in 10 iterations E = -75.984...
The underlying SCF trace logs on vibeqc.scf (per-iteration
energy, gradient norm, DIIS error vector). Set level=logging.DEBUG
for additional detail (per-iteration timing, integral cache hits).
Periodic calculations (current state)¶
vibeqc.ase.from_atoms(atoms) converts an ASE Atoms with
pbc=True + non-trivial cell directly to a vibe-qc
PeriodicSystem. From there, the native Γ-only GDF driver
(run_krhf_periodic_gdf / run_krks_periodic_gdf at
kmesh=(1,1,1)) handles closed-shell RHF + RKS at sub-µHa
parity to PySCF.pbc.GDF. The full multi-k native loop is in
progress; non-Γ meshes deliberately raise at the public API
boundary (see multi_k_scf.md for the
v0.8.0 ship state).
from ase.build import bulk
import vibeqc as vq
# ASE-built MgO rocksalt, primitive cell.
atoms = bulk("MgO", "rocksalt", a=4.211)
sysp = vq.ase.from_atoms(atoms)
basis = vq.BasisSet(sysp, "sto-3g")
# Γ-only native GDF (the v0.8.0 shipped path).
result = vq.run_krhf_periodic_gdf(sysp, basis, kmesh=(1, 1, 1))
print(f"E = {result.energy_per_cell_ha:.6f} Ha/cell")
For forces on a periodic system today: Phase G1 analytic
gradients ship for RHF, RKS (LDA / GGA / hybrid), plus the UHF /
UKS open-shell counterparts. Phase G2 provides the periodic
stress tensor via compute_stress_tensor. Until the
ASE-calculator adapter for periodic systems lands, drive both
directly from Python (see the patterns under tests/test_periodic_gradient*.py).
The recommended workflow today is:
Geometry generation + cell setup: ASE (
bulk,surface,crystal, CIF reader).Energy + Γ-only single-point: native GDF driver.
Forces (pure DFT, single k-point): native G1.
Geometry optimisation at multi-k: not yet — falls back to fixed-cell single-points + ASE driving the geometry step manually.
For full periodic-SCF detail see:
crystal lattices — Bravais classification + per-lattice visualisations + worked-example index.
k-point meshes — Monkhorst-Pack, IBZ.
multi-k SCF — KRHF / KRKS current state, parity target, scope caveats.
Ewald summation — long-range Coulomb.
When a dedicated VibeQCPeriodic ASE-calculator class ships
(v0.x.x), its API will mirror the molecular calculator but
accept atoms.cell / atoms.pbc directly:
# Forward-looking; not yet shipping:
atoms.calc = VibeQCPeriodic(basis="pob-tzvp", functional="PBE",
kmesh=[6, 6, 6])
BFGS(atoms).run(fmax=0.05) # full periodic relaxation
Versions¶
VibeQC requires ase>=3.22. Install via the optional extra
captured in pyproject.toml:
pip install '.[ase]' # from the repo
pip install 'vibe-qc[ase]' # once on PyPI
The cross-validation framework in :mod:vibeqc.benchmark is part
of the base install. PySCF is no longer driven in-process — the
former PySCFCalculator ASE shim was retired (CLAUDE.md § 10
forbids in-process imports of external QC programs under
python/vibeqc/); PySCF parity now runs out-of-process via
the subprocess runner at
examples/regression/core/runner_pyscf.py.
See also¶
external codes user guide — installing ORCA / Psi4 / Gaussian / NWChem alongside vibe-qc.
cross-validation tutorial — end-to-end walkthrough.
example outputs gallery — bundled reference outputs from the
examples/ase_compare/andexamples/ase_workflows/scripts.ASE tutorial parity matrix — which ASE tutorials currently work with vibe-qc, which need v0.5+ features.