Quasi-harmonic Workflow Tutorial with VASP

This first part is only needed as we have to mock VASP here as we cannot run it directly in a jupyter notebook:

import warnings

from mock_vasp import TEST_DIR, mock_vasp

ref_paths = {
    "phonon static 1/1": "Si_qha_2/phonon_static_1_1",
    "static": "Si_qha_2/static",
    "tight relax 1 EOS equilibrium relaxation": "Si_qha_2/tight_relax_1",
    "tight relax 2 EOS equilibrium relaxation": "Si_qha_2/tight_relax_2",
    "tight relax 1 deformation 0": "Si_qha_2/tight_relax_1_d0",
    "tight relax 1 deformation 1": "Si_qha_2/tight_relax_1_d1",
    "tight relax 1 deformation 2": "Si_qha_2/tight_relax_1_d2",
    "tight relax 1 deformation 3": "Si_qha_2/tight_relax_1_d3",
    "tight relax 1 deformation 4": "Si_qha_2/tight_relax_1_d4",
    "tight relax 1 deformation 5": "Si_qha_2/tight_relax_1_d5",
    "tight relax 2 deformation 0": "Si_qha_2/tight_relax_2_d0",
    "tight relax 2 deformation 1": "Si_qha_2/tight_relax_2_d1",
    "tight relax 2 deformation 2": "Si_qha_2/tight_relax_2_d2",
    "tight relax 2 deformation 3": "Si_qha_2/tight_relax_2_d3",
    "tight relax 2 deformation 4": "Si_qha_2/tight_relax_2_d4",
    "tight relax 2 deformation 5": "Si_qha_2/tight_relax_2_d5",
    "dft phonon static eos deformation 1": "Si_qha_2/"
    "dft_phonon_static_eos_deformation_1",
    "dft phonon static eos deformation 2": "Si_qha_2/"
    "dft_phonon_static_eos_deformation_2",
    "dft phonon static eos deformation 3": "Si_qha_2/"
    "dft_phonon_static_eos_deformation_3",
    "dft phonon static eos deformation 4": "Si_qha_2/"
    "dft_phonon_static_eos_deformation_4",
    "dft phonon static eos deformation 5": "Si_qha_2/"
    "dft_phonon_static_eos_deformation_5",
    "dft phonon static eos deformation 6": "Si_qha_2/"
    "dft_phonon_static_eos_deformation_6",
    "dft phonon static eos deformation 7": "Si_qha_2/"
    "dft_phonon_static_eos_deformation_7",
    "dft phonon static 1/1 eos deformation 1": "Si_qha_2/"
    "dft_phonon_static_1_1_eos_deformation_1",
    "dft phonon static 1/1 eos deformation 2": "Si_qha_2/"
    "dft_phonon_static_1_1_eos_deformation_2",
    "dft phonon static 1/1 eos deformation 3": "Si_qha_2/"
    "dft_phonon_static_1_1_eos_deformation_3",
    "dft phonon static 1/1 eos deformation 4": "Si_qha_2/"
    "dft_phonon_static_1_1_eos_deformation_4",
    "dft phonon static 1/1 eos deformation 5": "Si_qha_2/"
    "dft_phonon_static_1_1_eos_deformation_5",
    "dft phonon static 1/1 eos deformation 6": "Si_qha_2/"
    "dft_phonon_static_1_1_eos_deformation_6",
    "dft phonon static 1/1 eos deformation 7": "Si_qha_2/"
    "dft_phonon_static_1_1_eos_deformation_7",
}

This tutorial will make use of a quasi-harmonic workflow that allows to include volume-dependent anharmonicity into the calculation of phonon free energies. Please check out the paper by Togo to learn about the exact implementation as we will rely on Phonopy to perform the quasi-harmonic approximation. https://doi.org/10.7566/JPSJ.92.012001. At the moment, we perform harmonic free energy calculation along a volume curve to arrive at free energy-volume curves that are the starting point for the q uasi-harmonic approximation.

Let’s run the workflow

Now, we load a structure and other important functions and classes for running the qha workflow.

from jobflow import JobStore, run_locally
from maggma.stores import MemoryStore
from pymatgen.core import Structure

from atomate2.vasp.flows.qha import QhaMaker

job_store = JobStore(MemoryStore(), additional_stores={"data": MemoryStore()})
si_structure = Structure.from_file(TEST_DIR / "structures" / "Si_diamond.cif")

Then one can use the QhaMaker to generate a Flow. First, the structure will be optimized than the structures will be optimized at constant volume along an energy volume curve. Please make sure the structural optimizations are tight enough. At each of these volumes, a phonon run will then be performed. The quasi-harmonic approximation is only valid if the harmonic phonon curves don’t show any imaginary modes. However, for testing, you can also switch off this option.

