Skip to content

Density of States (DOS) and Projected DOS

The density of states (DOS) describes how many electronic states exist at each energy level. sci-form computes DOS from EHT orbital energies using Gaussian smearing.

DOS via Gaussian Smearing

Total DOS Formula

Given a set of orbital energies {εi} from the EHT solver, the density of states at energy E is:

D(E)=i=1NorbG(Eεi,σ)

where G is a Gaussian kernel:

G(x,σ)=1σ2πexp(x22σ2)

Parameters

ParameterSymbolTypical RangeDefault
Smearing widthσ0.1–0.5 eV0.3 eV
Energy grid pointsNgrid500–20001000
Energy range[Emin,Emax]Auto from eigenvalues ± 5σAuto

Properties

  1. Integral: D(E)dENorbitals
  2. Non-negative: D(E)0 for all E
  3. σ dependence: Smaller σ → sharper peaks near eigenvalue positions

Projected DOS (PDOS)

PDOS decomposes the total DOS into contributions from individual atoms, revealing which atoms contribute to states at each energy.

PDOS Formula

The projected density of states for atom A at energy E:

DA(E)=i=1NorbwAiG(Eεi,σ)

where the Mulliken weight for atom A in orbital i is:

wAi=μA|Cμi|2

PDOS Sum Rule

The atom-projected contributions always sum to the total DOS:

ADA(E)=D(E)E

This is guaranteed because AwAi=1 for each orbital i (the coefficients squared sum to 1 in the orthonormal basis).

Energy Grid

The energy grid is constructed automatically:

Emin=miniεi5σ,Emax=maxiεi+5σ

Grid points are evenly spaced:

Ek=Emin+kEmaxEminNgrid1,k=0,,Ngrid1

API

Rust

rust
use sci_form::compute_dos;

let result = compute_dos("O", 0.3, 1000, None);
// result.energies: Vec<f64> — energy grid points
// result.total_dos: Vec<f64> — D(E) values
// result.pdos: Vec<Vec<f64>> — per-atom PDOS
// result.eigenvalues: Vec<f64> — orbital energies

CLI

bash
sci-form dos "O" --sigma 0.3 --grid-points 1000
# Output: JSON with energies, total_dos, pdos arrays

Python

python
import sci_form
result = sci_form.dos("O", sigma=0.3, grid_points=1000)
print(len(result.energies))   # 1000
print(len(result.total_dos))  # 1000

Validation

  • Grid match: len(energies) = len(total_dos) = grid_points
  • Non-negative: All DOS values ≥ 0
  • PDOS sum: Sum of PDOS arrays equals total DOS at each energy point
  • σ effect: Narrower σ produces sharper, taller peaks
  • Energy range: Grid spans all eigenvalues with sufficient margin

DOS Comparison (MSE)

Two DOS curves can be quantitatively compared using the Mean Squared Error:

MSE(DA,DB)=1Nk=1N(DA(Ek)DB(Ek))2

This is used for benchmarking and to compare computed DOS against reference spectra. Both curves must be on the same energy grid (same length).

rust
use sci_form::dos::{compute_dos, dos_mse};

let res_a = compute_dos("O", 0.3, 1000, None)?;
let res_b = compute_dos("O", 0.5, 1000, None)?;  // different sigma
let mse = dos_mse(&res_a.total_dos, &res_b.total_dos);
println!("MSE = {mse:.6}");

JSON Export for Web Visualization

The export_dos_json function serializes a DosResult into a compact JSON string suitable for sending to a browser or plotting library:

json
{
  "energies": [-35.2, -34.8, ..., 5.0],
  "total_dos": [0.0, 0.12, ..., 0.0],
  "sigma": 0.3,
  "pdos": {
    "0": [0.0, 0.08, ...],
    "1": [0.0, 0.04, ...]
  }
}
rust
use sci_form::dos::{compute_dos, export_dos_json};

let result = compute_dos("c1ccccc1", 0.3, 1000, None)?;
let json = export_dos_json(&result);
// Send to frontend over WebSocket or WASM call

The PDOS dictionary keys are atom indices (0-based). This format is consumed directly by the TypeScript WASM API:

typescript
const result = JSON.parse(compute_dos("c1ccccc1", 0.3, 1000));
// result.energies: number[]
// result.total_dos: number[]
// result.pdos: { [atomIndex: string]: number[] }

Released under the MIT License.