ShakeNBreak for Polarons#

ShakeNBreak applied to polarons in CuSbSe₂#

In this tutorial, we use ShakeNBreak (SnB) to apply bond distortions and rattling to aid location of small polaron geometries in CuSbSe₂ (a candidate solar cell material). In this example case, we will:

  • Apply rattling and bond distortions to atoms to break symmetry and favour polaronic localisation.

  • Add an electron/hole (using NELECT) and an unpaired spin (NUPDOWN=1) to the system.

  • Generate a set of VASP input files to relax and post-process.

Reference:

Note

Searching for stable distorted polaron geometries in this way is very similar to the original bond distortion method introduced by Deskins et al. (upon which much of the defect structure-searching methodology in ShakeNBreak was inspired), with some additional benefits from partially-constrained Monte Carlo atomic rattling to break symmetry and further favour lower-energy localised solutions.

Please cite the original bond distortion method if using this approach to search for polaron structures!

Rationale for bond distortion workflow with polarons#

Polarons, much like point defects, often result in atom-centred structural distortions along with a change in the local charge density. As there may be a finite energy barrier between the undistorted free-electron case and distorted polaronic groundstate, it is usually necessary to perturb the system in some way before it will fall into a localised groundstate (if one exists).

from pymatgen.core.structure import Structure

bulk_supercell = Structure.from_file("CuSbSe2_POSCAR")  # load our bulk supercell structure

Simple Approach: Rattling#

For the most simple first step, we can apply atomic rattling (random perturbation of all atomic positions using a bond-length-dependent Gaussian distribution with a Monte Carlo algorithm; see docstring) to break the symmetry of the system and disrupt the long-range lattice potential, which often can be sufficient to identify a localised polaronic groundstate – particularly in cases of strong trapping.

from shakenbreak.distortions import rattle

rattled_supercell = rattle(bulk_supercell)
# note there are many options we can use with ``rattle()`` to alter the rattling process
# (see https://shakenbreak.readthedocs.io/en/latest/shakenbreak.distortions.html#shakenbreak.distortions.rattle; e.g. ``stdev``)
# but the default settings are usually good for the vast majority of cases

Bond Distortions#

In many cases however, rattling will not be sufficient on its own to locate polaronic geometries, and so we can apply targeted bond distortions to specific lattice sites to encourage localisation, using the distortions code in ShakeNBreak.

First, let’s determine the different symmetry-inequivalent sites in our material. We can do this using the SpacegroupAnalyzer class in pymatgen:

from pymatgen.symmetry.analyzer import SpacegroupAnalyzer

sga = SpacegroupAnalyzer(bulk_supercell)
sga.get_symmetrized_structure()  # 1 symmetry-inequivalent Cu site, 1 Sb site, 2 Se sites
SymmetrizedStructure
Full Formula (Cu16 Sb16 Se32)
Reduced Formula: CuSbSe2
Spacegroup: Pnma (62)
abc   :  10.334277  10.334277  16.265031
angles:  75.639685  75.639685 102.667344
Sites (64)
  #  SP           a         b         c  Wyckoff
---  ----  --------  --------  --------  ---------
  0  Cu    0.603183  0.978183  0.670123  16c
  1  Sb    0.727246  0.102246  0.442623  16c
  2  Se    0.287447  0.412448  0.175795  16c
  3  Se    0.047292  0.422292  0.403673  16c

Or we can quickly use doped to do this as well, which generates vacancies/antisites at each inequivalent site (and thus shows 1 symmetry-inequivalent Cu site, 1 Sb site and 2 Se sites also):

from doped.generation import DefectsGenerator

defect_gen = DefectsGenerator(  # no interstitials, don't generate supercell
    bulk_supercell, interstitial_gen_kwargs=False, generate_supercell=False
)
Generating DefectEntry objects: 100.0%|██████████| [00:04,  21.82it/s]
Vacancies       Guessed Charges    Conv. Cell Coords    Wyckoff
--------------  -----------------  -------------------  ---------
v_Cu            [+1,0,-1]          [0.248,0.250,0.170]  4c
v_Sb            [+1,0,-1,-2,-3]    [0.272,0.750,0.443]  4c
v_Se_Cs_Cu2.41  [+2,+1,0,-1]       [0.376,0.250,0.324]  4c
v_Se_Cs_Cu2.44  [+2,+1,0,-1]       [0.127,0.250,0.596]  4c

Substitutions    Guessed Charges              Conv. Cell Coords    Wyckoff
---------------  ---------------------------  -------------------  ---------
Cu_Sb            [0,-1,-2]                    [0.272,0.750,0.443]  4c
Cu_Se_Cs_Cu2.41  [+4,+3,+2,+1,0]              [0.376,0.250,0.324]  4c
Cu_Se_Cs_Cu2.44  [+4,+3,+2,+1,0]              [0.127,0.250,0.596]  4c
Sb_Cu            [+4,+3,+2,+1,0,-1,-2,-3,-4]  [0.248,0.250,0.170]  4c
Sb_Se_Cs_Cu2.41  [+7,+6,+5,+4,+3,+2,+1,0,-1]  [0.376,0.250,0.324]  4c
Sb_Se_Cs_Cu2.44  [+7,+6,+5,+4,+3,+2,+1,0,-1]  [0.127,0.250,0.596]  4c
Se_Cu            [+3,+2,+1,0,-1,-2,-3]        [0.248,0.250,0.170]  4c
Se_Sb            [+1,0,-1,-2,-3,-4,-5]        [0.272,0.750,0.443]  4c

