Source code for abipy.flowtk.lumi_works
# coding: utf-8
"""Work subclasses for the computation of luminiscent properties."""
from __future__ import annotations
from .works import Work
from abipy.abilab import abiopen
from abipy.lumi.deltaSCF import DeltaSCF
[docs]
class LumiWork(Work):
"""
This Work implements Fig 1 of https://arxiv.org/abs/2010.00423.
Client code is responsible for the preparation of the supercell and
of the GS SCF input files with the fixed electronic occupations associated to the two configurations.
By default, the work computes the two relaxed structures and the four total energies
corresponding to the Ag, Ag*, Ae*, Ae configurations. Optionally, one can activate the computation of
four electronic band structures. See docstring of from_scf_inputs for further info.
"""
[docs]
@classmethod
def from_scf_inputs(cls, gs_scf_inp, ex_scf_inp, relax_kwargs_gs, relax_kwargs_ex, ndivsm=0, nb_extra=10,
tolwfr=1e-12, four_points=True, meta=None, manager=None) -> LumiWork:
"""
Args:
gs_scf_inp: |AbinitInput| representing a GS SCF run for the ground-state.
ex_scf_inp: |AbinitInput| representing a GS SCF run for the excited-state.
relax_kwargs_gs: Dictonary with input variables to be added to gs_scf_inp
when generating input files for ground state structural relaxations.
relax_kwargs_ex: Dictonary with input variables to be added to ex_scf_inp
when generating input files for excited state structural relaxations.
ndivsm: Activates the computation of band structure if different from zero.
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.
nb_extra: Number of extra bands added to the input nband when computing band structures (ndivsm != 0).
tolwfr: Tolerance of the residuals used for the NSCF band structure calculations.
four_points : if True, compute the two relaxations and the four points energies.
If false, only the two relaxations.
meta : dict corresponding to the metadata of a lumiwork (supercell size, dopant type,...)
manager: |TaskManager| of the task. If None, the manager is initialized from the config file.
"""
new = cls(manager=manager)
# Templates for GS SCF calculations.
new.gs_scf_inp = gs_scf_inp
new.ex_scf_inp = ex_scf_inp
# Save paramenters for the generation of input files at runtime.
new.relax_kwargs_gs = relax_kwargs_gs
new.relax_kwargs_ex = relax_kwargs_ex
new.four_points = four_points
new.ndivsm = int(ndivsm)
new.tolwfr = tolwfr
new.nb_extra = int(nb_extra)
new.meta=meta
# Relaxation for the Ag configuration.`
new.gs_relax_task = new.register_relax_task(gs_scf_inp.new_with_vars(relax_kwargs_gs))
# Internal counter used in on_all_ok to drive the differ steps of the calculations.
new.iteration_step = 0
# JSON-compatible dictionary storing the most important results of the Work. (relaxations and 4 pts results
# are separated)
# Results will be stored in the `lumi_4pts.json` file in the outdata directory of the Work
# so that one can easily implement additional post-processing tools.
new.json_data = {}
return new
[docs]
def on_all_ok(self):
"""
This method is called when all the works in the flow have reached S_OK.
This is the section in which we implement most of the workflow logic at runtime.
since we need to generate input files with relaxed structures.
"""
# Get Ag relaxed structure
#with self.gs_relax_task.open_gsr() as gsr:
# ag_relaxed_structure = gsr.structure
with abiopen(self.gs_relax_task.output_file.path) as relax_gs_abo:
ag_relaxed_structure = relax_gs_abo.final_structure
if self.iteration_step == 0:
print("in iteration step 0")
self.iteration_step += 1
# Relax geometry with excited configuration starting from Ag*.
relax_ex_inp=self.ex_scf_inp.new_with_vars(self.relax_kwargs_ex)
relax_ex_inp_2 = relax_ex_inp.new_with_structure(ag_relaxed_structure)
self.ex_relax_task = self.register_relax_task(relax_ex_inp_2)#,deps={self.gs_relax_task: "DEN"})
# if only the two relaxation, go to results writing step directly
if self.four_points==False:
self.iteration_step = 2
return self.postpone_on_all_ok()
elif self.iteration_step == 1:
print("in iteration step 1")
self.iteration_step += 1
##### SCF task for each configuration and optionnaly their electronic band structures ####
# Build GS SCF input for the Ag configuration:
# use same structure as Ag with ground occupation factors.
ag_scf_inp = self.gs_scf_inp.new_with_structure(ag_relaxed_structure)
self.ag_scf_task = self.register_scf_task((ag_scf_inp),deps={self.gs_relax_task: "DEN"})
# Build GS SCF input for the Ag* configuration:
# use same structure as Ag but with excited occupation factors.
agstar_scf_inp = self.ex_scf_inp.new_with_structure(ag_relaxed_structure)
self.agstar_scf_task = self.register_scf_task((agstar_scf_inp),deps={self.gs_relax_task: "DEN"})
# Get Aestar relaxed structure.
#with self.ex_relax_task.open_gsr() as gsr:
# aestar_relaxed_structure = gsr.structure
with abiopen(self.ex_relax_task.output_file.path) as relax_ex_abo:
aestar_relaxed_structure = relax_ex_abo.final_structure
# Build ex SCF input for the Aestar configuration:
# use same structure as Aestar with excited occupation factors.
aestar_scf_inp = self.ex_scf_inp.new_with_structure(aestar_relaxed_structure)
self.aestar_scf_task = self.register_scf_task((aestar_scf_inp),deps={self.ex_relax_task: "DEN"})
# Build GS SCF task for the Ae configuration:
# use same structure as Aestar but with ground occupation factors.
ae_scf_inp = self.gs_scf_inp.new_with_structure(aestar_relaxed_structure)
self.ae_scf_task = self.register_scf_task((ae_scf_inp),deps={self.ex_relax_task: "DEN"})
if self.ndivsm != 0:
# Compute band structure for Ag configuration.
self.ag_scf_task.add_ebands_task_to_work(self, ndivsm=self.ndivsm,
tolwfr=self.tolwfr, nb_extra=self.nb_extra)
# Compute band structure for Agstar configuration.
self.agstar_scf_task.add_ebands_task_to_work(self, ndivsm=self.ndivsm,
tolwfr=self.tolwfr, nb_extra=self.nb_extra)
# Compute band structure for aestar configuration.
self.aestar_scf_task.add_ebands_task_to_work(self, ndivsm=self.ndivsm,
tolwfr=self.tolwfr, nb_extra=self.nb_extra)
# Compute band structure for Ae configuration.
self.ae_scf_task.add_ebands_task_to_work(self, ndivsm=self.ndivsm,
tolwfr=self.tolwfr, nb_extra=self.nb_extra)
return self.postpone_on_all_ok()
elif self.iteration_step == 2:
print("in iteration step 2")
self.iteration_step += 1
##### Writing the results in json files #####
self.json_data["meta"] = self.meta
#with self.gs_relax_task.open_gsr() as gsr:
self.json_data["gs_relax_filepath"]=self.gs_relax_task.gsr_path
#with self.ex_relax_task.open_gsr() as gsr:
self.json_data["ex_relax_filepath"]=self.ex_relax_task.gsr_path
if self.four_points == True:
# Get Ag total energy.
#with self.ag_scf_task.open_gsr() as gsr:
self.json_data["Ag_gsr_filepath"] = self.ag_scf_task.gsr_path
# Get Agstar total energy.
#with self.ex_relax_task.open_gsr() as gsr:
self.json_data["Agstar_gsr_filepath"] = self.agstar_scf_task.gsr_path
# Get Aestar total energy.
#with self.aestar_scf_task.open_gsr() as gsr:
self.json_data["Aestar_gsr_filepath"] = self.aestar_scf_task.gsr_path
# Get Aestar total energy.
#with self.ae_scf_task.open_gsr() as gsr:
self.json_data["Ae_gsr_filepath"] = self.ae_scf_task.gsr_path
# Write json file in the outdir of the work
self.write_json_in_outdir("lumi.json", self.json_data)
# Build deltascf results
delta_scf = DeltaSCF.from_four_points_file([self.ag_scf_task.gsr_path,
self.agstar_scf_task.gsr_path,
self.aestar_scf_task.gsr_path,
self.ae_scf_task.gsr_path])
# Create dict with all post-processed results
d = delta_scf.get_dict_results()
# save d in json format.
self.write_json_in_outdir("Delta_SCF.json", d)
return super().on_all_ok()
[docs]
class LumiWork_relaxations(Work):
"""
This Work implements the ground and excited state relaxations only.
The relaxations run simultaneously. No task creation at run-time.
"""
[docs]
@classmethod
def from_scf_inputs(cls, gs_scf_inp, ex_scf_inp, relax_kwargs_gs, relax_kwargs_ex, meta=None, manager=None):
"""
Args:
gs_scf_inp: |AbinitInput| representing a GS SCF run for the ground-state.
ex_scf_inp: |AbinitInput| representing a GS SCF run for the excited-state.
relax_kwargs_gs: Dictonary with input variables to be added to gs_scf_inp
when generating input files for ground state structural relaxations.
relax_kwargs_ex: Dictonary with input variables to be added to ex_scf_inp
when generating input files for excited state structural relaxations.
meta : dict corresponding to the metadata of a lumiwork (supercell size, dopant type,...)
manager: |TaskManager| of the task. If None, the manager is initialized from the config file.
"""
new = cls(manager=manager)
# Templates for GS SCF calculations.
new.gs_scf_inp = gs_scf_inp
new.ex_scf_inp = ex_scf_inp
# Save paramenters for the generation of input files at runtime.
new.relax_kwargs_gs = relax_kwargs_gs
new.relax_kwargs_ex = relax_kwargs_ex
new.meta=meta
# Relaxation for the Ag configuration.
new.gs_relax_task = new.register_relax_task(gs_scf_inp.new_with_vars(relax_kwargs_gs))
new.ex_relax_task = new.register_relax_task(ex_scf_inp.new_with_vars(relax_kwargs_ex))
new.json_data = {}
return new
[docs]
def on_all_ok(self):
"""
This method is called when all the works in the flow have reached S_OK.
"""
self.json_data["meta"] = self.meta
# with self.gs_relax_task.open_gsr() as gsr:
self.json_data["gs_relax_filepath"] = self.gs_relax_task.gsr_path
# with self.ex_relax_task.open_gsr() as gsr:
self.json_data["ex_relax_filepath"] = self.ex_relax_task.gsr_path
# Write json file in the outdir of the work
self.write_json_in_outdir("lumi_relaxations.json", self.json_data)
return super().on_all_ok()
[docs]
class LumiWorkFromRelax(Work):
"""
Same as LumiWork, without the relaxations. Typically used after a LumiWork_relaxations work.
The two relaxed structures (in ground and excited state) are given as input. No creation at run-time
"""
[docs]
@classmethod
def from_scf_inputs(cls, gs_scf_inp, ex_scf_inp, gs_structure, ex_structure, ndivsm=0, nb_extra=10,
tolwfr=1e-12, meta=None ,manager=None):
"""
Args:
gs_scf_inp: |AbinitInput| representing a GS SCF run for the ground-state.
exc_scf_inp: |AbinitInput| representing a GS SCF run for the excited-state.
gs_structure: object representing the relaxed ground state structure
ex_structure: object representing the excited ground state structure
ndivsm: Activates the computation of band structure if different from zero.
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.
nb_extra: Number of extra bands added to the input nband when computing band structures (ndivsm != 0).
tolwfr: Tolerance of the residuals used for the NSCF band structure calculations.
manager: |TaskManager| of the task. If None, the manager is initialized from the config file.
"""
new = cls(manager=manager)
new.meta=meta
new.gs_structure=gs_structure
new.ex_structure=ex_structure
# Templates for GS SCF calculations.
new.gs_scf_inp = gs_scf_inp
new.ex_scf_inp = ex_scf_inp
new.ndivsm = int(ndivsm)
new.tolwfr = tolwfr
new.nb_extra = int(nb_extra)
# JSON-compatible dictionary storing the most important results of the Work. (relaxations and 4 pts results
# are separated)
# Results will be stored in the `lumi_4pts.json` or 'lumi_relax.json file in the outdata directory of the Work
# so that one can easily implement additional post-processing tools.
new.json_data = {}
# Build GS SCF input for the Ag configuration:
# use same structure as Ag but with ground occupation factors
ag_scf_inp = new.gs_scf_inp.new_with_structure(gs_structure)
new.ag_scf_task = new.register_scf_task(ag_scf_inp)
# Build GS SCF input for the Ag* configuration:
# use same structure as Ag but with excited occupation factors.
agstar_scf_inp = new.ex_scf_inp.new_with_structure(gs_structure)
new.agstar_scf_task = new.register_scf_task(agstar_scf_inp)
# Build ex SCF input for the Aestar configuration:
aestar_scf_inp = new.ex_scf_inp.new_with_structure(ex_structure)
new.aestar_scf_task = new.register_scf_task(aestar_scf_inp)
# Build GS SCF input for the Ag* configuration:
# use same structure as Ag but with excited occupation factors.
ae_scf_inp = new.gs_scf_inp.new_with_structure(ex_structure)
new.ae_scf_task = new.register_scf_task(ae_scf_inp)
if new.ndivsm != 0:
# Compute band structure for Ag configuration.
new.ag_scf_task.add_ebands_task_to_work(new, ndivsm=new.ndivsm,
tolwfr=new.tolwfr, nb_extra=new.nb_extra)
# Compute band structure for Ag* configuration.
new.agstar_scf_task.add_ebands_task_to_work(new, ndivsm=new.ndivsm,
tolwfr=new.tolwfr, nb_extra=new.nb_extra)
# Compute band structure for aestar configuration.
new.aestar_scf_task.add_ebands_task_to_work(new, ndivsm=new.ndivsm,
tolwfr=new.tolwfr, nb_extra=new.nb_extra)
# Compute band structure for Ae configuration.
new.ae_scf_task.add_ebands_task_to_work(new, ndivsm=new.ndivsm,
tolwfr=new.tolwfr, nb_extra=new.nb_extra)
return new
[docs]
def on_all_ok(self):
self.json_data["meta"] = self.meta
# Get Ag total energy.
# with self.ag_scf_task.open_gsr() as gsr:
self.json_data["Ag_gsr_filepath"] = self.ag_scf_task.gsr_path
# Get Agstar total energy.
# with self.ex_relax_task.open_gsr() as gsr:
self.json_data["Agstar_gsr_filepath"] = self.agstar_scf_task.gsr_path
# Get Aestar total energy.
# with self.aestar_scf_task.open_gsr() as gsr:
self.json_data["Aestar_gsr_filepath"] = self.aestar_scf_task.gsr_path
# Get Aestar total energy.
# with self.ae_scf_task.open_gsr() as gsr:
self.json_data["Ae_gsr_filepath"] = self.ae_scf_task.gsr_path
# Write json file in the outdir of the work
self.write_json_in_outdir("lumi.json", self.json_data)
# Build deltascf results
delta_scf = DeltaSCF.from_four_points_file([self.ag_scf_task.gsr_path,
self.agstar_scf_task.gsr_path,
self.aestar_scf_task.gsr_path,
self.ae_scf_task.gsr_path])
# Create dict with all post-processed results
d = delta_scf.get_dict_results()
# save d in json format.
self.write_json_in_outdir("Delta_SCF.json", d)
return super().on_all_ok()