diff --git a/abipy/abio/factories.py b/abipy/abio/factories.py index 54737e1ec..3284df01e 100644 --- a/abipy/abio/factories.py +++ b/abipy/abio/factories.py @@ -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", @@ -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 @@ -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}. @@ -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} @@ -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) diff --git a/abipy/abio/inputs.py b/abipy/abio/inputs.py index 48c8be0b7..a41e2095e 100644 --- a/abipy/abio/inputs.py +++ b/abipy/abio/inputs.py @@ -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]) @@ -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. @@ -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. @@ -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.