Skip to content

Commit

Permalink
New factories for atomate2 WFs (#298)
Browse files Browse the repository at this point in the history
* Read SHG AR contrib

* new factories for Atomate2 SHG WF

* update factories.py __all__

* Atomate2 convention: gs_inp -> gs_input

* nband in ddkpert_from_gsinput because of factory_kwrags in atomate2

* Add ONCVPSP LDA v0.4

* test spinat

* modif set_autospinat

* modif for atomate2

* fix EventParser for RelaxConvergenceWarning + quick fix wrapper as_dict pmg_serialize

* remove VT

* remove autoparal 1 of GS from ddk, dde, dte

* activate ONCVPSP-LDA-SR-v0.3

* backward compatibility

* remove comments
  • Loading branch information
VicTrqt authored Oct 14, 2024
1 parent 28f8360 commit 011e6f0
Show file tree
Hide file tree
Showing 2 changed files with 261 additions and 13 deletions.
99 changes: 87 additions & 12 deletions abipy/abio/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
"piezo_elastic_inputs_from_gsinput",
"scf_piezo_elastic_inputs",
"scf_for_phonons",
"ddkpert_from_gsinput",
"ddepert_from_gsinput",
"dtepert_from_gsinput",
"dte_from_gsinput",
"dfpt_from_gsinput",
"minimal_scf_input",
Expand Down Expand Up @@ -1376,8 +1379,80 @@ def scf_for_phonons(structure, pseudos, kppa=None, ecut=None, pawecutdg=None, nb

return abiinput

def ddkpert_from_gsinput(gs_input, ddk_pert, nband=None, use_symmetries=False, ddk_tol=None, manager=None) -> AbinitInput:
"""
Returns an |AbinitInput| to perform a DDK calculations for a specific perturbation and based on a ground state |AbinitInput|.
Args:
gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK.
ddk_pert: dict with the Abinit variables defining the perturbation
Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]},
use_symmetries: boolean that determines if the irreducible components of the perturbation are used.
Default to False. (TODO: Should be implemented)
ddk_tol: a dictionary with a single key defining the type of tolerance used for the DDK calculations and its value. Default: {"tolvrs": 1.0e-22}.
manager: |TaskManager| of the task. If None, the manager is initialized from the config file.
"""
gs_input = gs_input.deepcopy()
gs_input.pop_irdvars()
gs_input.pop_vars(['autoparal', 'npfft'])

if ddk_tol is None:
ddk_tol = {"tolwfr": 1.0e-22}

ddk_inp = gs_input.make_ddkpert_input(perturbation=ddk_pert, use_symmetries=use_symmetries, tolerance=ddk_tol, manager=manager)

# TODO: add to see how it behaves wrt nband from atomate2
#if nband is None:
# nband = _find_nscf_nband_from_gsinput(gs_input)

#ddk_inp.set_vars(nband=nband)

return ddk_inp

def ddepert_from_gsinput(gs_input, dde_pert, use_symmetries=True, dde_tol=None, manager=None) -> AbinitInput:
"""
Returns an |AbinitInput| to perform a DDE calculations for a specific perturbation and based on a ground state |AbinitInput|.
Args:
gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK.
dde_pert: dict with the Abinit variables defining the perturbation
Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]},
use_symmetries: boolean that determines if the irreducible components of the perturbation are used.
Default to True. Should be set to False for nonlinear coefficients calculation.
dde_tol: a dictionary with a single key defining the type of tolerance used for the DDE calculations and
its value. Default: {"tolvrs": 1.0e-22}.
manager: |TaskManager| of the task. If None, the manager is initialized from the config file.
"""
gs_input = gs_input.deepcopy()
gs_input.pop_irdvars()
gs_input.pop_vars(['autoparal', 'npfft'])

if dde_tol is None:
dde_tol = {"tolvrs": 1.0e-22}

dde_inp = gs_input.make_ddepert_input(perturbation=dde_pert, use_symmetries=use_symmetries, tolerance=dde_tol, manager=manager)

return dde_inp

def dtepert_from_gsinput(gs_input, dte_pert, manager=None) -> AbinitInput:
"""
Returns an |AbinitInput| to perform a DTE calculations for a specific perturbation and based on a ground state |AbinitInput|.
def dte_from_gsinput(gs_inp, use_phonons=True, ph_tol=None, ddk_tol=None, dde_tol=None,
Args:
gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK.
dte_pert: dict with the Abinit variables defining the perturbation
Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]},
manager: |TaskManager| of the task. If None, the manager is initialized from the config file.
"""
gs_input = gs_input.deepcopy()
gs_input.pop_irdvars()
gs_input.pop_vars(['autoparal', 'npfft'])

dte_inp = gs_input.make_dtepert_input(perturbation=dte_pert, manager=manager)

return dte_inp

def dte_from_gsinput(gs_input, use_phonons=True, ph_tol=None, ddk_tol=None, dde_tol=None,
skip_dte_permutations=False, manager=None) -> MultiDataset:
"""
Returns a list of inputs in the form of a |MultiDataset| to perform calculations of non-linear properties, based on
Expand All @@ -1387,7 +1462,7 @@ def dte_from_gsinput(gs_inp, use_phonons=True, ph_tol=None, ddk_tol=None, dde_to
All of them have the tag "dfpt".
Args:
gs_inp: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK.
gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK.
use_phonons: determine wether the phonon perturbations at gamma should be included or not
ph_tol: a dictionary with a single key defining the type of tolerance used for the phonon calculations and
its value. Default: {"tolvrs": 1.0e-22}.
Expand All @@ -1400,8 +1475,8 @@ def dte_from_gsinput(gs_inp, use_phonons=True, ph_tol=None, ddk_tol=None, dde_to
duplicated outputs.
manager: |TaskManager| of the task. If None, the manager is initialized from the config file.
"""
gs_inp = gs_inp.deepcopy()
gs_inp.pop_irdvars()
gs_input = gs_input.deepcopy()
gs_input.pop_irdvars()

if ph_tol is None:
ph_tol = {"tolvrs": 1.0e-22}
Expand All @@ -1414,26 +1489,26 @@ def dte_from_gsinput(gs_inp, use_phonons=True, ph_tol=None, ddk_tol=None, dde_to

multi = []

multi_ddk = gs_inp.make_ddk_inputs(tolerance=ddk_tol)
multi_ddk = gs_input.make_ddk_inputs(tolerance=ddk_tol)
multi_ddk.add_tags(atags.DDK)
multi.extend(multi_ddk)
multi_dde = gs_inp.make_dde_inputs(dde_tol, use_symmetries=False, manager=manager)
multi_dde = gs_input.make_dde_inputs(dde_tol, use_symmetries=False, manager=manager)
multi_dde.add_tags(atags.DDE)
multi.extend(multi_dde)

if use_phonons:
multi_ph = gs_inp.make_ph_inputs_qpoint([0, 0, 0], ph_tol, manager=manager)
multi_ph = gs_input.make_ph_inputs_qpoint([0, 0, 0], ph_tol, manager=manager)
multi_ph.add_tags(atags.PH_Q_PERT)
multi.extend(multi_ph)

# non-linear calculations do not accept more bands than those in the valence. Set the correct values.
# Do this as last, so not to interfere with the the generation of the other steps.
nval = gs_inp.structure.num_valence_electrons(gs_inp.pseudos)
nval -= gs_inp['charge']
nval = gs_input.structure.num_valence_electrons(gs_input.pseudos)
nval -= gs_input['charge']
nband = int(round(nval / 2))
gs_inp.set_vars(nband=nband)
gs_inp.pop('nbdbuf', None)
multi_dte = gs_inp.make_dte_inputs(phonon_pert=use_phonons, skip_permutations=skip_dte_permutations,
gs_input.set_vars(nband=nband)
gs_input.pop('nbdbuf', None)
multi_dte = gs_input.make_dte_inputs(phonon_pert=use_phonons, skip_permutations=skip_dte_permutations,
manager=manager)
multi_dte.add_tags(atags.DTE)
multi.extend(multi_dte)
Expand Down
175 changes: 174 additions & 1 deletion abipy/abio/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,11 @@ def runlevel(self):
else:
runlevel.update([atags.MANY_BODY, atags.SIGMA])

elif optdriver == 5:
# DTE run.
runlevel.add(atags.DFPT)
runlevel.add(atags.DTE)

elif optdriver == 99:
# BSE run
runlevel.update([atags.MANY_BODY, atags.BSE])
Expand Down Expand Up @@ -1921,6 +1926,65 @@ def make_ph_inputs_qpoint(self, qpt, tolerance=None,

return ph_inputs

def make_ddkpert_input(self, perturbation, kptopt=2, only_vk=False, use_symmetries=False, tolerance=None, manager=None) -> AbinitInput:
"""
Returns |AbinitInput| for the calculation of an electric field perturbation.
This function should be called with an input that represents a GS run and
an electric field perturbation.
Args:
perturbation: dict with the Abinit variables defining the irreducible perturbation
Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]},
kptopt: 2 to take into account time-reversal symmetry. Note that kptopt 1 is not available.
only_vk: If only matrix elements of the velocity operator are needed.
First-order wavefunctions won't be converged --> not usable for other DFPT calculations.
use_symmetries: boolean that determines if the irreducible components of the perturbation are used.
Default to False. (TODO: Should be implemented)
tolerance: dict {varname: value} with the tolerance to be used in the DFPT run.
Defaults to {"tolwfr": 1.0e-22}.
manager: |TaskManager| of the task. If None, the manager is initialized from the config file.
"""
if tolerance is None:
tolerance = {"tolwfr": 1.0e-22}

if len(tolerance) != 1 or any(k not in _TOLVARS for k in tolerance):
raise self.Error("Invalid tolerance: %s" % str(tolerance))

if "tolvrs" in tolerance:
raise self.Error("tolvrs should not be used in a DDK calculation")

inp = self.deepcopy()
inp.pop_irdvars()

rfdir = 3 * [0]
rfdir[perturbation['idir'] - 1] = 1

inp.set_vars(
rfelfd=2, # Activate the calculation of the d/dk perturbation
# only the derivative of ground-state wavefunctions with respect to k
rfdir=rfdir, # Direction of the ddk.
nqpt=1, # One wavevector is to be considered
qpt=(0, 0, 0), # q-wavevector.
kptopt=kptopt, # 2 to take into account time-reversal symmetry.
iscf=-3, # The d/dk perturbation must be treated in a non-self-consistent way
)

inp.pop_vars("dfpt_sciss") # TODO: to add?

if only_vk:
inp.set_vars(nstep=1, nline=1)

# TODO: to implement
#if not use_symmetries:
# inp.set_vars(
# comment="Input file for ddk calculation without symmetries.",
# )

inp.pop_tolerances()
inp.set_vars(tolerance, comment="Input file for DDK calculation.")

return inp

def make_ddk_inputs(self, tolerance=None, kptopt=2, only_vk=False, manager=None) -> MultiDataset:
"""
Return inputs for performing DDK calculations.
Expand Down Expand Up @@ -2009,7 +2073,56 @@ def make_dkdk_input(self, tolerance=None, kptopt=2, manager=None) -> AbinitInput
dkdk_input.set_vars(tolerance)

return dkdk_input

def make_ddepert_input(self, perturbation, use_symmetries=True, tolerance=None, manager=None) -> AbinitInput:
"""
Returns |AbinitInput| for the calculation of an electric field perturbation.
This function should be called with an input that represents a GS run and
an electric field perturbation.
Args:
perturbation: dict with the Abinit variables defining the irreducible perturbation
Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]},
use_symmetries: boolean that determines if the irreducible components of the perturbation are used.
Default to True. Should be set to False for nonlinear coefficients calculation.
tolerance: dict {varname: value} with the tolerance to be used in the DFPT run.
Defaults to {"tolvrs": 1.0e-22}.
manager: |TaskManager| of the task. If None, the manager is initialized from the config file.
"""
if tolerance is None:
tolerance = {"tolvrs": 1.0e-22}

if len(tolerance) != 1 or any(k not in _TOLVARS for k in tolerance):
raise self.Error("Invalid tolerance: %s" % str(tolerance))

inp = self.deepcopy()
inp.pop_irdvars()

rfdir = 3 * [0]
rfdir[perturbation['idir'] - 1] = 1

inp.set_vars(
rfdir=rfdir, # Direction of the dde perturbation.
comment="Input file for DDE calculation with symmetries.",
rfelfd=3, # Activate the calculation of the electric field perturbation
# Assuming the data on derivative of ground-state wavefunction with respect
# to k (DDK) is available on disk and will be read with getddk/irddk
nqpt=1, # One wavevector is to be considered
qpt=(0, 0, 0), # q-wavevector.
kptopt=2, # Take into account time-reversal symmetry.
)

if not use_symmetries:
inp.set_vars(
comment="Input file for DDE calculation without symmetries.",
prepanl=1,
)

inp.pop_tolerances()
inp.set_vars(tolerance)

return inp

def make_dde_inputs(self, tolerance=None, use_symmetries=True, manager=None) -> MultiDataset:
"""
Return |MultiDataset| inputs for the calculation of electric field perturbations.
Expand Down Expand Up @@ -2074,7 +2187,67 @@ def make_dde_inputs(self, tolerance=None, use_symmetries=True, manager=None) ->

return multi

def make_dte_inputs(self, phonon_pert=False, skip_permutations=False, ixc=7, manager=None) -> MultiDataset:
def make_dtepert_input(self, perturbation, ixc=None, manager=None) -> AbinitInput:
"""
Return |AbinitInput| for DTE calculation for a given perturbation.
This functions should be called with an input that represents a GS run.
Args:
perturbation: dict with the Abinit variables defining the irreducible perturbation
Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]},
ixc: Value of ixc variable. Used to overwrite the default value read from pseudos.
manager: |TaskManager| of the task. If None, the manager is initialized from the config file.
"""
inp = self.deepcopy()
# non-linear calculations do not accept more bands than those in the valence. Set the correct values.
# Do this as last, so not to interfere with the the generation of the other steps.
nval = inp.structure.num_valence_electrons(inp.pseudos)
nval -= inp['charge']
nband = int(round(nval / 2))
inp.set_vars(nband=nband)
inp.pop('nbdbuf', None)

if ixc is not None:
inp.set_vars(ixc=int(ixc))

# See tutorespfn/Input/tnlo_2.in
na = len(self.structure)

rfdir1 = 3 * [0]
rfdir1[perturbation['i1dir'] - 1] = 1
rfdir2 = 3 * [0]
rfdir2[perturbation['i2dir'] - 1] = 1
rfdir3 = 3 * [0]
rfdir3[perturbation['i3dir'] - 1] = 1

# atpol if needed. Since there can be only one spatial perturbation
m = min(perturbation['i1pert'], perturbation['i2pert'], perturbation['i3pert'])
atpol = [m, m] if m <= na else None

inp.set_vars(
# Activate the calculation of the electric field perturbation
d3e_pert1_elfd=1 if perturbation['i1pert'] == na + 2 else 0,
d3e_pert2_elfd=1 if perturbation['i2pert'] == na + 2 else 0,
d3e_pert3_elfd=1 if perturbation['i3pert'] == na + 2 else 0,
d3e_pert1_dir=rfdir1, # Direction of the dte perturbation.
d3e_pert2_dir=rfdir2,
d3e_pert3_dir=rfdir3,
d3e_pert1_phon=1 if perturbation['i1pert'] <= na else 0,
d3e_pert2_phon=1 if perturbation['i2pert'] <= na else 0,
d3e_pert3_phon=1 if perturbation['i3pert'] <= na else 0,
d3e_pert1_atpol=atpol,
nqpt=1, # One wavevector is to be considered
qpt=(0, 0, 0), # q-wavevector.
optdriver=5, # non-linear response functions, using the 2n+1 theorem.
kptopt=2, # Take into account time-reversal symmetry.
comment="Input file for DTE calculation.",
)

inp.pop_tolerances()

return inp

def make_dte_inputs(self, phonon_pert=False, skip_permutations=False, ixc=None, manager=None) -> MultiDataset:
"""
Return |MultiDataset| inputs for DTE calculation.
This functions should be called with an input that represents a GS run.
Expand Down

0 comments on commit 011e6f0

Please sign in to comment.