Visualization#

Numbers tell you how much; pictures tell you where and why. This chapter turns the density and orbitals from an SCF into images you can inspect — 3-D isosurfaces of orbitals and real-space fields, 2-D plots, and convergence curves — all from inside a notebook, through the qc.view layer.

Tip

Extra packages for viewing The 3-D viewer uses py3Dmol and static-image export uses kaleido; the 2-D plots use matplotlib. Install what you need into your project venv:

uv add py3dmol kaleido --project "$UV_PROJECT"   # 3-D viewer + static PNG export

(matplotlib usually comes with the base scientific stack.) Every viewer entry point exists both as a method (mychk.view3d(...)) and a function (qc.view3d(mychk, ...)).

The orbital table#

Before drawing anything, list the orbitals so you know what to ask for. mychk.orbitals prints a picking table — index, occupancy, energy — with the frontier orbitals marked, and renders as an HTML table in a notebook:

import qc
water = "O 0 0 0.117; H 0 0.757 -0.469; H 0 -0.757 -0.469"
m = qc.chk.new(atom=water, ao="cc-pvdz", unit="angstrom").scf(ref="r").run()

m.orbitals
#  idx   occ        E(Eh)   label
#  ---  ----  -----------  -----
#    3   2.0     -0.56656
#    4   2.0     -0.49314   HOMO
#    5   0.0      0.18604   LUMO
#    6   0.0      0.25618

Indices are 0-based absolute (the same convention as PySCF), while the human-friendly path is the HOMO/LUMO±n tokens the viewer also accepts. For water/cc-pVDZ the HOMO is index 4 and the LUMO is index 5.

3-D isosurfaces: view3d#

An isosurface is the surface on which a scalar field takes a fixed value (the isovalue) — the intuitive way to “see” a cloud-like quantity in 3-D. view3d renders one inline; the last expression in a cell displays it:

m.view3d("density")               # electron-density isosurface
m.view3d(orbital="HOMO")          # a signed two-lobe orbital (positive red, negative blue)
m.view3d(orbitals="HOMO-2:LUMO+2")   # a small-multiples gallery
m.view3d("elf", isovalue=0.8)     # a real-space field at a chosen isovalue

Scalar fields#

view3d(field) accepts the whole qc-prop real-space family — density, spin, laplacian, rdg, iri, elf, lol, nci, igm, esp, mo, alie, deformation — each with a sensible default isovalue. Tune the render with isovalue=, color=, style= ("stick"/"sphere"/"line"), width=/height=, background=, and the grid with spacing=/margin= (bohr). These fields are the visual side of the molecular-properties suite — the same ELF, NCI, ESP you will compute as numbers there.

Selecting orbitals#

Orbitals render as signed two-lobe isosurfaces. Select them with orbital= (one) or orbitals= (several → gallery), mixing any of:

form

example

meaning

int

5

one orbital (0-based absolute)

range string

"10:20"

an inclusive range

frontier token

"HOMO", "LUMO+2"

resolved from the occupations

frontier range

"HOMO-3:LUMO+3"

a window around the gap

mixed list

["HOMO-1", "HOMO", 5]

any combination

spin="alpha" (default) or "beta" picks the channel for UHF/ROHF.

Cube data: mo_cube#

When you want the numbers behind the picture — to export a Gaussian cube, or feed another tool — mo_cube builds and caches the cube data, returning handles that carry each orbital’s energy, occupancy, and cube text:

handles = m.mo_cube("HOMO-1:LUMO+1")
[(h.orbital, round(h.energy, 3), h.occ) for h in handles]
# [(3, -0.567, 2.0), (4, -0.493, 2.0), (5, 0.186, 0.0), (6, 0.256, 0.0)]

Cubes are memoized for the session, keyed by a hash of the MO coefficients — re-running an SCF that changes the orbitals automatically invalidates stale cubes (m.mo_cache / m.mo_cache.clear()).

2-D plots#

Not everything is best in 3-D. Three matplotlib figures cover the common flat views:

m.plot_convergence()   # the SCF convergence: E, |ΔE|, and RMS gradient vs cycle
m.plot_nci()           # the 2-D NCI plot: reduced density gradient s vs sign(λ₂)ρ
m.plot_field_plane("density", ...)   # a scalar field on a cut plane

plot_convergence() is the quickest health check on an SCF (a smooth descent to the threshold); plot_nci() is the standard non-covalent-interaction diagnostic (spikes at low density reveal H-bonds, van der Waals contacts, and steric clashes). Each returns a matplotlib Figure.

Exporting and sharing#

A 3-D view can be written to a self-contained HTML file (the 3Dmol.js library is embedded, so it opens offline and in the VSCode webview):

m.view3d(orbital="HOMO").to_html("homo.html")          # standalone HTML
m.view3d(orbital="HOMO").to_html("homo.html", self_contained=False)   # smaller, loads 3Dmol.js from a CDN

With kaleido installed, the matplotlib/plotly figures can be saved as static PNGs the usual way (fig.savefig(...) / plotly write_image).

Worked example: look at the frontier orbitals#

import qc
water = "O 0 0 0.117; H 0 0.757 -0.469; H 0 -0.757 -0.469"
m = qc.chk.new(atom=water, ao="cc-pvdz", unit="angstrom").scf(ref="r").run()

m.orbitals                              # find the HOMO/LUMO (indices 4 and 5)
m.view3d(orbitals="HOMO-1:LUMO+1")      # gallery of the frontier orbitals
m.view3d("density").to_html("water_density.html")   # save the density isosurface

Exercise 8

  1. m.orbitals shows the HOMO at index 4 with occupancy 2.0 and the LUMO at index 5 with occupancy 0.0. Write two view3d calls: one for just the HOMO, and one for a gallery from HOMO−1 to LUMO+1.

  2. You want to hand a colleague an interactive 3-D orbital they can open in a browser with no Python. Which method produces that, and what makes the file work offline?

  3. Your notebook shows ImportError: qc.view 3D rendering needs py3Dmol. What is the one-line fix, and why do the 2-D plot_* calls still work without it?

Next, the logging & output chapter covers reading, replaying, and saving what a run emits. Then we reach the large molecular-properties suite, whose real-space fields are exactly what view3d draws.