Skip to content

Population Analysis (Mulliken & Löwdin)

Population analysis extracts per-atom partial charges from the molecular orbital coefficients produced by the EHT solver. sci-form implements both Mulliken and Löwdin partitioning schemes.

Overview

Given the density matrix P and overlap matrix S from the EHT solution, population analysis answers: how many electrons does each atom "own"?

Mulliken Population Analysis

Density Matrix

The density matrix is constructed from occupied molecular orbital coefficients:

Pμν=ioccniCμiCνi

where ni is the occupation number (2 for closed-shell doubly occupied orbitals) and Cμi is the coefficient of atomic orbital μ in molecular orbital i.

Mulliken Charge Formula

The Mulliken charge on atom A is:

qA=ZAμA(PS)μμ

where:

  • ZA is the nuclear charge (number of valence electrons)
  • (PS)μμ=νPμνSμν is the gross orbital population for orbital μ
  • The sum runs over all atomic orbitals μ centered on atom A

Charge Conservation

The total Mulliken charges always sum to the net molecular charge:

AqA=Qtotal

This follows from Tr(PS)=Nelectrons.

Löwdin Population Analysis

Symmetric Orthogonalization

Löwdin analysis first orthogonalizes the basis by diagonalizing the overlap matrix:

S=UΛUTS1/2=UΛ1/2UT

Löwdin Charge Formula

The Löwdin density matrix in the orthogonalized basis is:

P~=S1/2PS1/2

The Löwdin charge on atom A:

qA=ZAμAP~μμ

Advantages Over Mulliken

PropertyMullikenLöwdin
Basis-set dependenceStrong — charges shift with basis sizeWeaker — more stable
Orbital populationsCan be negativeAlways 0P~μμ2
Overlap handlingSplits 50/50 between atomsOrthogonalizes first
Rotational invarianceNoYes

API

Rust

rust
use sci_form::compute_population;

let result = compute_population("O", None);
// result.mulliken_charges: Vec<f64>
// result.lowdin_charges: Vec<f64>
// result.gross_populations: Vec<f64>
// result.bond_orders: Vec<Vec<f64>>

CLI

bash
sci-form population "O"
# Output: JSON with mulliken_charges, lowdin_charges, etc.

Python

python
import sci_form
result = sci_form.population("O")
print(result.mulliken_charges)  # [-0.33, 0.17, 0.17]

WASM

typescript
import { compute_population } from 'sci-form';
const result = compute_population("O");
// result.mulliken_charges, result.lowdin_charges

Validation

  • Charge conservation: iqi=Qtotal tested for all molecules
  • Electronegativity ordering: O charges < C charges < H charges (expected from EN)
  • Equivalence: Symmetry-equivalent atoms get equal charges (e.g., H atoms in CH₄)
  • Boundedness: Löwdin populations always in [0,2]

Released under the MIT License.