Source code for abipy.flowtk.effmass_works
# coding: utf-8
"""Work subclasses related to effective mass calculations."""
from __future__ import annotations
import numpy as np
import os
from monty.json import jsanitize
from abipy.core.kpoints import build_segments
from abipy.abio.inputs import AbinitInput
from .nodes import Node
from .works import Work, PhononWork
from .flows import Flow
def _get_red_dirs_from_opts(red_dirs, cart_dirs, reciprocal_lattice) -> np.ndarray:
"""
Helper function to compute the list of directions from user input. Return numpy array.
"""
all_red_dirs = []
if red_dirs is not None:
all_red_dirs.extend(np.reshape(red_dirs, (-1, 3)))
if cart_dirs is not None:
frac_coords = reciprocal_lattice.get_fractional_coords(np.reshape(cart_dirs, (-1, 3)))
all_red_dirs.extend(frac_coords)
return np.reshape(all_red_dirs, (-1, 3))
[docs]
class EffMassLineWork(Work):
"""
Work for the computation of effective masses via finite differences along a k-line.
Useful for cases such as NC+SOC where DFPT is not implemented or if one is interested
in the non-parabolic behaviour of the energy dispersion.
.. rubric:: Inheritance Diagram
.. inheritance-diagram:: EffMassLineWork
"""
[docs]
@classmethod
def from_scf_input(cls, scf_input: AbinitInput,
k0_list, step=0.01, npts=15,
red_dirs=[[1, 0, 0], [0, 1, 0], [0, 0, 1]], ndivsm=-20,
cart_dirs=None, den_node=None, manager=None) -> EffMassLineWork:
"""
Build the Work from an |AbinitInput| representing a GS-SCF calculation.
Args:
scf_input: |AbinitInput| for GS-SCF used as template to generate the other inputs.
k0_list: List with the reduced coordinates of the k-points where effective masses are wanted.
step: Step for finite difference in Angstrom^-1
npts: Number of points sampled around each k-point for each direction.
red_dirs: List of reduced directions used to generate the segments passing through the k-point
cart_dirs: List of Cartesian directions used to generate the segments passing through the k-point
den_node: Path to the DEN file or Task object producing a DEN file.
Can be used to avoid the initial SCF calculation if a DEN file is already available.
If None, a GS calculation is performed.
ndivsm: if > 0, it's the number of divisions for the smallest segment of the path (Abinit variable).
if < 0, it's interpreted as the pymatgen `line_density` parameter in which the number of points
in the segment is proportional to its length. Typical value: -20.
manager: |TaskManager| instance. Use default if None.
"""
if npts < 3:
raise ValueError("Number of points: `%s` should be >= 3 for finite differences" % npts)
# Define list of directions from user input.
reciprocal_lattice = scf_input.structure.lattice.reciprocal_lattice
all_red_dirs = _get_red_dirs_from_opts(red_dirs, cart_dirs, reciprocal_lattice)
kpts = build_segments(k0_list, npts, step, all_red_dirs, reciprocal_lattice)
# Now build NSCF input with explicit list of k-points.
nscf_input = scf_input.make_nscf_kptopt0_input(kpts)
new = cls(manager=manager)
# Need to perform SCF run if DEN file is not available.
scf_task = new.register_scf_task(scf_input) if den_node is None else Node.as_node(den_node)
new.register_nscf_task(nscf_input, deps={scf_task: "DEN"})
if ndivsm != 0:
ebands_inp = scf_input.make_ebands_input(ndivsm=ndivsm)
new.register_nscf_task(ebands_inp, deps={scf_task: "DEN"})
return new
[docs]
class EffMassDFPTWork(Work):
"""
Work for the computation of effective masses with DFPT.
Requires explicit list of k-points and range of bands.
.. rubric:: Inheritance Diagram
.. inheritance-diagram:: EffMassDFPTWork
"""
[docs]
@classmethod
def from_scf_input(cls, scf_input: AbinitInput,
k0_list, effmass_bands_f90,
ngfft=None, den_node=None, manager=None) -> EffMassDFPTWork:
"""
Build the Work from an |AbinitInput| representing a GS-SCF calculation.
Args:
scf_input: |AbinitInput| for GS-SCF used as template to generate the other inputs.
k0_list: List with the reduced coordinates of the k-points where effective masses are wanted.
effmass_bands_f90: (nkpt, 2) array with band range for effmas computation.
WARNING: Assumes Fortran convention with indices starting from 1.
ngfft: FFT divisions (3 integers). Used to enforce the same FFT mesh in the NSCF run as the one used for GS.
den_node: Path to the DEN file or Task object producing a DEN file.
Can be used to avoid the initial SCF calculation if a DEN file is already available.
If None, a GS calculation is performed.
manager: |TaskManager| instance. Use default if None.
"""
multi = scf_input.make_dfpt_effmass_inputs(k0_list, effmass_bands_f90, ngfft=ngfft)
nscf_input, effmass_input = multi[0], multi[1]
new = cls(manager=manager)
# Important: keep a reference to the Frohlich input that can be used to run EPH calculations if needed.
new.frohlich_input = multi[2]
# Need to perform SCF run if DEN file is not available
scf_task = new.register_scf_task(scf_input) if den_node is None else Node.as_node(den_node)
nscf_task = new.register_nscf_task(nscf_input, deps={scf_task: "DEN"})
new.register_effmass_task(effmass_input, deps={scf_task: "DEN", nscf_task: "WFK"})
return new
[docs]
class EffMassAutoDFPTWork(Work):
"""
Work for the automatic computation of effective masses with DFPT.
Band extrema are automatically detected by performing a NSCF calculation
along a high-symmetry k-path with ndivsm.
Requires more computation that EffMassWork since input variables (kpoints and band range)
are computed at runtime.
.. rubric:: Inheritance Diagram
.. inheritance-diagram:: EffMassAutoDFPTWork
"""
[docs]
@classmethod
def from_scf_input(cls, scf_input: AbinitInput,
ndivsm=15, tolwfr=1e-20, den_node=None, manager=None) -> EffMassAutoDFPTWork:
"""
Build the Work from an |AbinitInput| representing a GS-SCF calculation.
Args:
scf_input: |AbinitInput| for GS-SCF used as template to generate the other inputs.
ndivsm: if > 0, it's the number of divisions for the smallest segment of the path (Abinit variable).
if < 0, it's interpreted as the pymatgen `line_density` parameter in which the number of points
in the segment is proportional to its length. Typical value: -20.
This option is the recommended one if the k-path contains two high symmetry k-points that are very close
as ndivsm > 0 may produce a very large number of wavevectors.
tolwfr: Tolerance on residuals for NSCF calculation
den_node: Path to the DEN file or Task object producing a DEN file.
Can be used to avoid the initial SCF calculation if a DEN file is already available.
If None, a GS calculation is performed.
manager: |TaskManager| instance. Use default if None.
"""
if scf_input.get("nsppol", 1) != 1:
raise NotImplementedError("Magnetic semiconductors with nsppol = 2 are not implemented!")
new = cls(manager=manager)
# Keep a copy of the initial input.
new.scf_input = scf_input.deepcopy()
# Need SCF run to get DEN file.
if den_node is None:
new.den_node = new.register_scf_task(new.scf_input)
else:
new.den_node = Node.as_node(den_node)
# Perform NSCF run along k-path that will be used to find band extrema.
bands_input = scf_input.make_ebands_input(ndivsm=ndivsm, tolwfr=tolwfr)
new.bands_task = new.register_nscf_task(bands_input, deps={new.den_node: "DEN"})
return new
[docs]
def on_all_ok(self):
"""
This method is called once the `Work` is completed i.e. when all tasks have reached status S_OK.
Here, we read the band structure from GSR to find the position of the band edges and use these values
to generate a new work for effective masses with DFPT.
"""
with self.bands_task.open_gsr() as gsr:
ebands = gsr.ebands
# Warning: Assuming semiconductor with spin-unpolarized band energies.
# At present, Abinit input variables do not take into account nsppol.
ebands.set_fermie_to_vbm()
# Find k0_list and effmass_bands_f90
k0_list, effmass_bands_f90 = ebands.get_kpoints_and_band_range_for_edges()
den_ngfft = gsr.reader.read_ngfft3()
# Create the work for effective mass computation with DFPT and add it to the flow.
# Keep a reference in generated_effmass_dfpt_work.
work = EffMassDFPTWork.from_scf_input(self.scf_input, k0_list, effmass_bands_f90,
ngfft=den_ngfft, den_node=self.den_node)
self.generated_effmass_dfpt_work = work
self.flow.register_work(work)
self.flow.allocate()
self.flow.build_and_pickle_dump()
self.flow.finalized = False
return super().on_all_ok()
[docs]
class FrohlichZPRFlow(Flow):
"""
.. rubric:: Inheritance Diagram
.. inheritance-diagram:: FrohlichZPRFlow
"""
[docs]
@classmethod
def from_scf_input(cls, workdir: str, scf_input: AbinitInput,
ddb_node=None, ndivsm=15, tolwfr=1e-20,
metadata=None, manager=None) -> FrohlichZPRFlow:
"""
Build the Flow from an |AbinitInput| representing a GS-SCF calculation.
Final results are stored in the "zprfrohl_results.json" in the outdata directory of the flow.
Args:
workdir: Working directory.
scf_input: |AbinitInput| for GS-SCF used as template to generate the other inputs.
ddb_node: Path to an external DDB file that is used to avoid the calculation of BECS/eps_inf and phonons.
If None, a DFPT calculation is automatically performed by the flow.
ndivsm: Number of divisions used to sample the smallest segment of the k-path.
tolwfr: Tolerance on residuals for NSCF calculation
manager: |TaskManager| instance. Use default if None.
metadata: Dictionary with metadata addeded to the final JSON file.
"""
new = cls(workdir=workdir, manager=manager)
new.metadata = jsanitize(metadata) if metadata is not None else None
# Build work for the automatic computation of effective masses.
new.effmass_auto_work = EffMassAutoDFPTWork.from_scf_input(scf_input, ndivsm=ndivsm, tolwfr=tolwfr)
new.register_work(new.effmass_auto_work)
new.scf_task, new.ebands_kpath_task = new.effmass_auto_work[0], new.effmass_auto_work[1]
if ddb_node is not None:
new.ddb_node = Node.as_node(ddb_node)
new.ddb_file_path_if_ddb_node = os.path.abspath(ddb_node)
else:
# Compute DDB with BECS and eps_inf.
new.ddb_file_path_if_ddb_node = None
ph_work = PhononWork.from_scf_task(new.scf_task, qpoints=[0, 0, 0],
is_ngqpt=False, tolerance=None, with_becs=True,
ddk_tolerance=None)
new.register_work(ph_work)
new.ddb_node = ph_work
return new
[docs]
def on_all_ok(self):
"""
This method is called when all the works in the flow have reached S_OK.
This method shall return True if the calculation is completed or
False if the execution should continue due to side-effects such as adding a new work to the flow.
"""
if self.on_all_ok_num_calls > 0: return True
self.on_all_ok_num_calls += 1
work = Work()
inp = self.effmass_auto_work.generated_effmass_dfpt_work.frohlich_input
wfk_task = self.effmass_auto_work.generated_effmass_dfpt_work[0]
self.effmass_task = self.effmass_auto_work.generated_effmass_dfpt_work[1]
deps = {wfk_task: "WFK", self.ddb_node: "DDB", self.effmass_task: "EFMAS.nc"}
self.frohl_task = work.register_eph_task(inp, deps=deps)
self.register_work(work)
self.allocate()
self.build_and_pickle_dump()
return False
[docs]
def finalize(self):
"""
This method is called when the flow is completed.
Here we write the final results in the "zprfrohl_results.json" file
in the `outdata` directory of the flow
"""
d = {}
if self.metadata is not None: d.update({"metadata": self.metadata})
# Add GS results.
with self.scf_task.open_gsr() as gsr:
d["gsr_scf_path"] = gsr.filepath
d["pressure_GPa"] = float(gsr.pressure)
d["max_force_eV_Ang"] = float(gsr.max_force)
d["structure"] = gsr.structure
# Add NSCF band structure.
with self.ebands_kpath_task.open_gsr() as gsr:
d["gsr_nscf_kpath"] = gsr.filepath
gsr.ebands.set_fermie_to_vbm()
d["ebands_kpath"] = gsr.ebands
#d["ebands_info"] = gsr.ebands.get_dict4pandas(with_geo=False, with_spglib=False)
# TODO
# Extract results from run.abo
#d["effmass_results"] = self.effmass_task.yaml_parse_results()
#d["frohl_results"] = self.frohl_task.yaml_parse_results()
# Add epsinf, e0, BECS, alpha and DDB as string.
from abipy import abilab
if self.ddb_file_path_if_ddb_node is not None:
ddb_filepath = self.ddb_file_path_if_ddb_node
else:
ddb_filepath = self.ddb_node.outdir.path_in("out_DDB")
with abilab.abiopen(ddb_filepath) as ddb:
d["ddb_path"] = ddb.filepath
d["ddb_string"] = ddb.get_string()
epsinf, becs = ddb.anaget_epsinf_and_becs(chneut=1)
gen = ddb.anaget_dielectric_tensor_generator(asr=2, chneut=1, dipdip=1)
eps0 = gen.tensor_at_frequency(w=0.0)
d["becs"] = becs
d["epsinf_cart"] = epsinf
# FIXME Complex is not supported by JSON.
d["eps0_cart"] = eps0.real
#print(d)
abilab.mjson_write(d, self.outdir.path_in("zprfrohl_results.json"), indent=4)
return super().finalize()