Before we start the quasi-harmonic workflow, we adapt the first relaxation, the relaxation with different volumes and the static runs for the phonon calculation. As we deal with Si, we will not add the non-analytical term correction.

from atomate2.vasp.flows.core import DoubleRelaxMaker
from atomate2.vasp.flows.phonons import PhononMaker
from atomate2.vasp.jobs.core import TightRelaxMaker
from atomate2.vasp.jobs.phonons import PhononDisplacementMaker
from atomate2.vasp.sets.core import StaticSetGenerator, TightRelaxSetGenerator

warnings.filterwarnings("ignore")

phonon_bulk_relax_maker_isif3 = DoubleRelaxMaker.from_relax_maker(
    TightRelaxMaker(
        run_vasp_kwargs={"handlers": ()},
        input_set_generator=TightRelaxSetGenerator(
            user_incar_settings={
                "GGA": "PE",
                "ISPIN": 1,
                "KSPACING": 0.1,
                "ALGO": "Normal",
                "LAECHG": False,
                "ISMEAR": 0,
                "ENCUT": 700,
                "IBRION": 1,
                "ISYM": 0,
                "SIGMA": 0.05,
                "LCHARG": False,
                "LWAVE": False,
                "LVTOT": False,
                "LORBIT": None,
                "LOPTICS": False,
                "LREAL": False,
                "ISIF": 3,
                "NPAR": 4,
            }
        ),
    )
)

phonon_displacement_maker = PhononDisplacementMaker(
    run_vasp_kwargs={"handlers": ()},
    input_set_generator=StaticSetGenerator(
        user_incar_settings={
            "GGA": "PE",
            "IBRION": -1,
            "ISPIN": 1,
            "ISMEAR": 0,
            "ISIF": 3,
            "ENCUT": 700,
            "EDIFF": 1e-7,
            "LAECHG": False,
            "LREAL": False,
            "ALGO": "Normal",
            "NSW": 0,
            "LCHARG": False,
            "LWAVE": False,
            "LVTOT": False,
            "LORBIT": None,
            "LOPTICS": False,
            "SIGMA": 0.05,
            "ISYM": 0,
            "KSPACING": 0.1,
            "NPAR": 4,
        },
        auto_ispin=False,
    ),
)


phonon_bulk_relax_maker_isif4 = DoubleRelaxMaker.from_relax_maker(
    TightRelaxMaker(
        run_vasp_kwargs={"handlers": ()},
        input_set_generator=TightRelaxSetGenerator(
            user_incar_settings={
                "GGA": "PE",
                "ISPIN": 1,
                "KSPACING": 0.1,
                "ALGO": "Normal",
                "LAECHG": False,
                "ISMEAR": 0,
                "ENCUT": 700,
                "IBRION": 1,
                "ISYM": 0,
                "SIGMA": 0.05,
                "LCHARG": False,
                "LWAVE": False,
                "LVTOT": False,
                "LORBIT": None,
                "LOPTICS": False,
                "LREAL": False,
                "ISIF": 4,
                "NPAR": 4,
            }
        ),
    )
)

phonon_displacement_maker.name = "dft phonon static"
flow = QhaMaker(
    initial_relax_maker=phonon_bulk_relax_maker_isif3,
    eos_relax_maker=phonon_bulk_relax_maker_isif4,
    min_length=10,
    phonon_maker=PhononMaker(
        generate_frequencies_eigenvectors_kwargs={
            "tmin": 0,
            "tmax": 1000,
            "tstep": 10,
        },
        bulk_relax_maker=None,
        born_maker=None,
        static_energy_maker=phonon_displacement_maker,
        phonon_displacement_maker=phonon_displacement_maker,
    ),
    linear_strain=(-0.15, 0.15),
    number_of_frames=6,
    pressure=None,
    t_max=None,
    ignore_imaginary_modes=False,
    skip_analysis=False,
    eos_type="vinet",
).make(structure=si_structure)
with mock_vasp(ref_paths=ref_paths) as mf:
    run_locally(
        flow,
        create_folders=True,
        ensure_success=True,
        raise_immediately=True,
        store=job_store,
    )

Let’s retrieve the data and analyze it

job_store.connect()

result = job_store.query_one(
    {"name": "analyze_free_energy"},
    properties=[
        "output.helmholtz_volume",
        "output.temperatures",
        "output.volumes",
    ],
    load=True,
    sort={"completed_at": -1},  # to get the latest computation
)

You can then plot some of the output free energy volume curves

import matplotlib.pyplot as plt

for temp, energy_list in zip(
    result["output"]["temperatures"],
    result["output"]["helmholtz_volume"],
    strict=False,
):
    # Create the plot
    plt.plot(
        result["output"]["volumes"],
        energy_list,
        marker="o",
        label=temp,
    )
    # Add labels and title
plt.xlabel("Volume")
plt.ylabel("Free Energy")

# Show the plot
plt.show()