The number in the Wyckoff label is the site multiplicity/degeneracy of that defect in the conventional ('conv.') unit cell, which comprises 4 formula unit(s) of CuSbSe2.

Hole/electron polarons will typically localise in the orbitals which make up the VBM/CBM of the material, as these are (initially) the lowest energy states available for these charge carriers.

In CuSbSe₂, the VBM is primarily composed of Se p and Cu d orbitals, so we will target Se-centred and Cu-centred distortions for a hole polaron example here.

First we get the atomic site we’re going to distort around:

Cu_site_idx = bulk_supercell.sites.index(defect_gen["v_Cu_0"].defect_supercell_site)

Then we can generate the distorted structures. Let’s try +/-40% bond length distortions (i.e. expansion and contraction) around this lattice site:

from shakenbreak.distortions import distort_and_rattle

trial_hole_polaron_supercells = {f"Cu_{distortion_factor:+.1%}": distort_and_rattle(
    structure=bulk_supercell,
    site_index=Cu_site_idx,
    num_nearest_neighbours=4,  # let's distort the 4 nearest neighbours
    distortion_factor=distortion_factor,
)["distorted_structure"] for distortion_factor in [-0.4, 0.4]}

And for Se-centred distortions:

# Se sites: v_Se_Cs_Cu2.41 & v_Se_Cs_Cu2.44
Se_Cs_Cu2pt41_site_idx = bulk_supercell.sites.index(defect_gen["v_Se_Cs_Cu2.41_0"].defect_supercell_site)
Se_Cs_Cu2pt44_site_idx = bulk_supercell.sites.index(defect_gen["v_Se_Cs_Cu2.44_0"].defect_supercell_site)
trial_hole_polaron_supercells.update({f"Se_Cs_Cu2.41_{distortion_factor:+.1%}": distort_and_rattle(
    structure=bulk_supercell,
    site_index=Se_Cs_Cu2pt41_site_idx,
    num_nearest_neighbours=4,  # let's distort the 4 nearest neighbours
    distortion_factor=distortion_factor,
)["distorted_structure"] for distortion_factor in [-0.4, 0.4]})
trial_hole_polaron_supercells.update({f"Se_Cs_Cu2.44_{distortion_factor:+.1%}": distort_and_rattle(
    structure=bulk_supercell,
    site_index=Se_Cs_Cu2pt44_site_idx,
    num_nearest_neighbours=4,  # let's distort the 4 nearest neighbours
    distortion_factor=distortion_factor,
)["distorted_structure"] for distortion_factor in [-0.4, 0.4]})

We can then write these structures to file to run VASP polaron-searching calculations on them:

for name, structure in trial_hole_polaron_supercells.items():
    structure.to(fmt="POSCAR", filename=f"{name}_POSCAR")
!ls *_POSCAR  # check the POSCARs have been written
CuSbSe2_POSCAR             Se_Cs_Cu2.41_-40.0%_POSCAR
Cu_+40.0%_POSCAR           Se_Cs_Cu2.44_+40.0%_POSCAR
Cu_-40.0%_POSCAR           Se_Cs_Cu2.44_-40.0%_POSCAR
Se_Cs_Cu2.41_+40.0%_POSCAR

Input Files#

With our trial distorted supercells, we can then add an electron/hole to the system by increasing/decreasing NELECT in the INCAR by 1 (relative to a neutral cell calculation), and setting NUPDOWN=1 to constrain the unpaired spin.

The DopedDictSet/DefectDictSet classes from doped.vasp can be used to do this through python, or of course you can manually generate & edit INCAR, KPOINTS and POTCAR files for this.

The NELECT value for a neutral cell calculation can be obtained from the OUTCAR of a previous neutral cell / bulk calculation (or by momentarily running VASP without specifying NELECT and looking for NELECT in the OUTCAR), or by summing the number of valence electrons of each element in the POTCAR times their atom count in the supercell.

We would then run VASP relaxations for these structures, to see if localised polarons are obtained. For this, we would typically compare the final energies to that of a single-shot calculation (no ionic relaxation) of the same supercell with the same NELECT (which should give the energy of a delocalised carrier in this supercell), with this energy difference giving an estimate of the polaron binding energy. Of course, the binding energy calculated this way can be sensitive to the supercell size, and so it is recommended to either perform these calculations over multiple supercell sizes and check their convergence / extrapolate to the value at infinite cell size (e.g. see Figs S14,S15 in https://pubs.acs.org/doi/suppl/10.1021/acs.jpclett.2c02436/suppl_file/jz2c02436_si_001.pdf), or employ a charge correction scheme for the localised polaron case (such as the FNV scheme implemented in doped’s codebase or sxdefectalign or that of Falletta et al.).

Further Analysis#

After comparing the energies of these relaxed supercells, you may want to plot the energies vs distortion and perform further analysis of the electronic densities (e.g. by plotting orbital densities with PARCHG outputs, for which the Wavecar.get_parchg function from pymatgen is useful, by plotting magnetisation densities using CHGCAR files (e.g. in VESTA or CrystalMaker), by analysing the electronic structure (e.g. using get_eigenvalue_analysis in doped) etc) – e.g. see Fig. S10 in Structural and electronic features enabling delocalized charge-carriers in CuSbSe₂.

The hole-finder.py script provided in the ShakeNBreak docs directory can be useful for identifying hole states in VASP calculations, for PARCHG analysis.