Skip to content

Electronic-State Participation Ratio

The defectpl.participation_ratio module computes how much of each Kohn–Sham wavefunction is spatially concentrated on the defect neighbourhood. It reads site-projected wavefunction data from a VASP PROCAR file and pairs it with defect geometry from defect_entry.json.


Physical Background

Site-projected wavefunction character

For a Kohn–Sham state |ψ_{n,k,σ}⟩, VASP decomposes the wavefunction onto spherical harmonics centred at each atom i:

\[p_i(n,k,\sigma) = \sum_{lm} \left|\langle Y^{i}_{lm} \mid \psi_{n,k,\sigma}\rangle\right|^2\]

This is exactly the "tot" column for atom i in the PROCAR file.

Participation Ratio (P-ratio)

The P-ratio measures the fraction of the total wavefunction weight residing on the defect neighbourhood atoms:

\[\text{P-ratio}(n,k,\sigma) = \frac{\displaystyle\sum_{i \in \mathcal{N}} p_i}{\displaystyle\sum_{i} p_i}\]

where \(\mathcal{N}\) is the set of neighbouring-atom indices.

P-ratio Physical meaning
≈ 0 Delocalized — bulk host state
≈ 1 Fully localized on defect neighbours — deep defect level

States with P-ratio > 0.2 are typically classified as defect-localized (Kumagai et al., PRB 2021).

Inverse Participation Ratio (IPR)

The site-based IPR is a code-agnostic localization metric:

\[\text{IPR}(n,k,\sigma) = \frac{\displaystyle\sum_i p_i^2}{\left(\displaystyle\sum_i p_i\right)^2}\]
IPR value Physical meaning
\(1/N\) Perfectly delocalized over N atoms
1 Fully localized on a single atom

Required Files

File Required? Description
PROCAR Yes VASP projected wavefunction output. Needs LORBIT = 11 or 12 in INCAR.
defect_entry.json Yes Provides defect_name and defect_center. Generated by defectpl pr make-entry.
defect_structure_info.json Recommended Neighbour atom indices. Generated by defectpl pr make-dsi.
CONTCAR / POSCAR Fallback Used for distance-based neighbour search when DSI file is absent.

No pydefect needed

All prerequisite files can be generated directly with defectpl:

defectpl pr make-entry --name Va_O1_2 --center 0.5,0.5,0.5
defectpl pr make-dsi   --poscar CONTCAR --center 0.5,0.5,0.5 --cutoff 3.5

Workflow

1. Prepare VASP inputs

Add LORBIT = 11 (or 12) to your INCAR before running VASP:

LORBIT = 11

This ensures the PROCAR file contains full site projections.

2. Generate defect_entry.json

Option A — Manual (provide the defect centre directly)

defectpl pr make-entry \
    --name Va_O1_2 \
    --center 0.5,0.5,0.5

Option B — Auto-detect from perfect vs defect structure

defectpl pr make-entry \
    --name Va_O1_2 \
    --perfect ../perfect/POSCAR \
    --defect   CONTCAR

The command compares the two structures, finds the removed/added atom, and uses its position as the defect centre. Works for vacancies, interstitials, and substitutions.

defectpl pr make-dsi \
    --poscar CONTCAR \
    --center 0.5,0.5,0.5 \
    --cutoff 3.5

This writes a defect_structure_info.json containing the indices of all atoms within 3.5 Å of the defect centre.

4. Run the calculation

defectpl pr calc

All defaults are auto-detected. Explicit paths can be supplied:

defectpl pr calc \
    --procar  Va_O1_2/PROCAR \
    --entry   Va_O1_2/defect_entry.json \
    --dsi     Va_O1_2/defect_structure_info.json \
    --out     Va_O1_2/

Output files:

  • participation_ratio.json — full nested results
  • participation_ratio_summary.csv — flat table

Python API

High-level: ParticipationRatioCalculator

from defectpl.participation_ratio import ParticipationRatioCalculator

calc = ParticipationRatioCalculator(
    procar                = "PROCAR",
    defect_entry          = "defect_entry.json",
    defect_structure_info = "defect_structure_info.json",  # optional
    poscar                = "CONTCAR",                      # fallback
    cutoff_radius         = 3.5,                            # Å
)

result = calc.run()

# Serialise results
calc.to_json("participation_ratio.json")
calc.to_csv("participation_ratio_summary.csv")

# Query top-10 most localised states
for row in calc.top_localized(n=10, metric="p_ratio"):
    print(row["spin"], row["kpt"], row["band"],
          row["energy"], row["p_ratio"], row["ipr"])

Generating prerequisite files from Python

from defectpl.defect_utils import make_defect_entry, make_defect_structure_info

# Create defect_entry.json manually
make_defect_entry(
    name   = "Va_O1_2",
    center = [0.5, 0.5, 0.5],
)

# Auto-detect from structures
make_defect_entry(
    name           = "Va_O1_2",
    perfect_poscar = "../perfect/POSCAR",
    defect_poscar  = "CONTCAR",
)

# Create defect_structure_info.json
make_defect_structure_info(
    poscar             = "CONTCAR",
    defect_center_frac = [0.5, 0.5, 0.5],
    cutoff_radius      = 3.5,
)

Low-level functions

from defectpl.participation_ratio import (
    read_procar,
    compute_participation_ratios,
    neighbors_from_defect_structure_info,
    resolve_neighbors,
)

# Parse PROCAR (native parser — no extra dependencies)
procar_data = read_procar("PROCAR", use_pymatgen=False)

# Resolve neighbours from the best available source
indices, source = resolve_neighbors(
    dsi_path           = "defect_structure_info.json",
    poscar_path        = "CONTCAR",
    defect_center_frac = [0.5, 0.5, 0.5],
    cutoff_radius      = 3.5,
)

