# coding: utf-8
"""Work subclasses related to GS calculations."""
from __future__ import annotations
import json
from pymatgen.analysis.eos import EOS
from abipy.core.structure import Structure
from abipy.abio.inputs import AbinitInput
from abipy.electrons.gsr import GsrRobot
from .works import Work
__all__ = [
"GsKmeshConvWork"
"GsKmeshTsmearConvWork",
"EosWork",
]
class GsKmeshConvWork(Work):
"""
This work performs convergence studies of GS properties
with respect to the k-mesh
It produces ...
.. rubric:: Inheritance Diagram
.. inheritance-diagram:: GsKmeshConvWork
"""
@classmethod
def from_scf_input(cls, scf_input: AbinitInput, nksmall_list: list) -> GsKmeshConvWork:
"""
Build the work from a `scf_input` for a GS SCF run and a list
with the smallest number of divisions for the k-mesh.
"""
work = cls()
for nksmall in nksmall_list:
new_inp = scf_input.deepcopy()
new_inp.set_autokmesh(nksmall)
work.register_scf_task(new_inp)
return work
def on_all_ok(self): # pragma: no cover
"""
This method is called when all tasks in the GsKmeshTsmearConvWork have reached S_OK.
"""
with GsrRobot.from_work(self) as gsr_robot:
df = gsr_robot.get_dataframe(with_geo=False)
# Write excel file.
basename = self.__class__.__name__
df.to_excel(self.outdir.path_in(f"{basename}.xlsx"))
with gsr_robot.get_pyscript(self.outdir.path_in("gsr_robot.py")) as script:
script.add_text("""
# Quantities that should be tested for convergence.
abs_conv = {
"energy_per_atom": 1e-3,
"pressure": 1e-2,
"max_force": 1e-4,
}
items = abs_conv.keys()
robot.plot_convergence_items(items, sortby="nkpt", abs_conv=abs_conv)
""")
return super().on_all_ok()
class GsKmeshTsmearConvWork(Work):
"""
This work performs convergence studies of GS properties
with respect to the k-mesh and the electronic smearing.
It produces ...
.. rubric:: Inheritance Diagram
.. inheritance-diagram:: GsKmeshTsmearConvWork
"""
@classmethod
def from_scf_input(cls, scf_input: AbinitInput, nksmall_list: list, tsmear_list: list) -> GsKmeshTsmearConvWork:
"""
Build the work from a `scf_input` for a GS SCF run including `occopt`
and a list with the smallest number of divisions for the k-mesh.
"""
occopt = scf_input.get("occopt", default=None)
if occopt is None or occopt <= 0:
raise ValueError(f"scf_input should define occopt but found: {occopt}")
work = cls()
for tsmear in tsmear_list:
for nksmall in nksmall_list:
new_inp = scf_input.new_with_vars(tsmear=tsmear)
new_inp.set_autokmesh(nksmall)
work.register_scf_task(new_inp)
return work
def on_all_ok(self): # pragma: no cover
"""
This method is called when all tasks in the GsKmeshTsmearConvWork have reached S_OK.
"""
with GsrRobot.from_work(self) as gsr_robot:
df = gsr_robot.get_dataframe(with_geo=False)
# Write excel file.
basename = self.__class__.__name__
df.to_excel(self.outdir.path_in(f"{basename}.xlsx"))
with gsr_robot.get_pyscript(self.outdir.path_in("gsr_robot.py")) as script:
script.add_text("""
# Quantities that should be tested for convergence.
abs_conv = {
"energy_per_atom": 1e-3,
"pressure": 1e-2,
"max_force": 1e-4,
}
items = abs_conv.keys()
robot.plot_convergence_items(items, sortby="nkpt", hue="tsmear", abs_conv=abs_conv)
""")
return super().on_all_ok()
[docs]
class EosWork(Work):
"""
Work to compute the Equation of State.
The EOS is obtained by computing E(V) for several volumes around the input V0,
The initial volumes are obtained by rescaling the input lattice vectors so that
length proportions and angles are preserved.
This guess is exact for cubic materials while other Bravais lattices require
a constant-volume optimization of the cell geometry.
If lattice_type=="cubic" and atomic positions are fixed by symmetry.
use can use move_atoms=False to perform standard GS-SCF calculations.
In all the other cases, E(V) is obtained by relaxing the atomic positions at fixed volume.
The E(V) points are fitted at the end of the work and the results are saved in the
`eos_data.json` file produced in the `outdata` directory.
The file contains the energies, the volumes and the values of V0, B0, B1 obtained
with different EOS models.
.. rubric:: Inheritance Diagram
.. inheritance-diagram:: EosWork
"""
[docs]
def get_and_write_eosdata(self, write_json=True) -> dict:
"""
Compute the EOS and produce a JSON file `eos_data.json` in outdata.
"""
energies_ev, volumes = [], []
for task in self:
with task.open_gsr() as gsr:
energies_ev.append(float(gsr.energy))
volumes.append(float(gsr.structure.volume))
eos_data = {"input_volumes_ang3": self.input_volumes,
"volumes_ang3": volumes,
"energies_ev": energies_ev}
for model in EOS.MODELS:
if model in ("deltafactor", "numerical_eos"): continue
try:
fit = EOS(model).fit(volumes, energies_ev)
eos_data[model] = {k: float(v) for k, v in fit.results.items()}
except Exception as exc:
eos_data[model] = {"exception": str(exc)}
if write_json:
with open(self.outdir.path_in("eos_data.json"), "wt") as fh:
json.dump(eos_data, fh, indent=4, sort_keys=True)
return eos_data
[docs]
def on_all_ok(self): # pragma: no cover
"""
This method is called when all tasks have reached S_OK.
It reads the energies and the volumes from the GSR file, computes the EOS
and produce a JSON file `eos_data.json` in outdata.
"""
self.get_and_write_eosdata()
return super().on_all_ok()