Tutorial 27: Viewing geometries, orbitals, and vibrations with MolTUI

You’ll learn: how to inspect vibe-qc output files — geometries, molecular orbitals, normal modes — directly in the terminal, without firing up Avogadro / Jmol / VMD.

Why: when you’re SSH’d into a remote machine, iterating quickly on a screen full of input files, or just allergic to GUI context-switches, MolTUI is the right tool. It renders 3D structures inline using Unicode block characters; you navigate with the keyboard.

Prerequisites: vibe-qc installed in a venv, plus MolTUI on top. Two install paths:

# As a vibe-qc extra (single command, captures the dependency):
.venv/bin/pip install '.[viewer]'

# Or via the interactive helper:
./scripts/install_optional_tools.sh

After install, moltui is on the venv’s PATH:

.venv/bin/moltui --version

What MolTUI reads

Format

What it shows

Where vibe-qc writes it

.xyz

Geometry (single frame)

Molecule.to_xyz(), ASE export

.molden

Geometry + MOs + normal modes

run_job(...) by default

.cube

Volumetric data (orbitals, density)

write_cube_mo(), write_cube_density()

.fchk

Geometry + MOs + normal modes (Gaussian fchk)

(not yet emitted by vibe-qc)

.gbw

ORCA’s native binary (needs orca_2mkl)

(not emitted by vibe-qc; ORCA-specific)

.hess

ORCA’s normal-modes file

vq.write_orca_hess(path, mol, hess) (M1)

Multi-frame .xyz

Trajectory animations

vq.write_xyz_trajectory(path, frames) (M2)

.opt

Geometry-optimization history (multi-XYZ + per-step E)

vq.write_opt_trajectory(path, frames, energies) (M2)

The two formats vibe-qc writes today out of the box are .molden (full molecular-orbital data, plus geometry, plus normal modes if a Hessian was computed) and .cube (volumetric data for individual orbitals or the total density). Both work with MolTUI directly.

Step 1: Generate the files

from vibeqc import Atom, Molecule, run_job

mol = Molecule([
    Atom(8, [0.0,  0.00,  0.00]),
    Atom(1, [0.0,  1.43, -0.98]),
    Atom(1, [0.0, -1.43, -0.98]),
])

run_job(
    mol,
    basis="6-31g*",
    method="rks",
    functional="PBE",
    output="water",
)
# → water.out, water.molden

That gives you water.molden with the geometry + MOs + occupations. Add a cube for density / orbital visualization:

import numpy as np
import vibeqc as vq

basis = vq.BasisSet(mol, "6-31g*")
result = vq.run_rks(mol, basis, vq.RKSOptions(functional="PBE"))

# HOMO orbital cube
homo = mol.n_electrons() // 2 - 1
vq.write_cube_mo("water-homo.cube",
                 np.asarray(result.mo_coeffs), homo, basis, mol,
                 spacing=0.2, padding=3.0)

# Total density cube
vq.write_cube_density("water-density.cube",
                      np.asarray(result.density), basis, mol,
                      spacing=0.2, padding=3.0)

Step 2: View geometries

Launch MolTUI on the .molden:

moltui water.molden

The default view shows the molecular geometry rendered inline:

                    ┌──────── water.molden ────────┐
                    │                              │
                    │              o               │
                    │                              │
                    │           h     h            │
                    │                              │
                    │                              │
                    └──────────────────────────────┘
                    [g] geometry  [o] orbitals  [v] vibrations

(rendering varies by terminal size + Unicode font; on a typical full-screen 100×40 character terminal you get a recognisable 3D structure)

Keyboard navigation (defaults — see moltui --help for the authoritative list):

Key

Action

arrow keys / h j k l

Rotate the structure

+ / -

Zoom in / out

g

Switch to geometry view

o

Switch to orbitals view (.molden / .cube / .fchk)

v

Switch to vibrations view (.molden / .hess / .fchk)

Tab / Shift-Tab

Cycle frame / orbital / mode

q

Quit

Step 3: View orbitals

