IFCs Embedding#

This section explains how to obtain the phonon modes of a defect system using the Interatomic Force Constant (IFC) embedding approach. This method enables the calculation of both defect-localized and bulk-like phonon modes within a unified framework. We illustrate the process using the Sr[Li\(_2\)Al\(_2\)O\(_2\)N\(_2\)]:Eu\(^{2+}\) example, first computing pristine and defect phonons, then performing the embedding.

Note

The simulation parameters below are for demonstration only and are not converged.

1. Pristine Phonons#

The following script creates an AbiPy workflow to compute the phonons of the pristine system using DFPT. It uses the primitive structure of Sr[Li\(_2\)Al\(_2\)O\(_2\)N\(_2\)] and computes phonons on a 2x2x2 q-mesh.

import sys
import os
import abipy.abilab as abilab
import abipy.data as abidata
import abipy.core.structure as structure

from abipy import flowtk

def make_scf_input():
    """
    This function constructs the input file for the GS calculation:
    """
    pseudodir = 'pseudos'

    pseudos = ('Eu.xml',
               'Sr.xml',
               'Al.xml',
               'Li.xml',
               'O.xml',
               'N.xml')

    stru = structure.Structure.from_file("SALON_prim.cif")

    # Initialize the input
    gs_inp = abilab.AbinitInput(stru, pseudos=pseudos, pseudo_dir=pseudodir)

    # Set variables
    gs_inp.set_vars(
        ecut=10,
        pawecutdg=20,
        diemac=5,
        nstep=100,
        tolvrs=1e-15,
        nband=60,
        nbdbuf=10,
    )

    gs_inp.set_kmesh(ngkpt=[2,2,2], shiftk=[[0.5,0.5,0.5]])

    return gs_inp


def build_flow(options):
    # Working directory (default is the name of the script with '.py' removed and "run_" replaced by "flow_")
    if not options.workdir:
        options.workdir = os.path.basename(sys.argv[0]).replace(".py", "").replace("run_", "flow_")

    flow = flowtk.Flow(options.workdir, manager=options.manager)
    scf_input = make_scf_input()
    phonon_work = flowtk.works.PhononWork.from_scf_input(scf_input, qpoints=[2,2,2], is_ngqpt=True,
                                                         with_becs=True, ddk_tolerance={"tolwfr":1e-10})
    flow.register_work(phonon_work)
    return flow


@flowtk.flow_main
def main(options):
    """
    This is our main function that will be invoked by the script.
    flow_main is a decorator implementing the command line interface.
    Command line args are stored in `options`.
    """
    return build_flow(options)


if __name__ == "__main__":
    sys.exit(main())

2. Defect Phonons#

This script computes the phonons of the defect system using finite differences. It uses the supercell defect structure from the \(\Delta\)SCF calculation and then uses a phonopy workflow. The supercell size is set to [1,1,1], which is equivalent to a \(\Gamma\) point calculation.

import sys
import os
import abipy.abilab as abilab
import abipy.data as abidata

from abipy.core.structure import Structure
from abipy import flowtk
from abipy.flowtk.abiphonopy import PhonopyWork

