Tutorial for Wannierizing Nb-Ti (mp-1216634). We will run VASP using pymatgen. For a theory overview of Wannier functions, see the [[wannier]] notes.

Regardless, make sure the folder structure is the same as the one stated here.

You can find some slides in here that explain what Wannier functions are, why they matter, and some of this tutorial (if further explanation needed)

Overview

In this tutorial you will run the following

StepTask
1Write the input files for SCF and NSCF (uniform and bands)
2Wannierization with SCDM using the result form Step 3, in atomate2
3Wannierization with LOCPROJ directly in the terminal
4MLWF post optimization of the generated wannier functions

Requirements

pymatgen2026.4.16
from pymatgen.core import __version__ as p_ver
print(f"pymatgen version {p_ver}")
pymatgen version 2026.4.16
from pymatgen.io.vasp.sets import MPStaticSet, MPNonSCFSet
from pymatgen.symmetry.bandstructure import HighSymmKpath
from pymatgen.core import Structure, Lattice
from pymatgen.io.vasp import Kpoints

Structure

a = 2.84
b = 2.84
c = 4.65

structure = Structure.from_spacegroup(
    "Cmmm",
    Lattice.orthorhombic(a,b,c),
    species=["Nb", "Ti"],
    coords =[ [0, 0, 0], [1/2, 1/2, 1/2] ]
).get_primitive_structure()

Updates

Wannierization works best when symmetry is turned off (helps with the gauge smoothness).

NSCF INCAR updates

ParameterValueReason
ISYM-1Symmetry might introduce extra gauge discontinuities
ALGONormal
LWAVETRUEBasis for Wannierizaton, will need to diagonalize $\hat{H}_{\rm KS}$ again

LOCPROJ INCAR updates

ParameterValueReason
LOCPROJ“1 2 : s s p d : Hy”Whichever projections we want to build orbitals from, see below for an explanation
NUM_WANNIGNOREDNUM_WANN is determined by the number of projectors in LOCPROJ
ALGOEigenval or NoneWe just need to recalculate the energies or post-process using the current orbitals

SCDM INCAR updates

ParameterValueReason
LSCDMTRUETurns on SCDM
LWANNIER90TRUETurns on Wannier90
LWRITE_MMN_AMNTRUEWrites .mmn and .amn files for wannier90.x inputs
ALGOEigenval or NoneWe just need to recalculate the energies or post-process using the current orbitals
CUTOFF_MU23.0SCDM cutoff position, value specific to this example (but can be automated). NOT w.r.t. Fermi energy
CUTOFF_SIGMA0.5SCDM cuttoff width
NUM_WANN20Number of Wannier functions (or bands)

The wannier90.amn and wannier90.mmn files contain the matrices $A_{mn}=\langle \psi_{n}| g_m\rangle $ and $M_{mn}=\langle \psi_{n}| \psi_{m}\rangle$ respectively. They are used as inputs for the MLWF procedure. The functions $g_m$ are initial projections in the LOCPROJ tag, or the SCDM orbitals.

Note that for SCDM, we are not bound by NUM_WANN, but for LOCPROJ we are bound by how many projectors we choose (e.g. a set of $s$ and $p$ orbitals have $1+3$ total bands in a non-spin polarized calculation).

For this examle, we choose $s$, $s$, $p$, $d$ to sum to 10 bands per element ($1+1+3+5$).

uniform_incar = {"ISYM": -1, "LWAVE": True, "NBANDS": 40}
scdm_incar    = {"ALGO": "None", "ISTART": 2, "LWANNIER90": True, "LWRITE_MMN_AMN": True, "NUM_WANN": 20, "LSCDM": True, "CUTOFF_MU": 23.0, "CUTOFF_SIGMA": 0.5}
locproj_incar = {"ALGO": "None", "ISTART": 2, "LWANNIER90": True, "LWRITE_MMN_AMN": True}

[!WARNING ]"" The LOCPROJ tag requires a multiline string, which current pymatgen cannot work with. For this reason, you must open the INCAR file in the directory and append the following

! NbTi/locproj/INCAR
! ...
LOCPROJ = "1 2 : s s p d : Hy"
kpath   = HighSymmKpath(structure)

kpts_scf     = Kpoints.gamma_automatic((4,4,4))
kpts_uniform = Kpoints.gamma_automatic((6,6,4))
kpts_line    = Kpoints.automatic_linemode(5, kpath)

scf_set     = MPStaticSet(structure=structure, user_kpoints_settings = kpts_scf,     user_potcar_functional="PBE_54")
uniform_set = MPNonSCFSet(structure=structure, user_kpoints_settings = kpts_uniform, user_potcar_functional="PBE_54",  user_incar_settings = uniform_incar)
line_set    = MPNonSCFSet(structure=structure, user_kpoints_settings = kpts_line,    user_potcar_functional="PBE_54",  user_incar_settings = uniform_incar)

scdm_set    = MPNonSCFSet(structure=structure, user_kpoints_settings = kpts_uniform, user_potcar_functional="PBE_54",  user_incar_settings = scdm_incar)
locproj_set = MPNonSCFSet(structure=structure, user_kpoints_settings = kpts_uniform, user_potcar_functional="PBE_54",  user_incar_settings = locproj_incar)
from pathlib import Path

sets  = [scf_set, uniform_set, line_set, locproj_set, scdm_set]
names = ["scf",  "uniform",   "line",   "locproj",   "scdm"]

for (_key, _set) in zip(names, sets):
    workdir = Path("NbTi", _key)
    workdir.mkdir(exist_ok=True, parents=True)

    _set.write_input(workdir)

The file-structure should look like the following

$ ls NbTi
line    locproj scdm    scf     uniform

Here is where you run VASP

(remember to edit the LOCPROJ tag by hand)

After running VASP copy the WAVECAR and CHGCAR files to the Wannierization directories

$ cp NbTi/uniform/{WAVECAR,CHGCAR} NbTi/scdm/
$ cp NbTi/uniform/{WAVECAR,CHGCAR} NbTi/locproj/

Notes on LOCPROJ

The local orbitals are defined as sites : angular character : radial character, and so 1 2 : s s p d: Hy means “project onto two sets of $s$, one set of $p$ and $d$ Hydrogenic orbitals for both elements in sites 1 and 2 of the POSCAR”.

The multiple specifications in LOCPROJ requires a semicolon or a multiline string, which will cause problems anytime Custodian or pymatgen wants to edit/pase the INCAR file. This may result in a failed vasprun.xml.