From the geometry view, press o to switch to orbitals. MolTUI parses the molden file’s MO-coefficient block and renders an isosurface inline:

              ┌───── HOMO   ε = -6.09 eV   occ = 2 ─────┐
              │                                         │
              │              ▓▒░       ░▒▓              │
              │             ▓▒░         ░▒▓             │
              │             ▓▒░    o    ░▒▓             │
              │             ▒░           ░▒             │
              │            h                h           │
              │                                         │
              │                                         │
              └─────────────────────────────────────────┘
              [Tab]/[Shift-Tab] cycle MO  [+/-] zoom  [q] quit

Light shading is the positive isosurface lobe, darker is the negative. The HOMO of water is the in-plane oxygen lone pair — two lobes opposite the H atoms.

Tab cycles through the MOs; the header shows the eigenvalue and the occupation. Useful for sanity-checking that:

  • The HOMO has the right symmetry for your molecule (lone pairs, π systems, etc.).

  • The LUMO ordering is what you expect (no surprise σ* / Rydberg crossings).

  • The orbital energies (header) match what’s in the .out file’s orbital table.

For finer-grained orbital views — explicit isovalue control, publication-quality renderings — open the .molden or one of the cube files in Avogadro / Jmol / VMD instead. MolTUI is for quick triage, not for paper figures.

Step 4: View cube files (volumetric data)

For more detailed per-orbital control:

moltui water-homo.cube
moltui water-density.cube

Cube files are dense volumetric grids — MolTUI raycasts an isosurface at a default isovalue (usually 0.05 a.u.). Adjust with:

[i]/[I]  decrease/increase isovalue
[r]      reset isovalue to default

A density cube (sum over occupied orbitals) shows the total electron density — the “shape” of the molecule. A single-MO cube shows one orbital at a time (HOMO, LUMO, anything you asked write_cube_mo to output).

Step 5: View vibrational modes

If your .molden has a [FREQ] and [FR-COORD] block (which vibe-qc emits when you compute a Hessian), MolTUI renders the normal modes as animated displacement vectors. Press v from any view to switch.

        ┌── mode 7   ω = 1751.3 cm⁻¹   IR = 67.8 km/mol ──┐
        │                                                 │
        │              ↑   o   ↑                          │
        │              ↑       ↑                          │
        │              h       h                          │
        │              (symmetric stretch)                │
        │                                                 │
        └─────────────────────────────────────────────────┘
        [Tab]/[Shift-Tab] cycle mode  [Space] play/pause

The arrows (or oscillating atoms, depending on the terminal + animation toggle) show the eigenvector of that normal mode. For water you’ll see three real modes — bend, symmetric stretch, antisymmetric stretch — and six near-zero translation/rotation modes that MolTUI typically hides.

To produce a .molden with vibrational data:

import vibeqc as vq

# Compute analytic Hessian + frequencies
mol = vq.Molecule.from_xyz("water-equilibrium.xyz")
basis = vq.BasisSet(mol, "6-31g*")
result = vq.run_rhf(mol, basis)
hess = vq.compute_hessian_rhf_analytic(
    mol, basis, result, basis_name="6-31g*",
)
# Write a molden file with the normal modes attached
vq.write_molden("water-with-freqs.molden", mol, basis, result,
                hessian=hess)

Tip

Direct ORCA-format .hess export is available via :func:vibeqc.write_orca_hess (Phase M1). Useful when you’d rather inspect just the vibrational data than parse the full molden bundle — and it’s the format moltui reads natively for ORCA-style normal-modes display:

hess = vq.compute_hessian_rhf_analytic(mol, basis, result, basis_name="6-31g*")
vq.write_orca_hess("water.hess", mol, hess)
# then:  moltui water.hess

The same ASCII format means cross-code visual diff’ing works: moltui vibeqc.hess and moltui orca.hess should show identical modes when the underlying CPHF kernels agree. See tutorial 26 § cross-validation: vibrations for the cross-validation script that asserts ~0.5 cm⁻¹ agreement on H₂O.

