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)dE≈Norbitals
  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=1NorbwAi⋅G(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εi−5σ,Emax=maxiεi+5σ

Grid points are evenly spaced:

Ek=Emin+k⋅Emax−EminNgrid−1,k=0,…,Ngrid−1

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)=1N∑k=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.