def make_scf_input():
    # extract the structure from the deltaSCF calculation.
    structure = Structure.from_file("flow_deltaSCF/w0/t0/outdata/out_GSR.nc")
    pseudodir = 'pseudos'

    pseudos = ('Eu.xml',
               'Sr.xml',
               'Al.xml',
               'Li.xml',
               'O.xml',
               'N.xml')

    gs_scf_inp = abilab.AbinitInput(structure, pseudos=pseudos, pseudo_dir=pseudodir)

    gs_scf_inp.set_vars(ecut=10,
                        pawecutdg=20,
                        chksymbreak=0,
                        diemac=5,
                        prtwf=-1,
                        nstep=100,
                        toldfe=1e-6,
                        chkprim=0,
                        )

    # Set DFT+U and spinat parameters according to chemical symbols.
    symb2luj = {"Eu": {"lpawu": 3, "upawu": 7, "jpawu": 0.7}}

    gs_scf_inp.set_usepawu(usepawu=1, symb2luj=symb2luj)

    n_val = gs_scf_inp.num_valence_electrons
    n_cond = round(20)

    spin_up_gs = f"\n{int((n_val - 7) / 2)}*1 7*1 {n_cond}*0"
    spin_up_ex = f"\n{int((n_val - 7) / 2)}*1 6*1 0 1 {n_cond - 1}*0"
    spin_dn = f"\n{int((n_val - 7) / 2)}*1 7*0 {n_cond}*0"

    nsppol = 2
    shiftk = [0, 0, 0]
    ngkpt = [1, 1, 1]

    # Build SCF input for the ground state configuration.
    gs_scf_inp.set_kmesh_nband_and_occ(ngkpt, shiftk, nsppol, [spin_up_gs, spin_dn])

    # Build SCF input for the excited configuration.
    exc_scf_inp = gs_scf_inp.deepcopy()
    exc_scf_inp.set_kmesh_nband_and_occ(ngkpt, shiftk, nsppol, [spin_up_ex, spin_dn])

    return gs_scf_inp


def build_flow(options):

    if not options.workdir:
        options.workdir = os.path.basename(sys.argv[0]).replace(".py", "").replace("run_", "flow_")

    flow = flowtk.Flow(options.workdir, manager=options.manager)

    # Build input for GS calculation
    scf_input = make_scf_input()

    # We only need the phonons at Gamma point, so we set the supercell to [1,1,1].
    work = PhonopyWork.from_gs_input(scf_input, scdims=[1, 1, 1])
    flow.register_work(work)

    return flow


@flowtk.flow_main
def main(options):
    """
    This is our main function that will be invoked by the script.
    flow_main is a decorator implementing the command line interface.
    Command line args are stored in `options`.
    """
    return build_flow(options)


if __name__ == "__main__":
    sys.exit(main())

3. IFC Embedding: Step-by-Step#

The IFC embedding procedure combines the results of the two previous calculations to produce a new Phonopy object that contains the phonon modes of the defect system in a large supercell. The embedded phonons are typically created with the Embedded_phonons.from_phonopy_instances method:

from abipy.embedding.embedding_ifc import Embedded_phonons
help(Embedded_phonons.from_phonopy_instances)
Help on method from_phonopy_instances in module abipy.embedding.embedding_ifc:

from_phonopy_instances(phonopy_pristine, phonopy_defect, structure_defect_wo_relax, main_defect_coords_in_pristine, main_defect_coords_in_defect, substitutions_list: 'list' = None, vacancies_list: 'list' = None, interstitial_list: 'list' = None, tol_mapping=0.01, cut_off_mode='auto', rc_1=None, rc_2=None, factor_ifc=1, verbose=0, asr=True) -> 'Phonopy' method of builtins.type instance
    Args:
        phonopy_pristine: Phonopy object of the pristine structure
        phonopy_defect: Phonopy object of the defect structure
        structure_defect_wo_relax: Supercell structure associated to the defect structure, but without relaxation. Needed for an easier mapping.
            Should corresponds to order of phonopy_defect structure!
        main_defect_coords_in_pristine: Main coordinates of the defect in pristine structure, if defect complex, can be set to the
            center of mass of the complex
        main_defect_coords_in_defect : Main coordinates of the defect in defect structure, if defect complex, can be set to the
            center of mass of the complex
        substitutions_list: List of substitutions infos [index,specie], ex : [[0, "Eu"],[1,"N"]]
        vacancies_list: List of indices where the vacancies are, ex: [13,14]
        interstitial_list: List of interstitial infos [specie, cart_coord], ex [['Eu',[0,0,0]],['Ce','[0,0,3]']]
        tol_mapping: Tolerance in angstrom for the mapping between structures
        cut_off_mode: Cut off mode for the radii of the sphere centered around the defect (rc_2). if 'auto' : the code tries to find the largest sphere
            inscribed in the defect supercell. If 'manual' :  rc_1 and rc_2 should be provided.
        rc_1: Radii of the sphere centered around the defect outside which the IFCs are set to zero, allows to get sparse matrix.
        rc_2: Radii of the sphere centered around the defect where the IFCs of the defect computation are included
        factor_ifc: Multiply the IFCs inside the sphere of radii rc_2 by factor_ifc, usefull to introduce fictious high-frequency local mode
        verbose : Print explicitely all the IFCs replacements
        asr: If True, re-enforce acoustic sum rule after IFCs embedding, following eq. (S4) of https://pubs.acs.org/doi/10.1021/acs.chemmater.3c00537

    Returns:
        A new phonopy object with the embedded structure and embedded IFCs.

