Slabs and adsorbates

vibeqc.build is the native (no-ASE) helper for surface-catalysis inputs. It generates :class:PeriodicSystem objects with a vacuum gap along the third lattice vector — the standard layout for slab + vacuum calculations.

The submodule is deliberately small: closed-form surface unit cells for the most common low-index facets of fcc, bcc, and hcp metals. For exotic facets or non-metals, build a PeriodicSystem directly from coordinates (see crystal_lattices).

A 5-layer Fe(100) slab with N₂ adsorbed

import vibeqc as vq

# 5-layer Fe(100), 2×2 lateral cell, 12 Å vacuum. The default lattice
# constant (2.866 Å) comes from vq.BULK_LATTICE_CONSTANTS; override
# with a=...
slab_sys, info = vq.slab(
    "Fe", facet=(1, 0, 0), n_layers=5,
    vacuum=12.0, supercell=(2, 2),
    multiplicity=21,  # 5×4 = 20 atoms × 4 unpaired e-/Fe ≈ FM start guess
)

# Side-on N₂ at a bridge site, 1.9 Å above the top Fe layer.
slab_with_n2 = vq.place_adsorbate(
    slab_sys, "N2", info=info,
    site="bridge", orientation="side-on", height=1.9,
    bond_length=1.10,
)

The info object (a :class:SlabInfo) carries the per-atom layer index, which feeds the frozen-substrate relaxation pattern below.

Built-in adsorbate library

vq.build_molecule(name, bond_length=...) returns a small library of common adsorbate geometries (in Å):

Name

Atoms

Default geometry

H, H2, N2, O2

1–2

linear along z

CO, OH, NH

2

linear along z

H2O, NH3, CH4

3–5

gas-phase neutral geometry

For anything else, pass an explicit ((symbol, (x, y, z)), …) sequence in Å.

Adsorption sites

place_adsorbate(slab, ads, site=..., info=info) resolves named sites against the primitive surface cell:

Site

fcc(100), bcc(100)

fcc(111), hcp(0001)

bcc(110)

"top"

atop a metal

atop a metal

atop a metal

"bridge"

2-fold bridge

2-fold bridge

short bridge

"hollow"

4-fold hollow

(degenerate w/ fcc/hcp)

quasi-3-fold

"fcc-hollow"

hcp(110) absent, fcc-only

"hcp-hollow"

offset 3-fold hollow

For full control, pass position=(x, y) in Å (relative to the lattice origin) instead of site=.

Frozen-substrate relaxation

The standard pattern: freeze the bottom layers and relax only the top layers + adsorbate. :func:vibeqc.relax_atoms takes a freeze_indices= argument; :meth:SlabInfo.bottom_layer_indices produces the list:

freeze = info.bottom_layer_indices(3)  # bottom 3 of 5 layers
opt = vq.relax_atoms(
    slab_with_n2,
    basis_name="sto-3g",
    kmesh=vq.monkhorst_pack(slab_with_n2, [2, 2, 1]),
    method="UKS",
    functional="pbe",
    freeze_indices=freeze,
)

Internally the optimizer pins the fractional coordinates of the frozen atoms via L-BFGS-B box bounds and zeros their gradient components, so the reported |grad| reflects only the free degrees of freedom.

Animate the relaxation in vibe-view

Pass output_trajectory="stem" and relax_atoms writes a vibe-view-renderable QVF archive on exit — one frame per accepted L-BFGS-B step, with the initial geometry as frame 0 and the converged geometry as the last frame:

opt = vq.relax_atoms(
    slab_with_n2,
    basis_name="sto-3g",
    kmesh=vq.monkhorst_pack(slab_with_n2, [2, 2, 1]),
    method="UKS",
    functional="pbe",
    freeze_indices=freeze,
    output_trajectory="slab_n2_relax",   # → slab_n2_relax.qvf
)

The archive ships as QVF v2 (per-frame lattice + dim) for any PeriodicSystem input, so vibe-view’s renderer (per the “Periodic reaction paths (QVF v2)” section) draws the unit cell and wraps atoms across in-plane periodic boundaries automatically. Default output_trajectory=None is a no-op — no per-step capture overhead.

Open-shell + multi-k for metallic slabs

Periodic UKS / UHF dispatches via the BIPOLE J/K backend. Metallic slabs typically need a finite k-mesh in the surface plane — pass kpoints= to :func:vibeqc.run_periodic_job:

vq.run_periodic_job(
    slab_with_n2,
    basis=vq.BasisSet(slab_with_n2.unit_cell_molecule(), "sto-3g"),
    method="UKS",
    functional="pbe",
    jk_method="bipole",         # required for UHF / UKS today
    kpoints=[4, 4, 1],          # k-mesh in surface plane, 1 along z
)

The GDF path is RHF/RKS-only at present; selecting jk_method="gdf" together with method="UKS" raises NotImplementedError and steers you to jk_method="bipole".

Dispersion correction for periodic systems

vq.compute_d3bj_periodic returns the D3-BJ dispersion energy per unit cell. Two backends:

  • backend="dftd3" (recommended) — Grimme’s reference Fortran library via the optional dftd3 Python package. Bit-exact periodic D3-BJ. Install with pip install dftd3 or pip install -e '.[dispersion]'.

  • backend="builtin" — vibe-qc’s native C++ D3-BJ on a supercell expansion of the unit cell. Approximate (CN values near the supercell boundary are wrong); the per-cell energy converges with larger supercell. Use for elements not yet covered by the dftd3 install or to stay external-dependency-free.

  • backend="auto" (default) — picks dftd3 when available.

Standalone call:

import vibeqc as vq
slab_sys, info = vq.slab("Fe", facet=(1, 0, 0), n_layers=5, vacuum=12.0,
                         supercell=(2, 2), multiplicity=21)
res = vq.compute_d3bj_periodic(slab_sys, "pbe", with_gradient=True)
print(res.energy, "Ha per cell;", res.backend, res.supercell)

Or inline with run_periodic_job via the new dispersion= keyword:

vq.run_periodic_job(
    slab_with_n2,
    basis=vq.BasisSet(slab_with_n2.unit_cell_molecule(), "sto-3g"),
    method="UKS",
    functional="pbe",
    jk_method="bipole",
    kpoints=[4, 4, 1],
    dispersion="pbe",            # or True (uses the current functional)
    dispersion_backend="auto",
)

The dispersion piece is logged as a separate block in the .out file and the SCF result is wrapped with a _DispersionAugmented proxy that exposes .energy_total = E_SCF + E_disp (the bare .energy is unchanged).

For a slab with vacuum, the cutoff is naturally bounded along the vacuum direction — pass the slab PeriodicSystem unchanged. The dftd3 backend’s periodic=[True, True, True] flag handles it correctly.

What’s not here yet

The following surface-catalysis features are tracked in docs/roadmap.md but not yet shipped:

  • Relaxed bond-length / angle scans on the surface.

  • Nudged elastic band (NEB / CI-NEB).

  • Hubbard-U correction (+U) for strongly correlated d/f electrons.

  • Periodic D4 (the molecular D4 path already exists; periodic generalisation pending).

If you need any of these urgently, file an issue.