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ΛUT⇒S−1/2=UΛ−1/2UT

Löwdin Charge Formula ​

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

P~=S−1/2PS−1/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 0≤P~μμ≤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.