3.1. Load Pristine and Defect Phonon Data#

First, load the DDB file (pristine phonons) and the phonopy object for the defect system:

import phonopy
import numpy as np

from pymatgen.io.phonopy import get_pmg_structure
from abipy.dfpt.converters import ddb_ucell_to_phonopy_supercell
from abipy.embedding.embedding_ifc import Embedded_phonons
from abipy.core.kpoints import kmesh_from_mpdivs
from abipy.abilab import abiopen

# Load pristine phonons (DFPT, 2x2x2 q-mesh)
ddb_pristine = abiopen("../workflows_data/flow_phonons/w0/outdata/out_DDB")

# Load defect phonons (finite difference, supercell)
ph_defect = phonopy.load(
    supercell_filename="../workflows_data/flow_phonons_doped/w0/outdata/POSCAR",
    force_sets_filename="../workflows_data/flow_phonons_doped/w0/outdata/FORCE_SETS",
)

3.2. Fold pristine IFCs to the supercell#

The pristine DDB file is first interpolated using anaget_interpolated_ddb and then the folding procedure is executed by calling ddb_ucell_to_phonopy_supercell:

# Define the large phonon supercell size for the embedding
sc_size = [2, 2, 4]

# Generate the list of q-points corresponding to the supercell
qpts = kmesh_from_mpdivs(mpdivs=sc_size, shifts=[0, 0, 0], order="unit_cell")

# Interpolate the pristine DDB to obtain force constants on the q-point mesh
ddb_pristine_inter = ddb_pristine.anaget_interpolated_ddb(qpt_list=qpts)

# Fold the phonons, and convert to Phonopy object.
ph_pristine = ddb_ucell_to_phonopy_supercell(ddb_pristine_inter)

3.3. Prepare Structures and Defect Mapping#

To embed the IFCs, the algorithm needs to know which atom(s) are substituted or modified. This requires:

  • The defect structure without relaxation (i.e., before local relaxation around the defect)

  • The coordinates of the defect atom in both the defect and pristine supercells

# Create the defect structure without relaxation
structure_defect_wo_relax = ddb_pristine.structure.copy()
structure_defect_wo_relax.make_supercell([1, 1, 2])
structure_defect_wo_relax.replace(0, 'Eu')

# Index and coordinates of the defect atom (manually identified)
idefect_defect_stru = 0
main_defect_coords_in_defect = structure_defect_wo_relax.cart_coords[idefect_defect_stru]

idefect_pristine_stru = 0 # (manually identified)
main_defect_coords_in_pristine = get_pmg_structure(ph_pristine.supercell).cart_coords[idefect_pristine_stru]

3.4. Run the Embedding Algorithm#

The Embedded_phonons.from_phonopy_instances method combines the pristine and defect phonon data. The algorithm:

  • Maps atoms between the pristine and defect supercells (crucial step)

  • Extracts IFCs from both systems

  • Replaces pristine IFCs with defect IFCs within a cutoff radius around the defect

  • Retains pristine IFCs elsewhere

  • Enforces the Acoustic Sum Rule (ASR)

You can print details of the modifications by setting verbose=True.

