Bond analysis: Wiberg, NPA/NBO, EDA, and orbital entanglement

Chemists rarely stop at a total energy. The questions that follow a converged SCF – how strong is this bond, where did the charge go, what holds these fragments together – are answered by bond-analysis methods, and vibe-qc ships four complementary families of them. Each family looks at the same density matrix through a different lens: Wiberg indices count shared electron pairs, NPA/NBO recasts the density in chemically meaningful natural orbitals, EDA splits an interaction energy into physical components, and entanglement measures read bonding straight off the quantum-information structure of the wavefunction.

For energy-resolved periodic bonding analysis (COHP/COOP curves, band-by-band bonding character), use the canonical COOP/COHP module instead – the methods on this page give integrated per-bond scalars.

What ships where

Wiberg bond indices and NPA charges are computed automatically on every population dump: they appear as extra sections in the .population.{txt,json} sidecars (see Output files) next to the Mulliken / Löwdin / Mayer data, and their defining papers are cited in the end-of-run references block. The NBO search, EDA, and entanglement analyses are Python-API workflows you call on an SCF result.

Wiberg bond indices and the delocalization index

The Wiberg bond index counts the shared electron pairs between two atoms from the squared density-matrix elements in the Löwdin (symmetrically orthogonalised) basis. It is less basis-set sensitive than the Mayer bond order and complements it: Mayer contracts through the overlap matrix, Wiberg through the orthogonalised density.

import vibeqc as vq
from vibeqc.bond_analysis import wiberg_bond_orders, bond_order_summary

mol = vq.Molecule([
    vq.Atom(8, [0.0, 0.0, 0.0]),
    vq.Atom(1, [0.0, 1.43, -0.98]),
    vq.Atom(1, [0.0, -1.43, -0.98]),
])
basis = vq.BasisSet(mol, "sto-3g")
result = vq.run_rhf(mol, basis)

W = wiberg_bond_orders(result, basis, mol)   # (n_atoms, n_atoms)
print("O-H Wiberg index:", W[0, 1])

# All bond-order metrics side by side:
summary = bond_order_summary(
    result, basis, mol,
    compute_mayer=True,
    compute_wiberg=True,
    compute_delocalization=True,
)
print(summary.summary())

delocalization_index computes an AO-approximated delocalization index (DI) from the Löwdin-basis density; the exact DI is defined over QTAIM atomic basins (see the QTAIM guide). Periodic Gamma-point results go through the same code via vibeqc.bond_analysis.periodic_wiberg_bond_orders and periodic_delocalization_index.

References: Wiberg, Tetrahedron 24, 1083 (1968); Matito, Solà, Salvador & Duran, Faraday Discuss. 135, 325 (2007); Outeiral, Vincent, Martín Pendás & Popelier, Chem. Sci. 9, 5517 (2018).

Energy decomposition analysis (EDA)

EDA answers “why do these two fragments bind” by partitioning the interaction energy into electrostatic, exchange/Pauli-repulsion, polarisation, and dispersion components. vibe-qc implements the LMO-EDA scheme (Su & Li 2009) and the original Morokuma (1971) decomposition as post-processing over three SCF runs: the supersystem and the two isolated fragments (in the supersystem basis for BSSE consistency; see the counterpoise section of the basis sets guide).

from vibeqc.eda import eda_lmo

eda = eda_lmo(
    e_total, e_frag1, e_frag2,
    fock_total, fock_frag1, fock_frag2,
    density_total, density_frag1, density_frag2,
    overlap,
)
print(eda.summary())   # E_int split into elstat / exch / rep / pol / disp

vibeqc.eda.fragment_density_matrix builds the per-fragment projected densities from the supersystem MO coefficients.

References: Su & Li, J. Chem. Phys. 131, 014102 (2009); Morokuma, J. Chem. Phys. 55, 1236 (1971).

Orbital entanglement measures

Quantum-information analysis reads bonding from the entanglement structure of the wavefunction: the single-orbital entropy measures how strongly an orbital participates in correlation, and the mutual information between two orbitals quantifies their bonding entanglement. For single-determinant (HF/DFT) results vibe-qc offers an occupation-number approximation directly from the density matrix:

from vibeqc.entanglement import entanglement_from_density

ent = entanglement_from_density(P, S)
print("total quantum information:", ent.total_information)
print("correlation clusters:", ent.correlation_clusters)

For multi-determinantal wavefunctions (CAS, CC densities), feed the one- and two-orbital reduced density matrices to vibeqc.entanglement.single_orbital_entropy and vibeqc.entanglement.mutual_information directly.

References: Legeza & Sólyom, Phys. Rev. B 68, 195116 (2003); Szalay, Barcza, Szilvási, Veis & Legeza, Sci. Rep. 7, 2237 (2017); Ding, Matito & Schilling, arXiv:2501.15699 (2025).

Citations

Every method on this page carries its defining papers in the citation database: jobs that surface Wiberg / NPA data cite them automatically in the .out references block and the .bibtex / .references siblings, and the DOIs are pinned mechanically by tests/test_citations.py::test_bond_analysis_routes_pin_dois. See Citations for the full provenance surface.