# -*- coding: utf-8 -*-
"""
Input plug-in for a KKR calculation.
"""
import os
import numpy as np
from aiida.engine import CalcJob
from aiida.orm import CalcJobNode, load_node, RemoteData, Dict, StructureData, KpointsData, Bool, FolderData
from .voro import VoronoiCalculation
from ..tools.common_workfunctions import get_natyp
from aiida.common.utils import classproperty
from aiida.common.exceptions import InputValidationError, ValidationError
from aiida.common.datastructures import CalcInfo, CodeInfo
from aiida.common.exceptions import UniquenessError
from aiida_kkr.tools import (
generate_inputcard_from_structure, check_2Dinput_consistency, vca_check, kick_out_corestates
)
from aiida_kkr.tools.find_parent import find_parent_structure
from aiida_kkr.tools.tools_kkrimp import make_scoef, write_scoef_full_imp_cls
from aiida_kkr.tools.ldau import get_ldaupot_text
from masci_tools.io.common_functions import get_alat_from_bravais, get_Ang2aBohr, search_string, get_ef_from_potfile
from masci_tools.io.kkr_params import __kkr_default_params__, kkrparams
__copyright__ = (u'Copyright (c), 2017, Forschungszentrum Jülich GmbH, '
'IAS-1/PGI-1, Germany. All rights reserved.')
__license__ = 'MIT license, see LICENSE.txt file'
__version__ = '0.13.0'
__contributors__ = ('Jens Bröder', 'Philipp Rüßmann')
verbose = False
[docs]
class KkrCalculation(CalcJob):
"""
AiiDA calculation plugin for a KKR calculation.
"""
# calculation plugin version
_CALCULATION_PLUGIN_VERSION = __version__
# Default input and output files
_DEFAULT_INPUT_FILE = 'inputcard' # will be shown with inputcat
_DEFAULT_OUTPUT_FILE = 'out_kkr' # verdi shell output will be shown with outputcat
# same as _DEFAULT_OUTPUT_FILE: piped output of kkr execution to this file
_OUTPUT_FILE_NAME = _DEFAULT_OUTPUT_FILE
# List of mandatory input files
_INPUT_FILE_NAME = _DEFAULT_INPUT_FILE
_POTENTIAL = 'potential'
# List of optional input files (may be mandatory for some settings in inputcard)
_SHAPEFUN = 'shapefun' # mandatory if nonspherical calculation
_SCOEF = 'scoef' # mandatory for KKRFLEX calculation and some functionalities
_BFIELD = 'bfield.dat' # mandatory if <NONCOBFIELD>= True
_NONCO_ANGLES = 'nonco_angle.dat' # mandatory if noncollinear directions are used that are not (theta, phi)= (0,0) for all atoms
_NONCO_ANGLES_IMP = 'nonco_angle_imp.dat' # mandatory for GREENIMP option (scattering code)
_SHAPEFUN_IMP = 'shapefun_imp' # mandatory for GREENIMP option (scattering code)
_POTENTIAL_IMP = 'potential_imp' # mandatory for GREENIMP option (scattering code)
# List of output files that should always be present
_OUT_POTENTIAL = 'out_potential'
_OUTPUT_0_INIT = 'output.0.txt'
_OUTPUT_000 = 'output.000.txt'
_OUTPUT_2 = 'output.2.txt'
_OUT_TIMING_000 = 'out_timing.000.txt'
_NONCO_ANGLES_OUT = 'nonco_angle_out.dat'
_NONCO_ANGLES_ALL_ITER = 'nonco_angle_out_all_iter.dat'
# special files (some runs)
# DOS files
_COMPLEXDOS = 'complex.dos'
_DOS_ATOM = 'dos.atom%i'
_LMDOS = 'lmdos.%2i.%i.dat'
# qdos files
_QVEC = 'qvec.dat'
_QDOS_ATOM = 'qdos.%3i.%i.dat'
_QDOS_ATOM_OLD = 'qdos.%2i.%i.dat'
_QDOS_SX = 'qdos_sx.%2i.dat'
_QDOS_SY = 'qdos_sy.%2i.dat'
_QDOS_SZ = 'qdos_sz.%2i.dat'
# kkrflex files for impurity calculation
_KKRFLEX_GREEN = 'kkrflex_green'
_KKRFLEX_TMAT = 'kkrflex_tmat'
_KKRFLEX_ATOMINFO = 'kkrflex_atominfo'
_KKRFLEX_INTERCELL_REF = 'kkrflex_intercell_ref'
_KKRFLEX_INTERCELL_CMOMS = 'kkrflex_intercell_cmoms'
_ALL_KKRFLEX_FILES = [
_KKRFLEX_GREEN,
_KKRFLEX_TMAT,
_KKRFLEX_ATOMINFO,
_KKRFLEX_INTERCELL_REF,
_KKRFLEX_INTERCELL_CMOMS,
]
# Jij files
_Jij_ATOM = 'Jij.atom%0.5i'
_SHELLS_DAT = 'shells.dat'
# deci-out and decimation
_DECIFILE = 'decifile'
# BdG mode
_BDG_POT = 'den_lm_ir.%0.3i.%i.txt'
_BDG_CHI_NS = 'den_lm_ns_*.npy'
# LDA+U
_LDAUPOT = 'ldaupot'
# template.product entry point defined in setup.json
_default_parser = 'kkr.kkrparser'
# list of keywords that are not allowed to be modified (new calculation
# starting from structure and voronoi run is needed instead):
_do_never_modify = [
'ALATBASIS',
'BRAVAIS',
'NAEZ',
'<RBASIS>',
'CARTESIAN',
'INTERFACE',
'<NLBASIS>',
'<RBLEFT>',
'ZPERIODL',
'<NRBASIS>',
'<RBRIGHT>',
'ZPERIODR',
'KSHAPE',
'<SHAPE>',
'<ZATOM>',
'NATYP',
'<SITE>',
'<CPA-CONC>',
'<KAOEZL>',
'<KAOEZR>',
]
#TODO implement workfunction to modify structure (e.g. to use VCA)
# small number used to check for equivalence
_eps = 10**-12
[docs]
@classmethod
def define(cls, spec):
"""
Init internal parameters at class load time
"""
# reuse base class function
super(KkrCalculation, cls).define(spec)
# now define input files and parser
spec.input(
'metadata.options.parser_name',
valid_type=str,
default=cls._default_parser,
non_db=True,
)
spec.input('metadata.options.input_filename', valid_type=str, default=cls._DEFAULT_INPUT_FILE, non_db=True)
spec.input('metadata.options.output_filename', valid_type=str, default=cls._DEFAULT_OUTPUT_FILE, non_db=True)
# define input nodes (optional ones have required=False)
spec.input(
'parameters',
valid_type=Dict,
required=True,
help='Use a node that specifies the input parameters',
)
spec.input(
'parent_folder',
valid_type=RemoteData,
required=True,
help="""
Use a remote or local repository folder as parent folder
(also for restarts and similar). It should contain all the needed
files for a KKR calc, only edited files should be uploaded from the
repository.
"""
)
spec.input(
'impurity_info',
valid_type=Dict,
required=False,
help="""
Use a Parameter node that specifies properties for a following
impurity calculation (e.g. setting of impurity cluster in scoef
file that is automatically created)."""
)
spec.input(
'kpoints',
valid_type=KpointsData,
required=False,
help="""
Use a KpointsData node that specifies the kpoints for which a
bandstructure (i.e. 'qdos') calculation should be performed."""
)
spec.input(
'initial_noco_angles',
valid_type=Dict,
required=False,
help="""
Initial non-collinear angles for the magnetic moments of
the impurities. These values will be written into the
`kkrflex_angle` input file of KKRimp.
The Dict node should be of the form
initial_noco_angles = Dict(dict={
'theta': [theta_at1, theta_at2, ..., theta_atN],
# list theta values in degrees (0..180)
'phi': [phi_at1, phi_at2, ..., phi_atN],
# list phi values in degrees (0..360)
'fix_dir': [True/False at_1, ..., True/False at_N]
# list of booleans indicating if the direction of the magnetic
# moment should be fixed or is allowed relax (True means keep the
# direction of the magnetic moment fixed)
})
Note: The length of the theta, phi and fix_dir lists have to be
equal to the number of atoms.
"""
)
spec.input(
'bfield',
valid_type=Dict,
required=False,
help="""
Non-collinear exteral B-field used for constraint calculations.
The Dict node should be of the form
initial_noco_angles = Dict(dict={
'theta': [theta_at1, theta_at2, ..., theta_atN],
# list theta values in degrees (0..180)
'phi': [phi_at1, phi_at2, ..., phi_atN],
# list phi values in degrees (0..360)
'magnitude': [magnitude at_1, ..., magnitude at_N]
# list of magnitude of the applied fields in Ry units
})
Note: The length of the theta, phi and magnitude lists have to be
equal to the number of atoms.
"""
)
spec.input(
'deciout_parent',
valid_type=RemoteData,
required=False,
help='KkrCalculation RemoteData folder from deci-out calculation'
)
spec.input(
'retrieve_kkrflex',
valid_type=Bool,
required=False,
default=lambda: Bool(True),
help="""
For a GF writeout calculation, determine whether or not
the kkrflex_* files are copied to the retrieved (can clutter the
database) or are ony left in the remote folder.
"""
)
spec.input(
'anomalous_density',
valid_type=FolderData,
required=False,
help="""
FolderData that contains anomalous density input files for
the KKRhost BdG calculation. If these are not give the code looks
for them in the retrieved of the parent calculation and takes them
from there."""
)
spec.input(
'settings_LDAU',
valid_type=Dict,
required=False,
help="""
Settings for running a LDA+U calculation. The Dict node should be of the form
settings_LDAU = Dict(dict={'iatom=0':{
'L': 3, # l-block which gets U correction (1: p, 2: d, 3: f-electrons)
'U': 7., # U value in eV
'J': 0.75, # J value in eV
'Eref_EF': 0., # reference energy in eV relative to the Fermi energy. This is the energy where the projector wavefunctions are calculated (should be close in energy where the states that are shifted lie (e.g. for Eu use the Fermi energy))
}})
Note: you can add multiple entries like the one for iatom==0 in this example. The atom index refers to the corresponding atom in the impurity cluster.
"""
)
# define outputs
spec.output(
'output_parameters',
valid_type=Dict,
required=True,
help='results of the KKR calculation',
)
spec.default_output_node = 'output_parameters'
# define exit codes, also used in parser
spec.exit_code(
301,
'ERROR_NO_OUTPUT_FILE',
message='KKR output file not found',
)
spec.exit_code(
302,
'ERROR_KKR_PARSING_FAILED',
message='KKR parser retuned an error',
)
spec.exit_code(
303,
'ERROR_NO_SHAPEFUN_FOUND',
message='Could not find shapefun from voronoi parent',
)
[docs]
def prepare_for_submission(self, tempfolder):
"""
Create input files.
:param tempfolder: aiida.common.folders.Folder subclass where
the plugin should put all its files.
:param inputdict: dictionary of the input nodes as they would
be returned by get_inputs_dict
"""
########################################
### Create input files in tempfolder ###
# get mandatory input nodes
parameters = self.inputs.parameters
code = self.inputs.code
parent_calc_folder = self.inputs.parent_folder
# extract parent calculation from input remote folder
parent_calc = self._get_parent_calc(parent_calc_folder)
# check if no parameters are overwritten that should not be set manually
self._check_illegal_parameter_overwrite(parent_calc, parameters)
# get structure and related inputs from parent voronoi calculation
voro_parent, structure, vca_structure, use_alat_input = self._get_structure_inputs(parent_calc, parameters)
# prepare scoef file if impurity_info was given
parameters = self._write_scoef_file(tempfolder, parameters, structure, use_alat_input)
# qdos option, ensure low T, E-contour, qdos run option and write qvec.dat file
if 'kpoints' in self.inputs:
parameters = self._prepare_qdos_calc(parameters, self.inputs.kpoints, structure, tempfolder, use_alat_input)
# write nonco_angle.dat file and adapt RUNOPTS if needed (i.e. add FIXMOM if directions are not relaxed)
if 'initial_noco_angles' in self.inputs:
parameters = self._use_initial_noco_angles(parameters, structure, tempfolder)
# write bfield.dat file and add '<NONCOBFIELD>= True' to input parameters
if 'bfield' in self.inputs:
parameters = self._use_nonco_bfield(parameters, structure, tempfolder)
# activate decimation mode and copy decifile from deciout parent
if 'deciout_parent' in self.inputs:
parameters = self._use_decimation(parameters, tempfolder)
# Prepare inputcard from Structure and input parameter data
shapes = self._get_shapes_array(parent_calc, voro_parent)
with tempfolder.open(self._INPUT_FILE_NAME, u'w') as input_file:
natom, nspin, newsosol, warnings_write_inputcard = generate_inputcard_from_structure(
parameters,
structure,
input_file,
parent_calc,
shapes=shapes,
vca_structure=vca_structure,
use_input_alat=use_alat_input
)
#################################################
### Copy input files from parent calculations ###
# add BdG input potential if it is there in the parent calculation output
self._copy_BdG_pot(parent_calc.outputs.retrieved, tempfolder)
# get the local copy list (files copied from parent's retrieved to tempfolder at submission)
local_copy_list = self._get_local_copy_list(parent_calc, voro_parent, tempfolder, parameters)
# TODO use remote_copy_list or even symlinks if we can copy files between remote working dirs
#############################################################################
### Get list of files that we want to retrieve after the calculation ends ###
# Retrieve list needs some logic, retrieve certain files,
# only if certain input keys are specified....
retrieve_list = self._get_default_retrieve_list()
# for special cases add files to retireve list:
# 1. dos calculation, add *dos* files if NPOL==0
retrieve_list += self._get_dos_filelist(parameters, natom, nspin)
# 2. KKRFLEX calculation
retrieve_list += self._get_kkrflex_filelist(parameters)
# 3. qdos calculation output
retrieve_list += self._get_qdos_filelist(parameters, natom, nspin)
# 4. Jij calculation outputs
retrieve_list += self._get_Jij_filelist(parameters, natom)
# 5. deci-out (input file for decimation calculation)
retrieve_list += self._get_deci_file(parameters)
# 6. BdG output files (anomalous density and (q)dos files with _eh etc. endings)
retrieve_list += self._get_BdG_filelist(parameters, natom, nspin)
# 7. retrieve LDA+U potential
# write ldaupot file and change retrieved list for LDA+U calculation
# this is triggered with having the settings_LDAU dict node in input.
retrieve_list += self._init_ldau(tempfolder, parent_calc, natom)
####################################################
### Collect all inputs and put into the CalcInfo ###
# Prepare CalcInfo to be returned to aiida
calcinfo = CalcInfo()
calcinfo.uuid = self.uuid
calcinfo.local_copy_list = local_copy_list
calcinfo.remote_copy_list = []
calcinfo.retrieve_list = retrieve_list
# now set calcinfo and return
codeinfo = CodeInfo()
codeinfo.cmdline_params = []
codeinfo.code_uuid = code.uuid
codeinfo.stdout_name = self._DEFAULT_OUTPUT_FILE
calcinfo.codes_info = [codeinfo]
return calcinfo
[docs]
def _get_parent_calc(self, parent_calc_folder):
"""Get the parent calculation from the remote folder that is given as input"""
parent_calcs = parent_calc_folder.get_incoming(node_class=CalcJobNode)
# check if parent is unique
n_parents = len(parent_calcs.all_link_labels())
if n_parents != 1:
raise UniquenessError(
'Input RemoteData is child of {} '
'calculation{}, while it should have a single parent'
''.format(n_parents, '' if n_parents == 0 else 's')
)
# TODO change to exit code
parent_calc = parent_calcs.first().node
return parent_calc
[docs]
def _check_illegal_parameter_overwrite(self, parent_calc, parameters):
"""check if no keys are illegally overwritten (i.e. compare with keys in self._do_never_modify)"""
# extract parent input parameter dict for following check
try:
parent_inp_dict = parent_calc.inputs.parameters.get_dict()
except:
self.logger.error(f'Failed trying to find input parameter of parent {parent_calc}')
raise InputValidationError('No parameter node found of parent calculation.')
# now go though list of parameters which are overwritten and check if that is forbidden
for key in list(parameters.get_dict().keys()):
value = parameters.get_dict()[key]
#self.logger.info("Checking {} {}".format(key, value))
if value is not None:
if key in self._do_never_modify:
oldvalue = parent_inp_dict[key]
try:
if oldvalue is None and key in __kkr_default_params__:
oldvalue = __kkr_default_params__.get(key)
if value == oldvalue:
values_eqivalent = True
else:
values_eqivalent = False
# check if values match up to certain numerical accuracy
if type(value) == float:
if abs(value - oldvalue) < self._eps:
values_eqivalent = True
elif type(value) == list or type(value) == np.ndarray:
tmp_value, tmp_oldvalue = np.array(value).reshape(-1), np.array(oldvalue).reshape(-1)
values_eqivalent_tmp = []
for ival in range(len(tmp_value)):
if abs(tmp_value[ival] - tmp_oldvalue[ival]) < self._eps:
values_eqivalent_tmp.append(True)
else:
values_eqivalent_tmp.append(False)
if all(values_eqivalent_tmp) and len(value) == len(oldvalue):
values_eqivalent = True
except:
raise InputValidationError(
f'Error while trying to compare old and new values with key={key} in do_never_modify list, oldval={oldvalue}; newval={value}'
)
if not values_eqivalent:
self.logger.error(
f'You are trying to set keyword {key} = {value} but this is not allowed since the structure would be modified. Please use a suitable workfunction instead.'
)
raise InputValidationError(
f'You are trying to modify a keyword that is not allowed to be changed! (key={key}, oldvalue={oldvalue}, newvalue={value})'
)
[docs]
def _write_scoef_file(self, tempfolder, parameters, structure, use_alat_input):
"""Write the scoef file for KKRFLEX writeout"""
# for GF writeout
if 'impurity_info' in self.inputs:
imp_info = self.inputs.impurity_info
found_imp_info = True
else:
imp_info = None
found_imp_info = False
write_scoef = False
runopt = parameters.get_dict().get('RUNOPT', None)
if found_imp_info:
self.logger.info('Found impurity_info in inputs of the calculation, automatically add runopt KKRFLEX')
write_scoef = True
change_values = []
if runopt is None:
runopt = []
runopt = [i.strip() for i in runopt]
if 'KKRFLEX' not in runopt:
runopt.append('KKRFLEX')
change_values.append(['RUNOPT', runopt])
parameters = _update_params(parameters, change_values)
elif runopt is not None and 'KKRFLEX' in runopt:
# if we end up here there is a problem with the input
self.logger.info('Need to write scoef file but no impurity_info given!')
raise ValidationError('Found RUNOPT KKRFLEX but no impurity_info in inputs')
if found_imp_info and write_scoef:
imp_info_dict = imp_info.get_dict()
# find alat input if needed
if use_alat_input:
alat_input = parameters.get_dict().get('ALATBASIS', None) / get_Ang2aBohr()
self.logger.info('alat_input is ' + str(alat_input))
else:
self.logger.info('alat_input is None')
alat_input = None
# create scoef file
if 'imp_cls' not in imp_info_dict:
# this means cluster is found from parameters in imp_info
# extract cluster settings
Rcut = imp_info_dict.get('Rcut', None)
hcut = imp_info_dict.get('hcut', -1.)
cylinder_orient = imp_info_dict.get('cylinder_orient', [0., 0., 1.])
ilayer_center = imp_info_dict.get('ilayer_center', 0)
for i in range(len(cylinder_orient)):
try:
len(cylinder_orient[i])
vec_shape = False
except TypeError:
vec_shape = True
# some consistency checks
if ilayer_center > len(structure.sites) - 1:
raise IndexError(
f'Index of the reference site is out of range! Possible values: 0 to {len(structure.sites) - 1}.'
)
elif Rcut < 0:
raise ValueError('Cutoff radius has to be positive!')
if not vec_shape or len(cylinder_orient) != 3:
raise TypeError(
f'Input orientation vector ({cylinder_orient}) has the wrong shape! It needs to be a 3D-vector!'
)
# now write scoef file
with tempfolder.open(self._SCOEF, 'w') as scoef_file:
make_scoef(
structure,
Rcut,
scoef_file,
hcut,
cylinder_orient,
ilayer_center,
alat_input,
)
else:
# this means the full imp cluster is given in the input
# TODO add some consistency checks with structure etc.
with tempfolder.open(self._SCOEF, 'w') as scoef_file:
if alat_input is not None:
alat = get_alat_from_bravais(np.array(structure.cell), structure.pbc[2])
rescale_alat = alat / alat_input
self.report(f'INFO: rescaling imp cls due to alat_input: {rescale_alat}')
else:
rescale_alat = None
write_scoef_full_imp_cls(imp_info, scoef_file, rescale_alat)
return parameters
[docs]
def _get_shapes_array(self, parent_calc, voro_parent):
"""Get the shapes array from the parent calcualtion or the voronoi parent"""
# set shapes array either from parent voronoi run
if parent_calc.process_label == 'VoronoiCalculation' or parent_calc.process_label == 'KkrCalculation':
# get shapes array from voronoi parent
shapes = voro_parent.outputs.output_parameters.get_dict().get('shapes')
else:
# extract shapes from input parameters node constructed by kkrimporter calculation
shapes = voro_parent.inputs.parameters.get_dict().get('<SHAPE>')
self.logger.info(f'Extracted shapes: {shapes}')
return shapes
[docs]
def _get_local_copy_list(self, parent_calc, voro_parent, tempfolder, parameters):
"""Decide what files to copy based on settings to the code (e.g. KKRFLEX option needs scoef)"""
# copy the right files #TODO check first if file, exists and throw
# warning, now this will throw an error
outfolder = parent_calc.outputs.retrieved
copylist = []
if parent_calc.process_class == KkrCalculation:
copylist = [self._OUT_POTENTIAL]
# TODO ggf copy remotely from remote node if present ...
elif parent_calc.process_class == VoronoiCalculation:
copylist = [parent_calc.process_class._SHAPEFUN]
# copy either overwrite potential or voronoi output potential
# (voronoi caclualtion retreives only one of the two)
if parent_calc.process_class._POTENTIAL_IN_OVERWRITE in outfolder.list_object_names():
copylist.append(parent_calc.process_class._POTENTIAL_IN_OVERWRITE)
else:
copylist.append(parent_calc.process_class._OUT_POTENTIAL_voronoi)
else: # (if parent_calc.process_class == KkrImporterCalculation:)
#change copylist in case the calculation starts from an imported calculation
if self._OUT_POTENTIAL in outfolder.list_object_names():
copylist.append(self._OUT_POTENTIAL)
else:
copylist.append(self._POTENTIAL)
if self._SHAPEFUN in outfolder.list_object_names():
copylist.append(self._SHAPEFUN)
# create local_copy_list from copylist and change some names automatically
local_copy_list = []
for file1 in copylist:
# deal with special case that file is written to another name
if (
file1 == 'output.pot' or file1 == self._OUT_POTENTIAL or (
parent_calc.process_class == VoronoiCalculation and
file1 == parent_calc.process_class._POTENTIAL_IN_OVERWRITE
)
):
filename = self._POTENTIAL
else:
filename = file1
# now add to copy list
local_copy_list.append((outfolder.uuid, file1, filename))
# add shapefun file from voronoi parent if needed
if self._SHAPEFUN not in copylist:
try:
struc, voro_parent = find_parent_structure(parent_calc)
except ValueError:
return self.exit_codes.ERROR_NO_SHAPEFUN_FOUND # pylint: disable=no-member
# copy shapefun from retrieved of voro calc
voro_retrieved = voro_parent.outputs.retrieved
local_copy_list.append((voro_retrieved.uuid, VoronoiCalculation._SHAPEFUN, self._SHAPEFUN))
# check if core state lie within energy contour and take them out if needed
local_copy_list = self._kick_out_corestates_kkrhost(local_copy_list, tempfolder)
# for set-ef option (needs to be done AFTER kicking out core states):
ef_set = parameters.get_dict().get('ef_set', None)
ef_set = parameters.get_dict().get('EF_SET', ef_set)
if verbose:
set_values = kkrparams(**parameters.get_dict()).get_set_values()
self.report(
f"efset: {ef_set} efset_1: {parameters.get_dict().get('ef_set')} efset_2: {parameters.get_dict().get('EF_SET')} params: {set_values}"
)
if ef_set is not None:
local_copy_list = self._set_ef_value_potential(ef_set, local_copy_list, tempfolder)
# TODO different copy lists, depending on the keywors input
self.logger.info(f'local copy list: {local_copy_list}')
return local_copy_list
[docs]
def _get_default_retrieve_list(self):
"""initialize retrieve list with these files"""
ret_list = [
self._DEFAULT_OUTPUT_FILE,
self._INPUT_FILE_NAME,
self._SCOEF,
self._NONCO_ANGLES_OUT,
self._NONCO_ANGLES_ALL_ITER,
self._OUT_POTENTIAL,
self._OUTPUT_0_INIT,
self._OUTPUT_000,
self._OUTPUT_2,
self._OUT_TIMING_000,
]
return ret_list
[docs]
def _get_dos_filelist(self, parameters, natom, nspin, addition=''):
"""return file list of DOS output files to add to retrieve list """
retrieve_dos_files = False
add_files = []
if 'NPOL' in list(parameters.get_dict().keys()):
if parameters.get_dict()['NPOL'] == 0:
retrieve_dos_files = True
if 'TESTOPT' in list(parameters.get_dict().keys()):
testopts = parameters.get_dict()['TESTOPT']
if testopts is not None:
stripped_test_opts = [i.strip() for i in testopts]
if 'DOS' in stripped_test_opts:
retrieve_dos_files = True
if retrieve_dos_files:
if verbose:
self.report(f'adding files for dos output: {[self._COMPLEXDOS, self._DOS_ATOM, self._LMDOS]}')
add_files = [self._COMPLEXDOS + addition]
for iatom in range(natom):
add_files.append(self._DOS_ATOM % (iatom + 1) + addition)
for ispin in range(nspin):
add_files.append((self._LMDOS % (iatom + 1, ispin + 1)).replace(' ', '0') + addition)
return add_files
[docs]
def _get_kkrflex_filelist(self, parameters):
"""Add kkrflex_* files to the retrieve list if needed"""
retrieve_kkrflex_files = False
add_files = []
if 'RUNOPT' in list(parameters.get_dict().keys()):
runopts = parameters.get_dict()['RUNOPT']
if runopts is not None:
stripped_run_opts = [i.strip() for i in runopts]
if 'KKRFLEX' in stripped_run_opts:
retrieve_kkrflex_files = True
if retrieve_kkrflex_files:
if 'retrieve_kkrflex' in self.inputs and self.inputs.retrieve_kkrflex.value:
# retrieve all kkrflex files
add_files = self._ALL_KKRFLEX_FILES
else:
# do not retrieve kkrflex_tmat and kkrflex_green, they are kept on the remote and used from there
add_files = [self._KKRFLEX_ATOMINFO, self._KKRFLEX_INTERCELL_REF, self._KKRFLEX_INTERCELL_CMOMS]
return add_files
[docs]
def _get_qdos_filelist(self, parameters, natom, nspin, addition=''):
"""Add qdos output files to retrieve list if needed"""
retrieve_qdos_files = False
add_files = []
if 'RUNOPT' in list(parameters.get_dict().keys()):
runopts = parameters.get_dict()['RUNOPT']
if runopts is not None:
stripped_run_opts = [i.strip() for i in runopts]
if 'qdos' in stripped_run_opts:
retrieve_qdos_files = True
if retrieve_qdos_files:
add_files = [self._QVEC]
for iatom in range(natom):
for ispin in range(nspin):
add_files.append((self._QDOS_ATOM % (iatom + 1, ispin + 1)).replace(' ', '0') + addition)
# try to retrieve both old and new version of the files
add_files.append((self._QDOS_ATOM_OLD % (iatom + 1, ispin + 1)).replace(' ', '0') + addition)
# retrieve also qdos_sx,y,z files if written out
add_files.append((self._QDOS_SX % (iatom + 1)).replace(' ', '0') + addition)
add_files.append((self._QDOS_SY % (iatom + 1)).replace(' ', '0') + addition)
add_files.append((self._QDOS_SZ % (iatom + 1)).replace(' ', '0') + addition)
return add_files
[docs]
def _get_Jij_filelist(self, parameters, natom):
"""Add Jij output files to retrieve list"""
retrieve_Jij_files = False
add_files = []
if 'RUNOPT' in list(parameters.get_dict().keys()):
runopts = parameters.get_dict()['RUNOPT']
if runopts is not None:
stripped_run_opts = [i.strip() for i in runopts]
if 'XCPL' in stripped_run_opts:
retrieve_Jij_files = True
if retrieve_Jij_files:
add_files = [self._SHELLS_DAT] + [self._Jij_ATOM % iatom for iatom in range(1, natom + 1)]
return add_files
[docs]
def _get_deci_file(self, parameters):
"""Add deci-out file to retrieve list"""
retrieve_decifile = False
add_files = []
if 'RUNOPT' in list(parameters.get_dict().keys()):
runopts = parameters.get_dict()['RUNOPT']
if runopts is not None:
stripped_run_opts = [i.strip() for i in runopts]
if 'deci-out' in stripped_run_opts:
retrieve_decifile = True
if retrieve_decifile:
add_files = [self._DECIFILE]
return add_files
[docs]
def _get_BdG_filelist(self, parameters, natom, nspin):
"""Add list of BdG output files to retrieve list"""
retrieve_BdG_files = False
add_files = []
use_BdG_dict = {
k.upper().replace('<', '').replace('>', ''): v
for k, v in parameters.get_dict().items()
if 'USE_BDG' == k.upper().replace('<', '').replace('>', '')
}
retrieve_BdG_files = use_BdG_dict.get('USE_BDG', False)
if verbose:
self.report(f'retrieve BdG? {retrieve_BdG_files}')
if retrieve_BdG_files:
# anomalous density files for all atoms if present
for iatom in range(natom):
for ispin in range(nspin):
print('adding files for BdG mode')
add_files += [self._BDG_POT % (iatom + 1, ispin + 1)]
# radially-averaged anomalous density matrix (for triplet components etc.)
add_files.append(self._BDG_CHI_NS)
#also retrieve BdG DOS files for anomalous density and hole part
for BdGadd in ['_eh', '_he', '_hole']:
add_files += self._get_dos_filelist(parameters, natom, nspin, BdGadd)
add_files += self._get_qdos_filelist(parameters, natom, nspin, BdGadd)
return add_files
[docs]
def _set_parent_remotedata(self, remotedata):
"""
Used to set a parent remotefolder in the restart of fleur.
"""
if not isinstance(remotedata, RemoteData):
raise ValueError('remotedata must be a RemoteData')
# complain if another remotedata is already found
input_remote = self.get_inputs(node_type=RemoteData)
if input_remote:
raise ValidationError('Cannot set several parent calculation to a KKR calculation')
self.use_parent_folder(remotedata)
[docs]
def _set_ef_value_potential(self, ef_set, local_copy_list, tempfolder):
"""
Set EF value ef_set in the potential file.
"""
self.report(f'local copy list before change: {local_copy_list}')
self.report("found 'ef_set' in parameters: change EF of potential to this value")
# first read old potential
if self._POTENTIAL in tempfolder.get_content_list():
has_potfile = True
else:
has_potfile = False
self.report(f'has_potfile? {has_potfile}')
txt = []
if has_potfile:
# this is the case if we kicked out core states before
with tempfolder.open(self._POTENTIAL, 'r') as potfile:
# read potential
txt = potfile.readlines()
if not has_potfile or len(txt) == 0:
# this is the case when we take the potential from an existing folder
potcopy_info = [i for i in local_copy_list if i[2] == self._POTENTIAL][0]
with load_node(potcopy_info[0]).open(potcopy_info[1]) as potfile:
# remove previous output potential from copy list
local_copy_list.remove(potcopy_info)
# read potential
txt = potfile.readlines()
self.report(f'len(potfile)? {len(txt)}')
# now change value of Fermi level in potential text
potstart = []
for iline in range(len(txt)):
line = txt[iline]
if 'exc:' in line:
potstart.append(iline)
for ipotstart in potstart:
self.report(f'set ef {ef_set} in potential starting in line {ipotstart}')
tmpline = txt[ipotstart + 3]
tmpline = tmpline.split()
newline = f'{float(tmpline[0]):10.5f}{ef_set:20.14f}{float(tmpline[-1]):20.14f}\n'
txt[ipotstart + 3] = newline
# now (over)writing potential file in tempfolder with changed Fermi energy
with tempfolder.open(self._POTENTIAL, 'w') as pot_new_ef:
# write new file
pot_new_ef.writelines(txt)
# now this directory (tempfolder) contains the updated potential file
# thus it is not needed to put it in the local copy list anymore
# return updated local_copy_list
return local_copy_list
[docs]
def _kick_out_corestates_kkrhost(self, local_copy_list, tempfolder):
"""
Compare value of core states from potential file in local_copy_list with EMIN
and kick corestate out of potential if they lie inside the energy contour.
"""
# read EMIN value from inputcard
params = kkrparams()
with tempfolder.open(self._INPUT_FILE_NAME) as input_file:
params.read_keywords_from_inputcard(input_file)
emin = params.get_value('EMIN')
# run kick_out_corestates routine to remove core states that lie above emin
potcopy_info = [i for i in local_copy_list if i[2] == self._POTENTIAL][0]
with tempfolder.open(self._POTENTIAL, 'w') as potfile_out:
with load_node(potcopy_info[0]).open(potcopy_info[1]) as potfile_in:
num_deleted = kick_out_corestates(potfile_in, potfile_out, emin)
# remove changed potential from local copy list (already in tempfolder without overlapping core states)
if num_deleted > 0:
local_copy_list.remove(potcopy_info)
else:
# remove temporarily created file
tempfolder.remove_path(self._POTENTIAL)
# return updated local_copy_list
return local_copy_list
[docs]
def _prepare_qdos_calc(self, parameters, kpath, structure, tempfolder, use_alat_input):
"""
prepare a qdos (i.e. bandstructure) calculation, can only be done if k-points are given in input
Note: this changes some settings in the parameters to ensure a DOS contour and low smearing temperature
Also the qvec.dat file is written here.
"""
# check qdos settings
change_values = []
runopt = parameters.get_dict().get('RUNOPT')
if runopt is None:
runopt = []
runopt = [i.strip() for i in runopt]
if 'qdos' not in runopt:
runopt.append('qdos')
change_values.append(['RUNOPT', runopt])
tempr = parameters.get_dict().get('TEMPR')
if tempr is None or tempr > 100.:
change_values.append(['TEMPR', 50.])
N1 = parameters.get_dict().get('NPT1')
if N1 is None or N1 > 0:
change_values.append(['NPT1', 0])
N2 = parameters.get_dict().get('NPT2')
if N2 is None:
change_values.append(['NPT2', 100])
N3 = parameters.get_dict().get('NPT3')
if N3 is None or N3 > 0.:
change_values.append(['NPT3', 0])
NPOL = parameters.get_dict().get('NPOL')
if NPOL is None or NPOL > 0.:
change_values.append(['NPOL', 0])
parameters = _update_params(parameters, change_values)
# write qvec.dat file
kpath_array = kpath.get_kpoints(cartesian=True)
# convert automatically to internal units
alat = get_alat_from_bravais(np.array(structure.cell), is3D=structure.pbc[2]) * get_Ang2aBohr()
if use_alat_input:
alat_input = parameters.get_dict().get('ALATBASIS')
else:
alat_input = alat
kpath_array = kpath_array * (alat_input / alat) / get_Ang2aBohr() / (2 * np.pi / alat)
# now write file
qvec = [f'{len(kpath_array)}\n']
qvec += [f'{kpt[0]:e} {kpt[1]:e} {kpt[2]:e}\n' for kpt in kpath_array]
with tempfolder.open(self._QVEC, 'w') as qvecfile:
qvecfile.writelines(qvec)
return parameters
[docs]
def _use_initial_noco_angles(self, parameters, structure, tempfolder):
"""
Set starting values for non-collinear calculation (writes nonco_angle.dat to tempfolder).
Adapt FIXMOM runopt according to fix_dir input in initial_noco_angle input node
"""
self.report('Found `initial_noco_angles` input node, writing nonco_angle.dat file')
# extract fix_dir flag and set FIXMOM RUNOPT in parameters accordingly
fix_dir = self.inputs.initial_noco_angles['fix_dir']
natom = get_natyp(structure)
if len(fix_dir) != natom:
raise InputValidationError(
'Error: `fix_dir` list in `initial_noco_angles` input node needs to have the same length as number of atoms!'
)
change_values = []
runopt = parameters.get_dict().get('RUNOPT')
if runopt is None:
runopt = []
runopt = [i.strip() for i in runopt]
if all(fix_dir) and 'FIXMOM' not in runopt:
runopt.append('FIXMOM')
change_values.append(['RUNOPT', runopt])
elif not all(fix_dir) and 'FIXMOM' in [i.upper() for i in runopt]:
runopt = [i for i in runopt if 'FIXMOM' not in i.upper()]
change_values.append(['RUNOPT', runopt])
parameters = _update_params(parameters, change_values)
# extract theta and phi values from input node
thetas = self.inputs.initial_noco_angles['theta']
if len(thetas) != natom:
raise InputValidationError(
'Error: `theta` list in `initial_noco_angles` input node needs to have the same length as number of atoms!'
)
phis = self.inputs.initial_noco_angles['phi']
if len(phis) != natom:
raise InputValidationError(
'Error: `phi` list in `initial_noco_angles` input node needs to have the same length as number of atoms!'
)
# now write kkrflex_angle file
with tempfolder.open(self._NONCO_ANGLES, 'w') as noco_angle_file:
for iatom in range(natom):
theta, phi = thetas[iatom], phis[iatom]
# check consistency
if theta < 0. or theta > 180.:
raise InputValidationError(
f'Error: theta value out of range (0..180): iatom={iatom}, theta={theta}'
)
# write line
noco_angle_file.write(f' {theta} {phi} {fix_dir[iatom]}\n')
return parameters
[docs]
def _use_nonco_bfield(self, parameters, structure, tempfolder):
"""
Set external non-collinear bfield (writes bfield.dat to tempfolder) used in constraint calculations.
"""
self.report('Found `bfield` input node, writing nonco_angle.dat file')
# extract number of atoms for length comparison
natom = get_natyp(structure)
change_values = [['<NONCOBFIELD>', True]]
parameters = _update_params(parameters, change_values)
# extract magnitude, theta and phi values from input node
mags = self.inputs.bfield['magnitude']
if len(mags) != natom:
raise InputValidationError(
'Error: `magnitude` list in `bfield` input node needs to have the same length as number of atoms!'
)
thetas = self.inputs.bfield['theta']
if len(thetas) != natom:
raise InputValidationError(
'Error: `theta` list in `bfield` input node needs to have the same length as number of atoms!'
)
phis = self.inputs.bfield['phi']
if len(phis) != natom:
raise InputValidationError(
'Error: `phi` list in `bfield` input node needs to have the same length as number of atoms!'
)
# now write kkrflex_angle file
with tempfolder.open(self._BFIELD, 'w') as bfield_file:
bfield_file.write('# theta [deg] phi [deg] magnitude [Ry]\n')
for iatom in range(natom):
theta, phi = thetas[iatom], phis[iatom]
magnitude = mags[iatom]
# check consistency
if theta < 0. or theta > 180.:
raise InputValidationError(
f'Error: theta value out of range (0..180): iatom={iatom}, theta={theta}'
)
# write line
bfield_file.write(f' {theta} {phi} {magnitude}\n')
return parameters
[docs]
def _use_decimation(self, parameters, tempfolder):
"""
Activate decimation mode and copy decifile from output of deciout_parent calculation
"""
self.report('Found `deciout_parent` input node, activae decimation mode')
# check if deciout parent calculation was proper deci-out calculation
deciout_parent = self.inputs.deciout_parent
parent_calcs = deciout_parent.get_incoming(node_class=CalcJobNode)
n_parents = len(parent_calcs.all_link_labels())
if n_parents != 1:
raise UniquenessError(
'Input RemoteData is child of {} '
'calculation{}, while it should have a single parent'
''.format(n_parents, '' if n_parents == 0 else 's')
)
# TODO change to exit code
parent_calc = parent_calcs.first().node
deciout_retrieved = parent_calc.outputs.retrieved
if self._DECIFILE not in deciout_retrieved.list_object_names():
raise InputValidationError('Error: deciout_parent does not contain decifile!')
# add 'DECIMATE' flag, decifile and NSTEPS=1
change_values = []
runopt = parameters.get_dict().get('RUNOPT')
if runopt is None:
runopt = []
runopt = [i.strip() for i in runopt]
runopt.append('DECIMATE')
change_values.append(['RUNOPT', runopt])
change_values.append(['FILES', [self._POTENTIAL, self._SHAPEFUN]]) # needed to make DECIFILE work
change_values.append(['DECIFILES', ['vacuum', self._DECIFILE]]) # works only for right continuation for now!
change_values.append(['NSTEPS', 1]) # decimation works only in one-shot mode
parameters = _update_params(parameters, change_values)
# now write kkrflex_angle file
with deciout_retrieved.open(self._DECIFILE, 'r') as decifile_handle:
decifile_txt = decifile_handle.readlines()
with tempfolder.open(self._DECIFILE, 'w') as decifile_handle:
decifile_handle.writelines(decifile_txt)
return parameters
[docs]
def _copy_BdG_pot(self, retrieved, tempfolder):
"""
Activate BdG mode and copy den_lm_ir files of the previous output to the input of this calculation.
"""
if 'anomalous_density' in self.inputs:
# this means we have a FolderData input that contains the
# anomalous density files
adens_folder = self.inputs.anomalous_density
else:
# if no anomalous density is given as an input node we check
# if there are any anomalous density files in the parent retrieved
# and take them from there if present
adens_folder = retrieved
# list of den_lm_ir files (anomalous density per atom)
BDG_POT_FILES = [i for i in adens_folder.list_object_names() if self._BDG_POT.split('.')[0] in i]
# add 'den-lm_ir' files to input
for BdG_pot in BDG_POT_FILES:
self.report(f'Copy BdG potential {BdG_pot} from {adens_folder.uuid}')
# read from parent
with adens_folder.open(BdG_pot, 'r') as file_handle:
file_txt = file_handle.readlines()
# write to tempfolder
with tempfolder.open(BdG_pot, 'w') as file_handle:
file_handle.writelines(file_txt)
[docs]
def _init_ldau(self, tempfolder, parent_calc, natom):
"""
Check if settings_LDAU is in input and set up LDA+U calculation. Reuse old ldaupot of parent_folder contains a file ldaupot.
"""
retrieve_list = []
# first check if settings_LDAU is in inputs
if 'settings_LDAU' not in self.inputs:
# do nothing
return retrieve_list
else:
# this means we need to set up LDA+U
# add ldaupot to retrieve and local copy lists
retrieve_list.append(self._LDAUPOT + '_new')
# add runoption for LDA+U
with tempfolder.open(self._INPUT_FILE_NAME) as file:
inputcard = file.readlines()
itmp = search_string('RUNOPT', inputcard)
if itmp >= 0:
# add LDA+U as runoption
inputcard[itmp + 1] = 'LDA+U ' + inputcard[itmp + 1]
else:
# activate LDA+U with keyword
inputcard.append('\n# activate LDA+U\n<USE_LDAU>= True\n')
# create ldaupot file
reuse_old_ldaupot, lopt, jeff, ueff, eref, atyp = self._create_or_update_ldaupot(
parent_calc, tempfolder, natom
)
# set LDA+U inputs in inputcard
nat_ldau = len(lopt)
inputcard.append(f'NAT_LDAU= {nat_ldau}\n')
inputcard.append(f'LDAU_PARA\n')
for iat_ldau in range(nat_ldau):
inputcard.append(
f'{atyp[iat_ldau]+1} {lopt[iat_ldau]} {ueff[iat_ldau]:16.7e} {jeff[iat_ldau]:16.7e} {eref[iat_ldau]:16.7e}\n'
)
if reuse_old_ldaupot:
inputcard.append('KREADLDAU= 1\n')
else:
inputcard.append('KREADLDAU= 0\n')
# overwrite inputcard
with tempfolder.open(self._INPUT_FILE_NAME, 'w') as file:
file.writelines(inputcard)
return retrieve_list
[docs]
def _create_or_update_ldaupot(self, parent_calc, tempfolder, natom):
"""
Writes ldaupot to tempfolder.
If parent_calc is found and it contains an onld ldaupot, we reuse the values for wldau, uldau and phi from there.
"""
# extract Fermi energy from parent calculation
ef_Ry = parent_calc.outputs.output_parameters['fermi_energy']
# get old ldaupot file
reuse_old_ldaupot = self._get_old_ldaupot(parent_calc, tempfolder)
# settings dict (defines U, J etc.)
ldau_settings = self.inputs.settings_LDAU.get_dict()
if reuse_old_ldaupot:
# reuse wldau, ildau and phi from old ldaupot file
# Attention the first number needs to be non-zero
txt, lopt, jeff, ueff, eref, atyp = get_ldaupot_text(
ldau_settings, ef_Ry, natom, initialize=False, return_luj=True
)
# now we read the old file
with tempfolder.open(self._LDAUPOT + '_old', 'r') as ldaupot_file:
txt0 = ldaupot_file.readlines()
# find start of wldau etc.
ii = 0
for line in txt0:
if 'wldau' in line.lower():
break
ii += 1
txt0 = txt0[ii:]
# create header for ldaupot file
txt = txt[0:2] + [f'{len(atyp)}\nLOPT 1.. {len(atyp)}\n']
txt += [f' {l}' for l in lopt] + ['\n']
txt += ['IAT UEFF JEFF EREF\n']
for ii, iat in enumerate(atyp):
txt += [f'{iat+1} {ueff[ii]:16.9e} {jeff[ii]:16.9e} {eref[ii]:16.9e}\n']
# put new header and old bottom together
newtxt = txt + txt0
else:
# initialize ldaupot file
# Attention: here the first number needs to be 0 which triggers generating initial values in KKRimp
newtxt, lopt, jeff, ueff, eref, atyp = get_ldaupot_text(
ldau_settings, ef_Ry, natom, initialize=True, return_luj=True
)
# now write to file
with tempfolder.open(self._LDAUPOT, 'w') as out_filehandle:
out_filehandle.writelines(newtxt)
return reuse_old_ldaupot, lopt, jeff, ueff, eref, atyp
[docs]
def _get_old_ldaupot(self, parent_calc, tempfolder):
"""
Copy old ldaupot from retrieved of parent or extract from tarball.
If no parent_calc is present this step is skipped.
"""
has_ldaupot = False
if parent_calc is not None:
retrieved = parent_calc.outputs.retrieved
# copy old file to tempfolder
if self._LDAUPOT + '_new' in retrieved.list_object_names():
has_ldaupot = True
with tempfolder.open(self._LDAUPOT + '_old', u'w') as newfile:
with retrieved.open(self._LDAUPOT + '_new', u'r') as oldfile:
newfile.writelines(oldfile.readlines())
return has_ldaupot
[docs]
def _update_params(parameters, change_values):
"""
change parameters node from change_values list of key value pairs
Retrun input parameter node if change_values list is empty
"""
if change_values != []:
new_params = {}
for key, val in parameters.get_dict().items():
new_params[key] = val
for key, val in change_values:
new_params[key] = val
new_params_node = Dict(new_params)
parameters = new_params_node
return parameters