emb_ph = Embedded_phonons.from_phonopy_instances(
    phonopy_pristine=ph_pristine,
    phonopy_defect=ph_defect,
    structure_defect_wo_relax=structure_defect_wo_relax,
    main_defect_coords_in_defect=main_defect_coords_in_defect,
    main_defect_coords_in_pristine=main_defect_coords_in_pristine,
    substitutions_list=[[idefect_pristine_stru, "Eu"]],
    cut_off_mode="auto"
)
Number of atoms in the pristine supercell      : 288
Number of atoms in the defective supercell     : 36
Defect infos
    Substitutions:
       0, [4.00242548 0.         1.59996494], Sr1 replaced by Eu
Mapping after structure manipulation           : 36/36
 Set IFC to explicit defect phonons calculations if both atoms are separated from defect by a distance < R_c2 = 3.168
 Enforce ASR

 Embedding procedure done

If the structure mapping fails (e.g., due to mismatched coordinates), the code will notify you:

emb_ph_failed = Embedded_phonons.from_phonopy_instances(
    phonopy_pristine=ph_pristine,
    phonopy_defect=ph_defect,
    structure_defect_wo_relax=structure_defect_wo_relax,
    main_defect_coords_in_defect=main_defect_coords_in_defect,
    main_defect_coords_in_pristine=main_defect_coords_in_pristine + np.array([0.1, 0.0, 0.0]),
    substitutions_list=[[idefect_pristine_stru, "Eu"]],
    cut_off_mode="auto",
    verbose=False
)
Mapping incomplete : only 0/36 atoms mapped.
Number of atoms in the pristine supercell      : 288
Number of atoms in the defective supercell     : 36
Defect infos
    Substitutions:
       0, [4.00242548 0.         1.59996494], Sr1 replaced by Eu
Mapping after structure manipulation           : 0/36
 Set IFC to explicit defect phonons calculations if both atoms are separated from defect by a distance < R_c2 = 3.168
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[6], line 1
----> 1 emb_ph_failed = Embedded_phonons.from_phonopy_instances(
      2     phonopy_pristine=ph_pristine,
      3     phonopy_defect=ph_defect,
      4     structure_defect_wo_relax=structure_defect_wo_relax,
      5     main_defect_coords_in_defect=main_defect_coords_in_defect,
      6     main_defect_coords_in_pristine=main_defect_coords_in_pristine + np.array([0.1, 0.0, 0.0]),
      7     substitutions_list=[[idefect_pristine_stru, "Eu"]],
      8     cut_off_mode="auto",
      9     verbose=False
     10 )

File ~/work/abipy_book/abipy_book/abipy/abipy/embedding/embedding_ifc.py:199, in Embedded_phonons.from_phonopy_instances(cls, phonopy_pristine, phonopy_defect, structure_defect_wo_relax, main_defect_coords_in_pristine, main_defect_coords_in_defect, substitutions_list, vacancies_list, interstitial_list, tol_mapping, cut_off_mode, rc_1, rc_2, factor_ifc, verbose, asr)
    196                 print(f"by defect cell IFC = \n {ifc_defect[mapping.index(i)][mapping.index(j)]}")
    197                 print(f"Diff IFC = \n {ifc_pristine[i][j]-ifc_defect[mapping.index(i)][mapping.index(j)]}")
--> 199             ifc_emb[i][j] = factor_ifc*ifc_defect[mapping.index(i)][mapping.index(j)]
    201 # enforce ASR
    202 if asr:

ValueError: 0 is not in list

Finally, note that the Embedded_phonons class provides methods for further analysis and data export. You can use get_gamma_freq_with_vec_abipy_fmt() to compute the \(\Gamma\)-point phonon frequencies and eigenvectors in AbiPy format, or to_ddb() to convert the embedded phonons back to an Abinit DDB file. Also, you will find in abipy.embedding.utils_ifc function to compute the localization ratio of the phonon modes as well as helper function to draw phonon eigenvectors with VESTA.

Note

For additional examples of the use of this module, especially for the embedding of different defect types (vacancy, interstitial), please consult the examples located in abipy/embedding/tests/test_embedding_ifc.py.