Bond analysis: Wiberg, NBO, EDA, IBOs, 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, the provisional, exploratory NBO helper classifies orbitals, EDA splits an interaction energy into physical components, and entanglement measures read bonding straight off the quantum-information structure of the wavefunction. Bond orders, EDA and the entanglement measures are production on HF and DFT densities; NPA is gated and the NBO search provisional (see Methods: theory and status).
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 are computed automatically on every population dump.
The .population.{txt,json} sidecars retain an NPA field, but it is empty
and reports unavailable.npa until the full occupancy-weighted
Natural Atomic Orbital construction is implemented. This prevents a
Löwdin population from being reported under the NPA label. The NBO search
is a provisional, exploratory Python helper; it, the EDA and the
entanglement analyses are Python-API workflows you call on an SCF result.
Theory¶
All four families read the same converged one-particle density matrix \(\mathbf P\) (spin-summed, \(\mathbf P = \mathbf P^\alpha + \mathbf P^\beta\) for open shells) and the AO overlap \(\mathbf S\); what distinguishes them is which contraction they take and in which basis.
Bond orders. The Mayer bond order (Mayer, I. (1983). *Charge, bond order and valence in the ab initio SCF theory.* Chemical Physics Letters, **97**(3), 270–274. doi:[10.1016/0009-2614(83)80005-0](https://doi.org/10.1016/0009-2614(83)80005-0)) contracts through the overlap in the raw AO basis,
for a closed shell (open shells use \(2[(\mathbf P^\alpha\mathbf S)(\mathbf P^\alpha\mathbf S) + (\mathbf P^\beta\mathbf S)(\mathbf P^\beta\mathbf S)]\) summed the same way), while the Wiberg index (Wiberg, Kenneth B. (1968). *Application of the Pople-Santry-Segal CNDO method to the cyclopropylcarbinyl and cyclobutyl cation and to bicyclobutane.* Tetrahedron, **24**(3), 1083–1096. doi:[10.1016/0040-4020(68)88057-3](https://doi.org/10.1016/0040-4020(68)88057-3)) squares the elements of the density in the symmetrically orthogonalised (Loewdin) basis, \(\tilde{\mathbf P} = \mathbf S^{1/2}\mathbf P\mathbf S^{1/2}\):
Both count shared electron pairs, both give 1 for H2 in a minimal basis, and both depend on the basis because “atom A” means “the functions centred on A”; the Loewdin transformation makes the Wiberg index the less sensitive of the two. The delocalisation index of QTAIM (Matito, Eduard, Solà, Miquel, et al. (2007). *Electron sharing indexes at the correlated level. Application to aromaticity calculations.* Faraday Discussions, **135**, 325–345. doi:[10.1039/B605086G](https://doi.org/10.1039/B605086G), Outeiral, Carlos, Vincent, Mark A., et al. (2018). *Revitalizing the concept of bond order through delocalization measures in real space.* Chemical Science, **9**, 5517–5529. doi:[10.1039/C8SC01338A](https://doi.org/10.1039/C8SC01338A)) is defined without a basis, as the double integral of the exchange-correlation density over two atomic basins, which for a single determinant reduces to basin overlaps of the occupied orbitals:
vibe-qc has no basin integration yet (QTAIM guide), so
delocalization_index replaces the basin overlaps by an AO partition,
\(\delta^{\mathrm{AO}}_{AB} = 2\sum_{\mu\in A,\nu\in B}(\mathbf P\mathbf S)_{\mu\nu}(\mathbf P\mathbf S)_{\nu\mu} - \sum_{\mu\in A,\nu\in B}\tilde P_{\mu\nu}^{\,2}\).
That is an approximation with no positivity guarantee: on H2O/STO-3G it
returns \(\delta(\mathrm O,\mathrm H) = -0.17\) where the Wiberg index is
0.98 and the Mayer order 0.94 (vibe-qc 0.17.x development build), so read
it as a diagnostic beside the two bond orders, not as a QTAIM number.
Energy decomposition. LMO-EDA (Su, Peifeng and Li, Hui (2009). *Energy decomposition analysis of covalent bonds and intermolecular interactions.* Journal of Chemical Physics, **131**(1), 014102. doi:[10.1063/1.3159673](https://doi.org/10.1063/1.3159673)) and the Kitaura-Morokuma scheme (Morokuma, Keiji (1971). *Molecular orbital studies of hydrogen bonds. III. C=O…H-O hydrogen bond in H2CO…H2O and H2CO…2H2O.* Journal of Chemical Physics, **55**(3), 1236–1244. doi:[10.1063/1.1676210](https://doi.org/10.1063/1.1676210)) write the interaction energy of two fragments as a sum of steps from the isolated fragments, through the frozen promolecule and the frozen orthogonalised state, to the relaxed dimer,
The terms are built from the fragment densities and the AO integrals and sum to \(\Delta E_{\mathrm{int}}\) exactly; the EDA section below defines each of them, gives the call signature and shows a counterpoise water-dimer example.
Orbital entanglement. Tracing a normalised wavefunction over all but one spatial orbital leaves a \(4\times4\) one-orbital reduced density matrix in the Fock-space basis \(\{|0\rangle, |\!\uparrow\rangle, |\!\downarrow\rangle, |\!\uparrow\downarrow\rangle\}\) with eigenvalues \(\omega_{i,\alpha}\); its von Neumann entropy and the mutual information of two orbitals (Legeza, Ors and Solyom, Jeno (2003). *Optimizing the density-matrix renormalization group method using quantum information entropy.* Physical Review B, **68**(19), 195116. doi:[10.1103/PhysRevB.68.195116](https://doi.org/10.1103/PhysRevB.68.195116), Szalay, Szilárd, Barcza, Gergely, et al. (2017). *The correlation theory of the chemical bond.* Scientific Reports, **7**, 2237. doi:[10.1038/s41598-017-02447-z](https://doi.org/10.1038/s41598-017-02447-z)) are
with \(s_{ij}\) the entropy of the \(16\times16\) two-orbital density. \(s_i\)
vanishes for an orbital that is empty or doubly occupied in every
determinant and reaches \(\ln 4\) at maximal entanglement, so it measures how
strongly an orbital takes part in correlation; \(I_{ij}\) is large for the
pairs that must be correlated together, which makes it an active-space
selector and, in the maximally entangled atomic-orbital picture
(Ding, Lexin, Matito, Eduard, and Schilling, Christian (2026). *Chemical bonding concepts emerge naturally from maximally entangled atomic orbitals.* Nature Communications, **17**(1), 4732. doi:[10.1038/s41467-026-73527-w](https://doi.org/10.1038/s41467-026-73527-w)), a bonding index. For a single
determinant the one-orbital density is diagonal in the natural occupations
\(n_i\) and \(s_i = -n_i\ln n_i - (1-n_i)\ln(1-n_i)\), which is what
entanglement_from_density evaluates from the eigenvalues of the
orthogonalised density, estimating \(I_{ij}\) from products of fractional
occupations. That shortcut is only informative for densities with
fractional natural occupations (DFT, broken-symmetry UHF, correlated
1-RDMs); for a correlated wavefunction feed genuine one- and two-orbital
RDMs to single_orbital_entropy and mutual_information. See the caveat
in the entanglement section below about which orthogonalisation the
shortcut applies.
Intrinsic bond orbitals (Knizia, Gerald (2013). *Intrinsic Atomic Orbitals: An Unbiased Bridge between Quantum Theory and Chemical Concepts.* Journal of Chemical Theory and Computation, **9**(11), 4834–4843. doi:[10.1021/ct400687b](https://doi.org/10.1021/ct400687b)) and the natural-population gate (Reed, Alan E., Weinstock, Robert B., and Weinhold, Frank (1985). *Natural population analysis.* Journal of Chemical Physics, **83**(2), 735–746. doi:[10.1063/1.449486](https://doi.org/10.1063/1.449486)) are described in their own sections below.
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).
NPA gate and the provisional NBO search¶
Natural Population Analysis derives atomic charges from Natural
Atomic Orbital occupations. A global symmetric orthogonalization alone
produces Löwdin populations, not NPA. npa_charges therefore raises
NotImplementedError until vibe-qc implements the complete
occupancy-weighted NAO construction. Population sidecars preserve the npa
key as an empty list and explain the gate in unavailable.npa. They skip
the unimplemented API, so this limitation does not appear as a failed
calculation in errors. The text section says npa: not implemented.
The separate NBO search is a provisional, exploratory Python helper and remains available:
import numpy as np
from vibeqc.nbo import nbo_search, donor_acceptor_analysis
from vibeqc._vibeqc_core import compute_overlap
S = np.asarray(compute_overlap(basis))
P = np.asarray(result.density)
# Provisional NBO classification (BD / LP / CR / BD* / RY*):
nbos = nbo_search(P, S, basis, mol)
print(nbos.summary())
donor_acceptor_analysis adds second-order perturbative E(2)
stabilisation energies between donor and acceptor NBOs when you also
supply the Fock matrix in the orthogonalised basis.
References: Reed, Weinstock & Weinhold, J. Chem. Phys. 83, 735 (1985); Foster & Weinhold, J. Am. Chem. Soc. 102, 7211 (1980); Reed, Curtiss & Weinhold, Chem. Rev. 88, 899 (1988).
Energy decomposition analysis (EDA)¶
Status: preview. The terms are real and add up to the interaction energy,
for two closed-shell Hartree-Fock fragments only; other inputs are refused.
The Kohn-Sham form, open-shell fragments and a run_job keyword are not
implemented. The label vocabulary is in
Methods: theory and status.
EDA answers “why do these two fragments bind” by partitioning the
interaction energy of two closed-shell fragments into electrostatic,
exchange, Pauli-repulsion, polarisation and dispersion terms.
vibeqc.eda.eda_lmo implements the LMO-EDA partition of Su and Li (2009)
and vibeqc.eda.eda_morokuma the Kitaura-Morokuma partition (1971, 1976) of
the same states, both as post-processing of three converged Hartree-Fock
runs: the dimer and the two fragments, each fragment computed in the
dimer basis so that the interaction energy is counterpoise corrected.
vibe-qc has no ghost-atom keyword, but a Molecule accepts atoms with
Z = 0, and the explicit-shell BasisSet constructor puts the dimer’s
shells on them. The default SAD guess has no atomic density for Z = 0,
so the fragment runs start from the core Hamiltonian.
import vibeqc as vq
from vibeqc.eda import eda_lmo
ANG = 1.8897259886 # Angstrom to bohr
xyz = [ # water dimer, R(O..O) = 2.95 A
(8, (0.000, 0.000, 0.000)), (1, (0.000, 0.000, 0.960)), (1, (0.000, 0.929, -0.240)),
(8, (0.000, 0.000, 2.950)), (1, (0.000, 0.759, 3.538)), (1, (0.000, -0.759, 3.538)),
]
frag1, frag2 = [0, 1, 2], [3, 4, 5]
def molecule(real):
"""The dimer with every atom outside `real` turned into a ghost (Z = 0)."""
return vq.Molecule([vq.Atom(z if i in real else 0, [c * ANG for c in p])
for i, (z, p) in enumerate(xyz)], 0, 1)
dimer = molecule(frag1 + frag2)
basis = vq.BasisSet(dimer, "6-31g*")
shells = basis.shells()
def scf(mol):
opts = vq.RHFOptions()
if any(a.Z == 0 for a in mol.atoms):
opts.initial_guess = vq.InitialGuess.HCORE # SAD has no density for Z = 0
return vq.run_rhf(mol, vq.BasisSet(mol, shells, name="6-31g*"), opts)
return vq.run_rhf(mol, basis, opts)
ab, a, b = scf(dimer), scf(molecule(frag1)), scf(molecule(frag2))
eda = eda_lmo(
ab.energy, a.energy, b.energy,
None, None, None, # Fock matrices: accepted, not used
ab.density, a.density, b.density,
vq.compute_overlap(basis),
basis=basis, molecule=dimer, frag1_atoms=frag1, frag2_atoms=frag2,
)
print(eda.summary())
Energy Decomposition Analysis (LMO-EDA)
----------------------------------------------------------------
E_total (supersystem) = -152.02576771 E_h
E_fragment_1 (isolated) = -76.00957528 E_h
E_fragment_2 (isolated) = -76.00960659 E_h
----------------------------------------------------------------
dE_int (total) = -0.00658583 E_h -4.13 kcal/mol
Electrostatic = -0.01255786 E_h -7.88 kcal/mol
Exchange = -0.01136365 E_h -7.13 kcal/mol
Repulsion (Pauli) = 0.01991724 E_h 12.50 kcal/mol
Polarisation (+CT) = -0.00258156 E_h -1.62 kcal/mol
----------------------------------------------------------------
The partition follows one path: the two fragments, the promolecule
(P_1 + P_2, the fragment orbitals as they are), the frozen state (one
determinant of the mutually orthogonalised occupied orbitals), and the
relaxed dimer.
Term |
Definition |
|---|---|
|
Classical Coulomb energy of the two frozen charge distributions, nuclei included. |
|
Inter-fragment exchange energy of the promolecule state (LMO-EDA). |
|
Pauli repulsion beyond exchange: the frozen state minus the promolecule. |
|
Converged dimer minus frozen state: polarisation and charge transfer together. |
|
Difference of additive dispersion corrections, if you pass them ( |
The five terms add up to e_int = e_total - e_frag1 - e_frag2 exactly.
eda_morokuma reports the Kitaura-Morokuma terms of the same states:
e_exch is then the full exchange repulsion EX (the sum of the LMO-EDA
exchange and repulsion, +5.37 kcal/mol here), e_rep is zero, and
e_pol is polarisation, charge transfer and coupling as one number. The
restricted SCF variations that separate those three are not implemented.
What to expect: the electrostatic attraction of a hydrogen bond is larger than the binding energy, Pauli repulsion cancels most of it, and relaxation adds a smaller stabilisation. Only the sum is an observable; the split depends on the chosen intermediate states, and the polarisation and charge-transfer terms are not basis-set stable. Compare them within one basis.
Scope, enforced by the code. The terms are functionals of the fragment
densities, so eda_lmo needs the integrals and takes basis, molecule
and the atom indices of each fragment beside the SCF results. It evaluates
the Hartree-Fock energy expression on the supplied densities and refuses,
with a ValueError, energies that do not match it to
consistency_tol (1e-6 Hartree): Kohn-Sham energies, ECP runs and
fragments computed in another basis all fail here rather than return
wrong numbers. It also refuses densities that are not closed-shell SCF
densities. That excludes fragment_density_matrix, which returns a
projection of the dimer orbitals and not a fragment density; use the
density of a fragment run in the dimer basis. Open-shell fragments and the
Kohn-Sham form of LMO-EDA are not implemented. The Fock-matrix arguments
of the older signature are still accepted and are ignored.
References: Su & Li, J. Chem. Phys. 131, 014102 (2009); Morokuma, J. Chem. Phys. 55, 1236 (1971); Kitaura & Morokuma, Int. J. Quantum Chem. 10, 325 (1976).
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. vibe-qc offers an occupation-number approximation directly from a spin-restricted, spin-summed AO 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)
The density is transformed as \(S^{1/2} P S^{1/2}\), using the same Loewdin convention as Wiberg bond orders. Its eigenvalues are the spin-summed natural occupations in \([0, 2]\). The binary entropy uses per-spin occupations \(n_i\), obtained by dividing those eigenvalues by two: \(s_i = -n_i\ln n_i - (1-n_i)\ln(1-n_i)\). Closed-shell RHF densities give zero entropy, mutual information and entanglement bond orders; fractional occupations, such as those of stretched H2 CASCI, give positive occupation entropy. Endpoint deviations within \(10^{-14}\) per spin are treated as eigensolver roundoff.
Warning
entanglement_from_density reports an occupation-based approximation.
Its mutual information and bond orders are heuristic estimates; a
one-particle density alone does not determine full orbital entanglement.
Zero entropy for a closed-shell determinant refers to its natural orbitals,
not to an arbitrary choice of localized orbitals. Supply the spin-summed
density with Tr(P @ S) equal to the electron count.
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, Nat. Commun. 17, 4732 (2026),
DOI 10.1038/s41467-026-73527-w.
Intrinsic bond orbitals (IBOs) and IAO charges¶
The methods above all read scalars off the density matrix. IBOs answer the complementary question – what do the bonds look like – by rotating the occupied orbitals into a set that reproduces the Lewis structure: one orbital per bond, core, or lone pair, with no Lewis pattern assumed anywhere in the construction.
import vibeqc as vq
# On by default for closed-shell molecular jobs: writes one localized
# wavefunction section per criterion for vibe-view to switch between.
res = vq.run_job(molecule=mol, basis="def2-svp", method="rhf")
res = vq.run_job(..., localize=False) # off
res = vq.run_job(..., localize="ibo") # just one criterion
res = vq.run_job(..., localize=["ibo", "boys"])
Three criteria, because they disagree¶
The default emits IBO, Foster-Boys and Pipek-Mezey. That is not redundancy: the criteria answer the same question differently, and on benzene they visibly part company.
benzene/def2-SVP |
1-centre |
2-centre |
3-centre |
|---|---|---|---|
IBO |
6 |
12 |
3 (the pi system) |
Foster-Boys |
6 |
15 |
0 |
Pipek-Mezey |
6 |
12 |
3 |
Boys maximises the orbital dipole spread and so mixes sigma with pi, turning the aromatic pi system into three more two-centre “banana” bonds. IBO and Pipek-Mezey maximise atomic populations and keep sigma and pi apart. Neither picture is wrong; which one you want depends on the question. Comparing them is the point.
Whichever criterion runs, the descriptors – atomic populations, centre counts, charges – are computed on the IAO yardstick, so the numbers are directly comparable across the three. The IAO charges are identical for all three by construction: localization is a unitary rotation inside the occupied space, and the charges are a property of that space.
Or directly, on a converged occupied set:
from vibeqc.iao import analyse_ibo
analysis = analyse_ibo(mol, basis, C_occ)
print(analysis.charges) # IAO partial charges, one per atom
print(analysis.n_centres) # 1 = core/lone pair, 2 = bond, 3+ = delocalised
print(analysis.atom_populations) # n_A(i); each row sums to 1
print(analysis.centroids) # <i|r|i> in bohr
For methane this gives one carbon core plus four equivalent C-H bonds; for benzene, six cores, twelve two-centre sigma bonds, and a pi system that genuinely cannot be squeezed onto two centres.
IAO charges instead of Mulliken. The partial charges that come with
this are basis-set stable in a way Mulliken charges are not – the CH4
carbon moves only from -0.47 to -0.50 between def2-SVP and cc-pVTZ. That
stability is the reason IBOs are built on IAO populations rather than the
Mulliken populations that Pipek-Mezey uses, and it is why
vibeqc.localise.pipek_mezey_localise is not the right tool for
interpretation even though it is available (it exists for DLPNO domain
construction, where the criterion is cost, not physical meaning).
The difference is not subtle. On the same CH4/def2-SVP wavefunction,
Mulliken puts -0.144 on carbon where IAO puts -0.473. When localize="ibo"
is set, the IAO charges are written into the QVF’s atom_properties
section alongside the Mulliken, Löwdin and Hirshfeld rows, so vibe-view’s
charge picker offers them directly.
Use iao_analysis=True to request IAO charges, spin populations and
spin-resolved IAO-Wiberg bonds independently of localization or QVF. The
requested section is then included in .out and the population text/JSON
sidecars. See IAO population analysis for its definition,
API, support limits and periodic integration plan.
Scope and cost
Closed-shell molecular SCF only. Measured against the SCF that produced
them, all three criteria together cost about +16 % on a typical small
job (H2O/def2-TZVPP) and +62 % on benzene/def2-SVP. Nearly all of the
aromatic case is Foster-Boys, whose p=2 functional converges slowly on
degenerate pi systems – ~89 Jacobi sweeps against IBO’s 14. If that matters
for a batch, localize="ibo" keeps the cheapest and most interpretable one.
Jobs using ECPs are refused – an all-electron minimal reference basis cannot partition a valence-only target basis – as are elements past Z=86. In each case the job completes and warns; only the localized section is omitted.
Reference: Knizia, J. Chem. Theory Comput. 9, 4834 (2013), doi:10.1021/ct400687b. The minimal reference basis is Huzinaga MINI (Huzinaga et al., Gaussian Basis Sets for Molecular Calculations, Elsevier 1984).
Citations¶
Every method on this page carries its defining papers in the citation
database. The DOIs are pinned mechanically by
tests/test_citations.py::test_bond_analysis_routes_pin_dois. See
Citations for the full provenance surface.