Tips and tricks

  • Over SSH: MolTUI runs entirely in your terminal — no X11 forwarding, no GUI windows. Just ssh in, cd to your output directory, and moltui ....

  • Quick triage: for a calculation that just finished, look at the .out file first (text — energy, convergence, warnings), then moltui ...molden to spot-check the geometry

    • HOMO shape. Catches “the SCF converged to the wrong state” bugs immediately.

  • Animations (Phase M2): vibe-qc writes multi-XYZ trajectories natively — geometry-optimization paths, NEB images, normal-mode movies, MD snapshots — and moltui animates them frame-by-frame.

    Optimization history → moltui movie:

    import vibeqc as vq
    geoms, energies, rms_grads = run_my_optimization(mol)
    vq.write_opt_trajectory("h2o.opt", geoms, energies,
                              rms_grad=rms_grads)
    
    moltui h2o.opt          # animated optimisation path with per-step E in the
                            # comment line — Tab cycles iterates
    

    Normal-mode movie — paired helper builds the displaced frames for one vibrational mode:

    hess = vq.compute_hessian_rhf_analytic(mol, basis, result, basis_name="6-31g*")
    frames = vq.normal_mode_trajectory(mol, hess, mode_index=8,
                                         amplitude=0.5, n_frames=20)
    vq.write_xyz_trajectory("h2o-asym-stretch.xyz", frames)
    
    moltui h2o-asym-stretch.xyz   # animated symmetric-stretch movie
    

    Falling back via ASE — convert any ASE .traj to multi-XYZ — also still works:

    ase convert output-h2o-opt.traj output-h2o-opt.xyz
    moltui output-h2o-opt.xyz
    
  • Keep moltui open in a separate terminal while you iterate on input files; reload by re-running moltui <file> after each calculation finishes.

Crystalline orbitals: not yet

MolTUI is a molecular viewer. Its supported formats — .xyz, .molden, .cube, .fchk, .gbw, .hess — were all designed for finite molecular systems with no periodic boundary conditions. Specifically:

  • .molden has no concept of a unit cell. The format predates most periodic-DFT implementations and there’s no widely-agreed extension for periodic data.

  • .cube can in principle carry periodic data (the header has a “voxel grid + origin” format that maps onto a periodic cell), but most viewers — MolTUI included — don’t replicate the cell or render the lattice vectors. You’d see the unit-cell content as a free-floating cluster.

  • Bloch states ψ_{n,k}(r) are k-labeled and generally complex-valued (ψ = u_k(r) e^{i k·r}); MolTUI has no k-point-aware navigation.

So periodic / crystalline orbitals don’t render correctly in MolTUI today. This is a real gap — for a quick triage of a periodic SCF, you’d want the same “ssh in, look at orbital, move on” workflow that MolTUI gives molecular calculations.

What works today for periodic / crystalline visualization:

  • Vibe-qc’s plot scriptsexamples/plots/h-chain-bands-dos.py, examples/plots/lih-chain-bloch-density.py, examples/plots/h-chain-crystalline-orbitals.py — produce matplotlib PNGs of bands, DOS, Bloch densities, and crystalline-orbital contour plots. Run from the repo, viewable in any image viewer.

  • VESTA (free, GUI) — designed for crystals; reads cube files with periodic-cell metadata and replicates the lattice correctly.

  • XCrySDen (free, GUI) — direct support for k-resolved crystalline orbitals; bands + DOS + isosurfaces.

  • VMD with pbctools — script the cell replication; not as smooth as VESTA but works.

Ask the upstream: a feature request to MolTUI for periodic-cell awareness (read lattice vectors from CUBE / future periodic formats; render unit-cell box; replicate atoms across PBC) would unlock the same triage workflow for crystals. Track at github.com/kszenes/moltui.

When MolTUI isn’t enough

MolTUI is a triage tool — fast, terminal-native, low-resolution. For:

  • Publication figures — Avogadro 2 (cross-platform GUI), VMD (scriptable, scientific-grade), ChimeraX (high-quality surfaces), Jmol (web-friendly).

  • Detailed orbital analysis — natural-bond-orbital decompositions, ELF/NCI plots, charge-transfer maps — use Multiwfn (free, Windows/Wine on macOS+Linux) or pymol.

  • Crystal structures — VESTA (free), pymatgen visualizations.

vibe-qc writes formats every one of these tools reads (.molden, .cube, .xyz, future .gbw/.hess); pick the right viewer for the job. MolTUI’s superpower is no GUI, no setup, runs over SSH — that’s the niche.

See also