# Compute P-ratio and IPR
result = compute_participation_ratios(
    procar_data      = procar_data,
    neighbor_indices = indices,
    defect_name      = "Va_O1_2",
    defect_center    = [0.5, 0.5, 0.5],
)

Output Schema

participation_ratio.json:

{
  "defect_name": "Va_O1_2",
  "defect_center": [0.86, 0.86, 0.86],
  "neighbor_atom_indices": [3, 7, 11, 15],
  "n_atoms": 56,
  "n_spins": 1,
  "n_kpoints": 1,
  "n_bands": 224,
  "neighbor_source": "defect_structure_info.json (4 atoms)",
  "data": {
    "spin_1": {
      "kpt_1": {
        "band_112": {
          "energy":      -0.231,
          "occupation":   1.0,
          "p_ratio":      0.7812,
          "ipr":          0.031200,
          "p_neighbors":  0.768,
          "p_total":      0.983
        }
      }
    }
  }
}

participation_ratio_summary.csv columns: spin, kpt, band, energy, occ, p_ratio, ipr, p_neighbors, p_total


Neighbour Resolution Strategy

The module uses the best available source for neighbour indices, in order:

  1. defect_structure_info.json — reads keys neighbor_atom_indices, neighboring_atom_indices, or a neighbors list with index fields. Compatible with both defectpl-generated and pydefect-generated files.

  2. Distance-based search on POSCAR/CONTCAR — finds all atoms within cutoff_radius Å using the minimum-image convention.

  3. Empty list — if no structural information is available. All P-ratios are reported as 0 (only IPR remains meaningful).


CLI Reference

See the Command Line Interface page.

Quick reference:

# Single directory (auto-detect all paths)
defectpl pr calc

# Batch across charge-state subdirectories
defectpl pr batch --dir defects/Va_O1/

# Print summary of existing results
defectpl pr summary Va_O1_2/participation_ratio.json

# List top-10 most localised states by IPR
defectpl pr top Va_O1_2/participation_ratio.json --n 10 --metric ipr

Plotting

defectpl provides three dedicated plot functions for visualising P-ratio and IPR results. All functions return a matplotlib.Axes and work without a display (they use whatever matplotlib backend is active; the CLI forces Agg).

P-ratio / IPR vs energy

from defectpl.participation_ratio import plot_pr_vs_energy
import json

with open("participation_ratio.json") as fh:
    result = json.load(fh)

# Simple scatter — P-ratio vs energy
ax = plot_pr_vs_energy(result)

# With VBM/CBM markers and custom window
ax = plot_pr_vs_energy(
    result,
    metric    = "p_ratio",   # or "ipr"
    threshold = 0.2,          # dashed horizontal line
    vbm       = 5.20,         # orange vertical line
    cbm       = 8.10,         # green vertical line
    emin      = 4.0,          # filter: only bands with energy ≥ 4 eV
    emax      = 9.5,
    out       = "pr_energy.pdf",
)
Parameter Default Description
metric "p_ratio" Y-axis: "p_ratio" or "ipr".
threshold 0.2 Dashed horizontal reference line value.
vbm None Orange dotted vertical line at VBM.
cbm None Green dotted vertical line at CBM.
emin / emax None Energy window filter (eV).
kpt_idx 0 0-based k-point to plot.
out None Save file path. When None the axes is returned without saving.
ax None Inject into an existing matplotlib.Axes.

P-ratio / IPR vs band index

from defectpl.participation_ratio import plot_pr_vs_band_index

ax = plot_pr_vs_band_index(
    result,
    metric    = "p_ratio",
    threshold = 0.2,
    emin      = 4.0,      # restrict to bands with energy ≥ 4 eV
    emax      = 9.5,
    out       = "pr_band.png",
)

The band index (1-based, matching PROCAR band numbering) appears on the X-axis. Use emin/emax to show only the bands that fall within an energy window — useful for focusing on the gap region.

Kohn-Sham level plot with P-ratio colour code

from defectpl.vasp import read_eigenval_file
from defectpl.ks_analysis import extract_ksplot_data, plot_ks_with_pr
import json

eigenval_data = read_eigenval_file("EIGENVAL", k_idx=0)
ks_data       = extract_ksplot_data(eigenval_data, vbm=5.20, cbm=8.10, espan=1.5)

with open("participation_ratio.json") as fh:
    pr_result = json.load(fh)

plot_ks_with_pr(
    ks_data, pr_result,
    metric          = "p_ratio",     # or "ipr"
    cmap            = "RdYlGn_r",    # green = low, red = high localization
    vmin            = 0.0,
    vmax            = 1.0,
    output_filename = "ks_pr_plot.png",
)

Each horizontal bar in the level diagram is coloured by the P-ratio (or IPR) of that Kohn-Sham state. A continuous colorbar is added on the right.

Parameter Default Description
metric "p_ratio" Localization metric for colouring.
cmap "RdYlGn_r" Matplotlib colormap name.
vmin / vmax 0.0 / 1.0 Colormap bounds.
kpt_idx 0 0-based k-point index.
output_filename "ks_pr_plot.png" Destination file.
figsize (7, 6) Figure size in inches.
dpi 300 Image resolution.

Tips

  • Always use LORBIT = 11 (or 12) in INCAR so PROCAR contains full site projections.
  • For Gamma-point-only calculations (KPOINTS with a single k-point), n_kpoints = 1 and the output always shows kpt_1.
  • For spin-polarized calculations (ISPIN = 2) the output contains both spin_1 (majority) and spin_2 (minority) blocks.
  • The native PROCAR parser (--native-procar flag or use_pymatgen=False) works without a pymatgen installation and is faster for large files.
  • Use defectpl pr batch to process all charge states of a defect in one command.