diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 34f7b9d8..bbee7539 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.0.1 +current_version = 2.1.0 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 8877d644..504dd9ec 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -94,7 +94,7 @@ jobs: fail-fast: false matrix: python-version: ["3.8", "3.9", "3.10"] - aiida: [{version: 'aiida-core==2.1.2', name: '2.1.2'}] + aiida: [{version: 'aiida-core==2.3.0', name: '2.3.0'}] masci-tools: [{version: 'git+https://github.com/JuDFTteam/masci-tools.git@develop', name: '-masci-develop'}] allowed-to-fail: [false] @@ -125,7 +125,7 @@ jobs: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -181,12 +181,12 @@ jobs: uses: actions/setup-python@v4 with: python-version: 3.7 - - name: Build package + - name: Install flit run: | - pip install wheel - python setup.py sdist bdist_wheel - - name: Publish to PyPi - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_KEY }} + pip install flit~=3.4 + - name: Build and publish to PyPi + run: | + flit publish + env: + FLIT_USERNAME: __token__ + FLIT_PASSWORD: ${{ secrets.PYPI_KEY }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0046fce2..dc98742e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,7 @@ jobs: fail-fast: false matrix: python-version: ["3.8", "3.9", "3.10"] - aiida: [{version: 'aiida-core==2.1.2', name: '2.1.2'}] + aiida: [{version: 'aiida-core==2.3.0', name: '2.3.0'}] masci-tools: [{version: 'git+https://github.com/JuDFTteam/masci-tools.git@develop', name: '-masci-develop'}] allowed-to-fail: [false] @@ -183,5 +183,6 @@ jobs: - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: + token: ${{ secrets.CODECOV_TOKEN }} file: ./tests/coverage.xml fail_ci_if_error: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aa64de60..4fcc6dcd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -18,7 +18,7 @@ repos: - id: forbid-new-submodules - repo: https://github.com/google/yapf - rev: v0.32.0 + rev: v0.40.2 hooks: - id: yapf name: yapf @@ -27,7 +27,7 @@ repos: additional_dependencies: [toml] - repo: https://github.com/ikamensh/flynt/ - rev: '0.78' + rev: '1.0.1' hooks: - id: flynt args: [ diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/aiida_kkr/__init__.py b/aiida_kkr/__init__.py index 3767b684..36b462ff 100644 --- a/aiida_kkr/__init__.py +++ b/aiida_kkr/__init__.py @@ -2,4 +2,4 @@ AiiDA KKR """ -__version__ = '2.0.1' +__version__ = '2.1.0' diff --git a/aiida_kkr/calculations/kkr.py b/aiida_kkr/calculations/kkr.py index f60101b6..468ce90f 100644 --- a/aiida_kkr/calculations/kkr.py +++ b/aiida_kkr/calculations/kkr.py @@ -13,17 +13,20 @@ 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, update_params_wf, vca_check, kick_out_corestates + generate_inputcard_from_structure, check_2Dinput_consistency, vca_check, kick_out_corestates ) -from masci_tools.io.common_functions import get_alat_from_bravais, get_Ang2aBohr -from aiida_kkr.tools.tools_kkrimp import make_scoef, write_scoef_full_imp_cls 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.12.5' + +__version__ = '0.13.0' + __contributors__ = ('Jens Bröder', 'Philipp Rüßmann') verbose = False @@ -99,6 +102,8 @@ class KkrCalculation(CalcJob): # 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' @@ -161,90 +166,114 @@ def define(cls, spec): '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.""" + 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).""" + 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.""" + 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. - """ + 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. - """ + 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""" + 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.""" + 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.""" + 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 @@ -301,7 +330,7 @@ def prepare_for_submission(self, tempfolder): voro_parent, structure, vca_structure, use_alat_input = self._get_structure_inputs(parent_calc, parameters) # prepare scoef file if impurity_info was given - self._write_scoef_file(tempfolder, parameters, structure, use_alat_input) + 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: @@ -369,6 +398,11 @@ def prepare_for_submission(self, tempfolder): # 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 ### @@ -498,23 +532,21 @@ def _write_scoef_file(self, tempfolder, parameters, structure, use_alat_input): write_scoef = False runopt = parameters.get_dict().get('RUNOPT', None) - if runopt is not None and 'KKRFLEX' in runopt: - write_scoef = True - elif found_imp_info: + if found_imp_info: self.logger.info('Found impurity_info in inputs of the calculation, automatically add runopt KKRFLEX') write_scoef = True - runopt = parameters.get_dict().get('RUNOPT', []) - runopt.append('KKRFLEX') - parameters = update_params_wf( - parameters, - Dict( - dict={ - 'RUNOPT': runopt, - 'nodename': 'update_KKRFLEX', - 'nodedesc': 'Update Parameter node with KKRFLEX runopt' - } - ) - ) + 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: @@ -581,10 +613,7 @@ def _write_scoef_file(self, tempfolder, parameters, structure, use_alat_input): rescale_alat = None write_scoef_full_imp_cls(imp_info, scoef_file, rescale_alat) - elif write_scoef: - # 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') + return parameters def _get_shapes_array(self, parent_calc, voro_parent): """Get the shapes array from the parent calcualtion or the voronoi parent""" @@ -1154,6 +1183,134 @@ def _copy_BdG_pot(self, retrieved, tempfolder): with tempfolder.open(BdG_pot, 'w') as file_handle: file_handle.writelines(file_txt) + 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= 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 + + 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 + + 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 + def _update_params(parameters, change_values): """ @@ -1162,12 +1319,12 @@ def _update_params(parameters, change_values): """ if change_values != []: new_params = {} - #{'nodename': 'changed_params_qdos', 'nodedesc': 'Changed parameters to mathc qdos mode. Changed values: {}'.format(change_values)} 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 = update_params_wf(parameters, new_params_node) + parameters = new_params_node return parameters diff --git a/aiida_kkr/calculations/kkrimp.py b/aiida_kkr/calculations/kkrimp.py index 6e603450..f4d6b392 100644 --- a/aiida_kkr/calculations/kkrimp.py +++ b/aiida_kkr/calculations/kkrimp.py @@ -11,8 +11,9 @@ from masci_tools.io.kkr_params import kkrparams from .voro import VoronoiCalculation from .kkr import KkrCalculation -from aiida_kkr.tools.tools_kkrimp import modify_potential, make_scoef, write_scoef_full_imp_cls +from aiida_kkr.tools.tools_kkrimp import modify_potential, make_scoef, write_scoef_full_imp_cls, get_imp_info_from_parent from aiida_kkr.tools.common_workfunctions import get_username +from aiida_kkr.tools.ldau import get_ldaupot_text from masci_tools.io.common_functions import search_string, get_ef_from_potfile import os import tarfile @@ -21,7 +22,7 @@ __copyright__ = (u'Copyright (c), 2018, Forschungszentrum Jülich GmbH, ' 'IAS-1/PGI-1, Germany. All rights reserved.') __license__ = 'MIT license, see LICENSE.txt file' -__version__ = '0.7.0' +__version__ = '0.9.1' __contributors__ = (u'Philipp Rüßmann', u'Fabian Bertoldo') #TODO: implement 'ilayer_center' consistency check @@ -48,11 +49,13 @@ class KkrimpCalculation(CalcJob): _SHAPEFUN = u'shapefun' _KKRFLEX_ANGLE = u'kkrflex_angle' _KKRFLEX_LLYFAC = u'kkrflex_llyfac' + _KKRFLEX_SOCFAC = u'kkrflex_spinorbitperatom' # full list of kkrflex files _ALL_KKRFLEX_FILES = KkrCalculation._ALL_KKRFLEX_FILES _ALL_KKRFLEX_FILES.append(_KKRFLEX_ANGLE) _ALL_KKRFLEX_FILES.append(_KKRFLEX_LLYFAC) + _ALL_KKRFLEX_FILES.append(_KKRFLEX_SOCFAC) # List of output files that should always be present (are always retrieved) _OUT_POTENTIAL = u'out_potential' @@ -65,11 +68,11 @@ class KkrimpCalculation(CalcJob): # List of output files that are retrieved if special conditions are fulfilled _OUT_JIJMAT = u'out_Jijmatrix' - _OUT_JIJ_OF_E_BASE = 'out_Jijmatrix_Eres_IE%0.3i.dat' - _OUT_LDOS_BASE = u'out_ldos.atom=%2i_spin%i.dat' - _OUT_LDOS_INTERPOL_BASE = u'out_ldos.interpol.atom=%2i_spin%i.dat' - _OUT_LMDOS_BASE = u'out_lmdos.atom=%2i_spin%i.dat' - _OUT_LMDOS_INTERPOL_BASE = u'out_lmdos.interpol.atom=%2i_spin%i.dat' + _OUT_JIJ_OF_E_BASE = 'out_Jijmatrix_Eres_IE*' + _OUT_LDOS = u'out_ldos.atom=*' + _OUT_LDOS_INTERPOL = u'out_ldos.interpol.atom=*' + _OUT_LMDOS = u'out_lmdos.atom=*' + _OUT_LMDOS_INTERPOL = u'out_lmdos.interpol.atom=*' _OUT_MAGNETICMOMENTS = u'out_magneticmoments' _OUT_ORBITALMOMENTS = u'out_orbitalmoments' _LDAUPOT = 'ldaupot' @@ -168,13 +171,6 @@ def define(cls, spec): Note: The length of the theta, phi and fix_dir lists have to be equal to the number of atoms in the impurity cluster. """ ) - spec.input( - 'cleanup_outfiles', - valid_type=Bool, - required=False, - default=lambda: Bool(False), - help='Cleanup and compress output (works only in aiida-core<2.0 and breaks caching ability).' - ) # define outputs spec.output('output_parameters', valid_type=Dict, required=True, help='results of the KKRimp calculation') @@ -222,8 +218,8 @@ def prepare_for_submission(self, tempfolder): retrieve_list = [ self._OUTPUT_FILE_NAME, self._CONFIG, self._KKRFLEX_ATOMINFO, self._KKRFLEX_ANGLE, self._KKRFLEX_LLYFAC, - self._OUT_POTENTIAL, self._OUTPUT_000, self._OUT_TIMING_000, self._OUT_ENERGYSP_PER_ATOM, - self._OUT_ENERGYTOT_PER_ATOM + self._KKRFLEX_SOCFAC, self._OUT_POTENTIAL, self._OUTPUT_000, self._OUT_TIMING_000, + self._OUT_ENERGYSP_PER_ATOM, self._OUT_ENERGYTOT_PER_ATOM ] # extract run and test options (these change retrieve list in some cases) @@ -298,26 +294,16 @@ def _get_and_verify_hostfiles(self, tempfolder): else: parent_calc = parent_calcs.first().node # extract impurity_info + found_impurity_inputnode = False if 'impurity_info' in self.inputs: imp_info_inputnode = self.inputs.impurity_info if not isinstance(imp_info_inputnode, Dict): raise InputValidationError('impurity_info not of type Dict') - if 'impurity_info' in parent_calc.get_incoming().all_link_labels(): - imp_info = parent_calc.get_incoming().get_node_by_label('impurity_info') - else: - imp_info = None - if imp_info is None: - raise InputValidationError('host_Greenfunction calculation does not have an input node impurity_info') found_impurity_inputnode = True found_host_parent = True - else: - if 'impurity_info' in parent_calc.get_incoming().all_link_labels(): - imp_info = parent_calc.get_incoming().get_node_by_label('impurity_info') - else: - imp_info = None - if imp_info is None: - raise InputValidationError('host_Greenfunction calculation does not have an input node impurity_info') - found_impurity_inputnode = False + imp_info = get_imp_info_from_parent(parent_calc) + if imp_info is None: + raise InputValidationError('host_Greenfunction calculation does not have an input node impurity_info') # if impurity input is seperate input, check if it is the same as # the one from the parent calc (except for 'Zimp'). If that's not the @@ -494,80 +480,8 @@ def _extract_and_write_config(self, parent_calc_folder, params_host, parameters, fill kkr params for KKRimp and write config file also writes kkrflex_llyfac file if Lloyd is used in the host system """ - # initialize kkrimp parameter set with default values - params_kkrimp = kkrparams( - params_type='kkrimp', - NPAN_LOGPANELFAC=2, - RADIUS_MIN=-1, - NCOLL=0, - SPINORBIT=0, - SCFSTEPS=1, - IMIX=0, - MIXFAC=0.05, - ITDBRY=20, - BRYMIX=0.05, - QBOUND=10**-7, - RUNFLAG=[], - TESTFLAG=[], - HFIELD=[0.0, 0], - CALCFORCE=0, - CALCJIJMAT=0, - CALCORBITALMOMENT=0, - ICST=2 - ) - # keys that are being overwritten from host calculation settings - keys_overwrite = [ - 'NSPIN', 'KVREL', 'XC', 'INS', 'ICST', 'RADIUS_LOGPANELS', 'NPAN_EQ', 'NPAN_LOG', 'NCHEB', 'QBOUND' - ] - for key in keys_overwrite: - if key == 'XC': - key0 = 'KEXCOR' - elif key == 'RADIUS_LOGPANELS': - key0 = 'R_LOG' - elif key == 'MIXFAC': - key0 = 'STRMIX' - else: - key0 = key - val = params_host.get_value(key0) - if val is not None: - params_kkrimp.set_value(key, val) - # settings for SOC solver - runopts = params_host.get_value('RUNOPT') - if 'NEWSOSOL' in runopts: - params_kkrimp.set_multiple_values(NCOLL=1, SPINORBIT=1, CALCORBITALMOMENT=1, TESTFLAG=['tmatnew']) - else: - params_kkrimp.set_multiple_values(NCOLL=0, SPINORBIT=0, CALCORBITALMOMENT=0, TESTFLAG=[]) - - # extract input RUNFLAGS - runflag = None - if parameters is not None: - for (key, val) in parameters.get_set_values(): - if key == 'RUNFLAG': - runflag = list(val) - if runflag is None: - runflag = [] - - # special settings - runopts = params_host.get_value('RUNOPT') - if 'SIMULASA' in runopts or (params_kkrimp.get_value('NCOLL') > 0 and params_kkrimp.get_value('INS') == 0): - runflag.append('SIMULASA') - # take care of LLYsimple (i.e. Lloyd in host system) - if 'LLOYD' in runopts: - # add runflag for imp code - runflag.append('LLYsimple') - # also extract renormalization factor and create kkrflex_llyfac file (contains one value only) - with GFhost_folder.open('output.000.txt') as f: - txt = f.readlines() - iline = search_string('RENORM_LLY: Renormalization factor of total charge', txt) - if iline >= 0: - llyfac = txt[iline].split()[-1] - # now write kkrflex_llyfac to tempfolder where later on config file is also written - with tempfolder.open(self._KKRFLEX_LLYFAC, 'w') as f2: - f2.writelines([llyfac]) - - # now set runflags - params_kkrimp.set_value('RUNFLAG', runflag) + runflag, params_kkrimp = self._initialize_kkrimp_params(params_host, parameters, GFhost_folder, tempfolder) # overwrite keys if found in parent_calc (previous KKRimp calculation) # here `parent_calc_folder` is the `remote` output node of the previous KKRimp calculation @@ -591,90 +505,13 @@ def _extract_and_write_config(self, parent_calc_folder, params_host, parameters, # special run mode: calculation of Jijs if parameters.get_value('CALCJIJMAT') is not None and parameters.get_value('CALCJIJMAT') == 1: self.report('Found CALCJIJMAT=1: trigger JIJ mode which overwrites IMIX, MIXFAC, SCFSTEPS and RUNFLAGs') - - # settings in config file - runflag.append('force_angles') - # take care of LDA+U - if 'settings_LDAU' in self.inputs: - # this prevents mixing LDAU potential in between iterations - runflag.append('freezeldau') - - # now add runflags - params_kkrimp.set_multiple_values(IMIX=0, MIXFAC=0., SCFSTEPS=3, RUNFLAG=runflag) - - # for DOS mode add flag to writeout Jij info energy resolved (this will be retrieved if ) - testflag = params_kkrimp.get_value('TESTFLAG') - testflag.append('Jij(E)') - params_kkrimp.set_value('TESTFLAG', testflag) - - # extract NATOM from atominfo file - with GFhost_folder.open(self._KKRFLEX_ATOMINFO) as file: - atominfo = file.readlines() - itmp = search_string('NATOM', atominfo) - if itmp >= 0: - natom = int(atominfo[itmp + 1].split()[0]) - else: - raise ValueError('Could not extract NATOM value from kkrflex_atominfo') - - # now write kkrflex_angle file - with tempfolder.open(self._KKRFLEX_ANGLE, 'w') as kkrflex_angle_file: - for istep in range(3): - for iatom in range(natom): - if istep == 0: - kkrflex_angle_file.write(f' 0.0 0.0 1\n') - elif istep == 1: - kkrflex_angle_file.write(f' 90.0 0.0 1\n') - else: - kkrflex_angle_file.write(f' 90.0 90.0 1\n') + runflag = self._activate_jij_calc(runflag, params_kkrimp, GFhost_folder, tempfolder) # write kkrflex_angle file # DOES NOT WORK TOGETHER WITH JIJ mode!!! if 'initial_noco_angles' in self.inputs: self.report('Found `initial_noco_angles` input node, writing kkrflex_angle file') - - # check if calculation is no Jij run - if parameters.get_value('CALCJIJMAT') is not None and parameters.get_value('CALCJIJMAT') == 1: - raise InputValidationError('ERROR: ') - - # extract NATOM from atominfo file - with GFhost_folder.open(self._KKRFLEX_ATOMINFO) as file: - atominfo = file.readlines() - itmp = search_string('NATOM', atominfo) - if itmp >= 0: - natom = int(atominfo[itmp + 1].split()[0]) - else: - raise ValueError('Could not extract NATOM value from kkrflex_atominfo') - - # extract 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 in the impurity cluster!' - ) - 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 in the impurity cluster!' - ) - fix_dirs = self.inputs.initial_noco_angles['fix_dir'] - if len(fix_dirs) != natom: - raise InputValidationError( - 'Error: `fix_dir` list in `initial_noco_angles` input node needs to have the same length as number of atoms in the impurity cluster!' - ) - - # now write kkrflex_angle file - with tempfolder.open(self._KKRFLEX_ANGLE, 'w') as kkrflex_angle_file: - for iatom in range(natom): - theta, phi, fix_dir = thetas[iatom], phis[iatom], fix_dirs[iatom] - # check consistency - if theta < 0 or theta > 180: - raise InputValidationError( - f'Error: theta value out of range (0..180): iatom={iatom}, theta={theta}' - ) - if phi < 0 or phi > 360: - raise InputValidationError(f'Error: phi value out of range (0..360): iatom={iatom}, phi={phi}') - # write line - kkrflex_angle_file.write(f' {theta} {phi} {fix_dir}\n') + self._write_kkrflex_angle(parameters, GFhost_folder, tempfolder) # write config.cfg with tempfolder.open(self._CONFIG, u'w') as config_file: @@ -694,7 +531,12 @@ def _change_atominfo(self, imp_info, kkrflex_file_paths, tempfolder): # read scoef for comparison with Rimp_rel scoef = [] with tempfolder.open(KkrCalculation._SCOEF, u'r') as scoeffile: - scoef = loadtxt(scoeffile, skiprows=1)[:, :3] + n_rows = len(scoeffile.readlines()) - 1 + with tempfolder.open(KkrCalculation._SCOEF, u'r') as scoeffile: + if n_rows > 1: + scoef = loadtxt(scoeffile, skiprows=1)[:, :3] + else: + scoef = loadtxt(scoeffile, skiprows=1)[:3] # find replaceZimp list from Zimp and Rimp_rel imp_info_dict = imp_info.get_dict() @@ -707,7 +549,10 @@ def _change_atominfo(self, imp_info, kkrflex_file_paths, tempfolder): for iatom in range(len(Zimp_list)): rtmp = array(Rimp_rel_list[iatom])[:3] self.report(f'INFO: Rimp_rel {iatom}, {rtmp}') - diff = sqrt(sum((rtmp - scoef)**2, axis=1)) + if n_rows > 1: + diff = sqrt(sum((rtmp - scoef)**2, axis=1)) + else: + diff = sqrt(sum((rtmp - scoef)**2, axis=0)) Zimp = Zimp_list[iatom] ipos_replace = where(diff == diff.min())[0][0] replace_zatom_imp.append([ipos_replace, Zimp]) @@ -820,6 +665,222 @@ def _get_pot_and_shape( if '.dummy' in os.listdir(tempfolder_path): os.remove(os.path.join(tempfolder_path, '.dummy')) + def _get_natom(self, tempfolder): + """Get the number of atoms in the impurity cluster from kkrflex_atominfo file""" + with tempfolder.open(self._KKRFLEX_ATOMINFO) as file: + atominfo = file.readlines() + itmp = search_string('NATOM', atominfo) + if itmp >= 0: + natom = int(atominfo[itmp + 1].split()[0]) + else: + raise ValueError('Could not extract NATOM value from kkrflex_atominfo') + return natom + + def _initialize_kkrimp_params(self, params_host, parameters, GFhost_folder, tempfolder): + """Initialize KKRimp parameters and set keys that are the same as in the host calculation""" + + # initialize kkrimp parameter set with default values + params_kkrimp = kkrparams( + params_type='kkrimp', + NPAN_LOGPANELFAC=2, + RADIUS_MIN=-1, + NCOLL=0, + SPINORBIT=0, + SCFSTEPS=1, + IMIX=0, + MIXFAC=0.05, + ITDBRY=20, + BRYMIX=0.05, + QBOUND=10**-7, + RUNFLAG=[], + TESTFLAG=[], + HFIELD=[0.0, 0], + CALCFORCE=0, + CALCJIJMAT=0, + CALCORBITALMOMENT=0, + ICST=2 + ) + + # keys that are being overwritten from host calculation settings + keys_overwrite = [ + 'NSPIN', 'KVREL', 'XC', 'INS', 'ICST', 'RADIUS_LOGPANELS', 'NPAN_EQ', 'NPAN_LOG', 'NCHEB', 'QBOUND' + ] + for key in keys_overwrite: + if key == 'XC': + key0 = 'KEXCOR' + elif key == 'RADIUS_LOGPANELS': + key0 = 'R_LOG' + elif key == 'MIXFAC': + key0 = 'STRMIX' + else: + key0 = key + val = params_host.get_value(key0) + if val is not None: + params_kkrimp.set_value(key, val) + + # settings for SOC solver + use_cheby = False + runopts = params_host.get_value('RUNOPT') + if runopts is not None and 'NEWSOSOL' in runopts: + use_cheby = True + if ( + params_host.get_value('') is not None and + params_host.get_value('') + ): + use_cheby = True + if use_cheby: + params_kkrimp.set_multiple_values(NCOLL=1, SPINORBIT=1, CALCORBITALMOMENT=1, TESTFLAG=['tmatnew']) + else: + params_kkrimp.set_multiple_values(NCOLL=0, SPINORBIT=0, CALCORBITALMOMENT=0, TESTFLAG=[]) + + # SOC solver but with SOC strength scaled to zero + if use_cheby: + # maybe deactivate SOC in KKRimp calculation + self._set_nosoc(params_host, GFhost_folder, tempfolder) + + # extract input RUNFLAGS + runflag = None + if parameters is not None: + for (key, val) in parameters.get_set_values(): + if key == 'RUNFLAG': + runflag = list(val) + if runflag is None: + runflag = [] + + # special settings + runopts = params_host.get_value('RUNOPT') + if 'SIMULASA' in runopts or (params_kkrimp.get_value('NCOLL') > 0 and params_kkrimp.get_value('INS') == 0): + runflag.append('SIMULASA') + # take care of LLYsimple (i.e. Lloyd in host system) + if 'LLOYD' in runopts: + runflag = self._use_lloyd(runflag, GFhost_folder, tempfolder) + + # now set runflags + params_kkrimp.set_value('RUNFLAG', runflag) + + return runflag, params_kkrimp + + def _set_nosoc(self, params_host, GFhost_folder, tempfolder): + """Check if host is a noSOC calculation and then set the kkrflex_spinorbitperatom accordingly""" + + # check if noSOC mode was used in the host run + nosoc = False + if (params_host.get_value('') is not None and params_host.get_value('')): + nosoc = True + if params_host.get_value('') is not None: + # TODO make this more flexible, now only SOC on/off is possible + socscale = params_host.get_value('') + try: + for scl in socscale: + if scl < 1e-14: + nosoc = True + except TypeError: + if socscale < 1e-14: + nosoc = True + + # maybe deactivate SOC by writing the kkrflex_spinorbitperatom file + if nosoc: + # extract NATOM from atominfo file + natom = self._get_natom(GFhost_folder) + + # set soc scale factor (should be integer at the moment!) + socscale = [0 for i in range(natom)] + + # now write kkrflex_spinorbitperatom file + with tempfolder.open(self._KKRFLEX_SOCFAC, 'w') as kkrflex_socfac: + for socscl in socscale: + kkrflex_socfac.write(f' {socscl}\n') + + def _use_lloyd(self, runflag, GFhost_folder, tempfolder): + """Use the LLYsimple version of KKRimp code with the average renormalization factor from the host calculation""" + # add runflag for imp code + runflag.append('LLYsimple') + # also extract renormalization factor and create kkrflex_llyfac file (contains one value only) + with GFhost_folder.open('output.000.txt') as f: + txt = f.readlines() + iline = search_string('RENORM_LLY: Renormalization factor of total charge', txt) + if iline >= 0: + llyfac = txt[iline].split()[-1] + # now write kkrflex_llyfac to tempfolder where later on config file is also written + with tempfolder.open(self._KKRFLEX_LLYFAC, 'w') as f2: + f2.writelines([llyfac]) + return runflag + + def _activate_jij_calc(self, runflag, params_kkrimp, GFhost_folder, tempfolder): + """Adapt runoptions to use Jij calculation and set inputs for Jij run""" + + # settings in config file + runflag.append('force_angles') + # take care of LDA+U + if 'settings_LDAU' in self.inputs: + # this prevents mixing LDAU potential in between iterations + runflag.append('freezeldau') + + # now add runflags + params_kkrimp.set_multiple_values(IMIX=0, MIXFAC=0., SCFSTEPS=3, RUNFLAG=runflag) + + # for DOS mode add flag to writeout Jij info energy resolved (this will be retrieved if ) + testflag = params_kkrimp.get_value('TESTFLAG') + testflag.append('Jij(E)') + params_kkrimp.set_value('TESTFLAG', testflag) + + # extract NATOM from atominfo file + natom = self._get_natom(GFhost_folder) + + # now write kkrflex_angle file + with tempfolder.open(self._KKRFLEX_ANGLE, 'w') as kkrflex_angle_file: + for istep in range(3): + for iatom in range(natom): + if istep == 0: + kkrflex_angle_file.write(f' 0.0 0.0 1\n') + elif istep == 1: + kkrflex_angle_file.write(f' 90.0 0.0 1\n') + else: + kkrflex_angle_file.write(f' 90.0 90.0 1\n') + + return runflag + + def _write_kkrflex_angle(self, parameters, GFhost_folder, tempfolder): + """Create the kkrflex_angle file in tempfolder""" + + # check if calculation is no Jij run + if parameters.get_value('CALCJIJMAT') is not None and parameters.get_value('CALCJIJMAT') == 1: + raise InputValidationError('ERROR: ') + + # extract NATOM from atominfo file + natom = self._get_natom(GFhost_folder) + + # extract 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 in the impurity cluster!' + ) + 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 in the impurity cluster!' + ) + fix_dirs = self.inputs.initial_noco_angles['fix_dir'] + if len(fix_dirs) != natom: + raise InputValidationError( + 'Error: `fix_dir` list in `initial_noco_angles` input node needs to have the same length as number of atoms in the impurity cluster!' + ) + + # now write kkrflex_angle file + with tempfolder.open(self._KKRFLEX_ANGLE, 'w') as kkrflex_angle_file: + for iatom in range(natom): + theta, phi, fix_dir = thetas[iatom], phis[iatom], fix_dirs[iatom] + # check consistency + if theta < 0 or theta > 180: + raise InputValidationError( + f'Error: theta value out of range (0..180): iatom={iatom}, theta={theta}' + ) + if phi < 0 or phi > 360: + raise InputValidationError(f'Error: phi value out of range (0..360): iatom={iatom}, phi={phi}') + # write line + kkrflex_angle_file.write(f' {theta} {phi} {fix_dir}\n') + def _check_key_setting_consistency(self, params_kkrimp, key, val): """ Check if key/value pair that is supposed to be set is not in conflict with previous settings of parameters in params_kkrimp @@ -846,37 +907,20 @@ def add_lmdos_files_to_retrieve(self, tempfolder, allopts, retrieve_list, kkrfle """Add DOS files to retrieve list""" if 'lmdos' in allopts or 'ldos' in allopts: - # extract NSPIN + # check if mode is Jij with tempfolder.open(self._CONFIG) as file: config = file.readlines() - itmp = search_string('NSPIN', config) - if itmp >= 0: - nspin = int(config[itmp].split()[-1]) - else: - raise ValueError('Could not extract NSPIN value from config.cfg') - # check if mode is Jij itmp = search_string('CALCJIJMAT', config) if itmp >= 0: calcjijmat = int(config[itmp].split()[-1]) else: raise ValueError('Could not extract CALCJIJMAT value from config.cfg') - # extract NATOM from atominfo file - with tempfolder.open(self._KKRFLEX_ATOMINFO) as file: - atominfo = file.readlines() - itmp = search_string('NATOM', atominfo) - if itmp >= 0: - natom = int(atominfo[itmp + 1].split()[0]) - else: - raise ValueError('Could not extract NATOM value from kkrflex_atominfo') - - # loop over atoms and spins to add DOS output files accordingly - for iatom in range(1, natom + 1): - for ispin in range(1, nspin + 1): - retrieve_list.append((self._OUT_LDOS_BASE % (iatom, ispin)).replace(' ', '0')) - retrieve_list.append((self._OUT_LDOS_INTERPOL_BASE % (iatom, ispin)).replace(' ', '0')) - retrieve_list.append((self._OUT_LMDOS_BASE % (iatom, ispin)).replace(' ', '0')) - retrieve_list.append((self._OUT_LMDOS_INTERPOL_BASE % (iatom, ispin)).replace(' ', '0')) + # add DOS output files accordingly (names have '*' ending to catch all files for atoms + retrieve_list.append(self._OUT_LDOS) + retrieve_list.append(self._OUT_LDOS_INTERPOL) + retrieve_list.append(self._OUT_LMDOS) + retrieve_list.append(self._OUT_LMDOS_INTERPOL) # add Jij of E file if Jij mode if calcjijmat > 0: with kkrflex_file_paths[self._KKRFLEX_TMAT].open(self._KKRFLEX_TMAT, 'r') as f: @@ -885,8 +929,7 @@ def add_lmdos_files_to_retrieve(self, tempfolder, allopts, retrieve_list, kkrfle txt.append(f.readline()) nepts = int(txt[1].split()[3]) for ie in range(1, nepts + 1): - retrieve_list.append(self._OUT_JIJ_OF_E_BASE % ie) # energy resolved values - retrieve_list.append((self._OUT_JIJ_OF_E_BASE.replace('IE', 'IE_int')) % ie) # integrated values + retrieve_list.append(self._OUT_JIJ_OF_E) # energy resolved values return retrieve_list @@ -963,13 +1006,7 @@ def create_or_update_ldaupot(self, parent_calc_folder, tempfolder): ef_Ry = get_ef_from_potfile(potfile) # extract NATOM from atominfo file - with tempfolder.open(self._KKRFLEX_ATOMINFO) as file: - atominfo = file.readlines() - itmp = search_string('NATOM', atominfo) - if itmp >= 0: - natom = int(atominfo[itmp + 1].split()[0]) - else: - raise ValueError('Could not extract NATOM value from kkrflex_atominfo') + natom = self._get_natom(tempfolder) # get old ldaupot file reuse_old_ldaupot = self.get_old_ldaupot(parent_calc_folder, tempfolder) @@ -1086,6 +1123,7 @@ def get_ldaupot_from_retrieved(self, retrieved, tempfolder): returns True of ldaupot was found, otherwise returns False """ tar_filenames = [] + has_ldaupot = False if self._FILENAME_TAR in retrieved.list_object_names(): # get path of tempfolder with tempfolder.open('.dummy', 'w') as tmpfile: @@ -1137,85 +1175,3 @@ def add_jij_files(self, tempfolder, retrieve_list): raise ValueError('Could not extract CALCJIJMAT value from config.cfg') return retrieve_list - - -def get_ldaupot_text(ldau_settings, ef_Ry, natom, initialize=True): - """ - create the text for the ldaupot file - """ - from masci_tools.io.common_functions import get_Ry2eV - - eV2Ry = 1. / get_Ry2eV() - - # these lists are extracted from ldau_settings node and then written to the ldaupot file - iatoms_ldau = [] - lopt = [] - ueff = [] - jeff = [] - eref = [] - - # extract values from ldau_settings - for key, val in ldau_settings.items(): - if 'iatom' in key: - iatoms_ldau.append(int(key.split('=')[1])) - lopt.append(val['L']) - # add values in Ry units - jeff.append(val['J'] * eV2Ry) - ueff.append(val['U'] * eV2Ry) - eref.append(val['Eref_EF'] * eV2Ry + ef_Ry) - - if initialize: - # this means we initialize this file - ldaurun = 0 - else: - # this means wldau etc are reused (need to be added to the file) - ldaurun = 1 - - # collect text which is written to ldaupot - txt = [f'{ldaurun} '] - txt_lopt, txt_jeff, txt_ueff, txt_eref = [], [], [], [] - ii = 0 - for iatom in range(natom): - if iatom not in iatoms_ldau: - txt_lopt += [f'{-1} '] - txt_jeff += [f'{0.0} '] - txt_ueff += [f'{0.0} '] - txt_eref += [f'{0.0} '] - else: - txt_lopt += [f'{lopt[ii]} '] - txt_jeff += [f'{jeff[ii]} '] - txt_ueff += [f'{ueff[ii]} '] - txt_eref += [f'{eref[ii]} '] - ii += 1 - txt += ['\n'] + txt_lopt + ['\n'] + txt_ueff + ['\n'] + txt_jeff + ['\n'] + txt_eref - txt += ['\nwldau\nuldau\nphi\n'] - - # add initial matrices - if initialize and 'initial_matrices' in ldau_settings.keys(): - # save for consistency check - nldauatoms = ii - - # change first number from 0 to 1 to signal reading-in instead of calculating initial matrices - txt[0] = '1 ' - - # remove last dummy line, will be replaced with starting values now - txt[-1] = '\n' - txt_wldau = ['wldau\n'] - txt_uldau = ['uldau\n'] - txt_phi = ['phi\n'] - ii = 0 - for iatom in range(natom): - if iatom in iatoms_ldau: - txt_wldau += [f'atom {iatom+1}\n'] + ldau_settings['initial_matrices'][f'iatom={iatom}']['wldau'] - txt_uldau += [f'atom {iatom+1}\n'] + ldau_settings['initial_matrices'][f'iatom={iatom}']['uldau'] - txt_phi += [f'atom {iatom+1}\n'] + ldau_settings['initial_matrices'][f'iatom={iatom}']['phi'] - ii += 1 # count number of atoms - - # consistency check - if nldauatoms != ii: - raise ValueError('initial_matrices input inconsistent') - - # add additional lines to txt - txt = txt + txt_wldau + txt_uldau + txt_phi - - return txt diff --git a/aiida_kkr/cmdline/visualization/__init__.py b/aiida_kkr/cmdline/visualization/__init__.py index 9d01f646..88ef02f8 100755 --- a/aiida_kkr/cmdline/visualization/__init__.py +++ b/aiida_kkr/cmdline/visualization/__init__.py @@ -7,9 +7,7 @@ from aiida.cmdline.params import arguments -@click.command( - name='plot', -) +@click.command(name='plot',) @click.option('-f', 'filename', type=click.File('r'), default=None) @click.option( '-o', diff --git a/aiida_kkr/parsers/kkr.py b/aiida_kkr/parsers/kkr.py index 555072ee..e1203dec 100644 --- a/aiida_kkr/parsers/kkr.py +++ b/aiida_kkr/parsers/kkr.py @@ -18,7 +18,7 @@ __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.7.0' +__version__ = '0.8.0' __contributors__ = ('Jens Broeder', u'Philipp Rüßmann') @@ -201,6 +201,11 @@ def parse(self, debug=False, **kwargs): self.logger.info('take output of parser run 2') success, msg_list, out_dict = success2, msg_list2, out_dict2 + # TODO discriminate between real errors and warnings + msg_list = [i for i in msg_list if 'single particle energies' not in i] + if len(msg_list) == 0: + success = True + out_dict['parser_errors'] = msg_list # add file open errors to parser output of error messages for (err_cat, f_err) in file_errors: @@ -224,21 +229,3 @@ def parse(self, debug=False, **kwargs): if not success: return self.exit_codes.ERROR_KKR_PARSING_FAILED - else: # cleanup after parsing (only if parsing was successful) - # cleanup only works below aiida-core v2.0 - if int(aiida_core_version.split('.')[0]) < 2: - # delete completely parsed output files - self.remove_unnecessary_files() - # then (maybe) tar the output to save space - # TODO needs implementing (see kkrimp parser) - - def remove_unnecessary_files(self): - """ - Remove files that are not needed anymore after parsing - The information is completely parsed (i.e. in outdict of calculation) - and keeping the file would just be a duplication. - """ - files_to_delete = [KkrCalculation._POTENTIAL, KkrCalculation._SHAPEFUN] - for fileid in files_to_delete: - if fileid in self.retrieved.list_object_names(): - self.retrieved.delete_object(fileid, force=True) diff --git a/aiida_kkr/parsers/kkrimp.py b/aiida_kkr/parsers/kkrimp.py index d3d40a4c..79783da2 100644 --- a/aiida_kkr/parsers/kkrimp.py +++ b/aiida_kkr/parsers/kkrimp.py @@ -21,7 +21,7 @@ __copyright__ = (u'Copyright (c), 2018, Forschungszentrum Jülich GmbH, ' 'IAS-1/PGI-1, Germany. All rights reserved.') __license__ = 'MIT license, see LICENSE.txt file' -__version__ = '0.5.0' +__version__ = '0.6.0' __contributors__ = ('Philipp Rüßmann') @@ -41,8 +41,8 @@ def __init__(self, calc): super(KkrimpParser, self).__init__(calc) # pylint: disable=protected-access - - def parse(self, debug=False, **kwargs): + # pylint: disable=unexpected-keyword-arg + def parse(self, debug=False, ignore_nan=True, **kwargs): """ Parse output data folder, store results in database. @@ -113,7 +113,7 @@ def parse(self, debug=False, **kwargs): # now we can parse the output files success, msg_list, out_dict = KkrimpParserFunctions().parse_kkrimp_outputfile( - out_dict, named_file_handles, debug=debug + out_dict, named_file_handles, debug=debug, ignore_nan=ignore_nan ) out_dict['parser_errors'] = msg_list @@ -132,22 +132,7 @@ def parse(self, debug=False, **kwargs): # create output node and link self.out('output_parameters', Dict(dict=out_dict)) - # cleanup after parsing (only if parsing was successful), only works below aiida-core v2.0 - if success: - if int(aiida_core_version.split('.')[0]) < 2: - # check if we should do the cleanup or not - cleanup_outfiles = False - if 'cleanup_outfiles' in self.node.inputs: - cleanup_outfiles = self.node.inputs.cleanup_outfiles.value - if cleanup_outfiles: - # reduce size of timing file - self.cleanup_outfiles(files['out_timing'], ['Iteration number', 'time until scf starts']) - # reduce size of out_log file - self.cleanup_outfiles(files['out_log'], ['Iteration Number']) - # delete completely parsed output files and create a tar ball to reduce size - self.remove_unnecessary_files() - self.final_cleanup() - else: + if not success: return self.exit_codes.ERROR_PARSING_KKRIMPCALC def _check_file_existance(self, files, keyname, fname, icrit, file_errors): @@ -168,71 +153,3 @@ def _check_file_existance(self, files, keyname, fname, icrit, file_errors): raise ValueError('icrit should be either 1 or 2') file_errors.append((icrit, crit_level + f" File '{fname}' not found.")) files[keyname] = None - - def cleanup_outfiles(self, fileidentifier, keyslist): - """open file and remove unneeded output""" - if fileidentifier is not None: - lineids = [] - with self.retrieved.open(fileidentifier) as tfile: - txt = tfile.readlines() - for iline in range(len(txt)): - for key in keyslist: # go through all keys - if key in txt[iline]: # add line id to list if key has been found - lineids.append(iline) - # rewrite file deleting the middle part - if len(lineids) > 1: # cut only if more than one iteration was found - txt = txt[:lineids[0]] + \ - ['# ... [removed output except for last iteration] ...\n'] + \ - txt[lineids[-1]:] - with self.retrieved.open(fileidentifier, 'w') as tfilenew: - tfilenew.writelines(txt) - - def remove_unnecessary_files(self): - """ - Remove files that are not needed anymore after parsing - The information is completely parsed (i.e. in outdict of calculation) - and keeping the file would just be a duplication. - """ - # first delete unused files (completely in parsed output) - files_to_delete = [ - KkrimpCalculation._OUT_ENERGYSP_PER_ATOM, KkrimpCalculation._OUT_ENERGYTOT_PER_ATOM, - KkrimpCalculation._SHAPEFUN - ] - for fileid in files_to_delete: - if fileid in self.retrieved.list_object_names(): - self.retrieved.delete_object(fileid, force=True) - - def final_cleanup(self): - """Create a tarball of the rest.""" - - # short name for retrieved folder - ret = self.retrieved - - # Now create tarball of output - # - # check if output has been packed to tarfile already - # only if tarfile is not there we create the output tar file - if KkrimpCalculation._FILENAME_TAR not in ret.list_object_names(): - # first create dummy file which is used to extract the full path that is given to tarfile.open - with ret.open(KkrimpCalculation._FILENAME_TAR, 'w') as f: - filepath_tar = f.name - - # now create tarfile and loop over content of retrieved directory - to_delete = [] - with tarfile.open(filepath_tar, 'w:gz') as tf: - for f in ret.list_object_names(): - with ret.open(f) as ftest: - filesize = os.stat(ftest.name).st_size - ffull = ftest.name - if ( - f != KkrimpCalculation._FILENAME_TAR # ignore tar file - and filesize > 0 # ignore empty files - # ignore files starting with '.' like '.nfs...' - and f[0] != '.' - ): - tf.add(ffull, arcname=os.path.basename(ffull)) - to_delete.append(f) - - # finally delete files that have been added to tarfile - for f in to_delete: - ret.delete_object(f, force=True) diff --git a/aiida_kkr/tools/__init__.py b/aiida_kkr/tools/__init__.py index 3836cfad..9ee6fc27 100644 --- a/aiida_kkr/tools/__init__.py +++ b/aiida_kkr/tools/__init__.py @@ -19,6 +19,7 @@ from .neworder_potential import * from .find_parent import get_calc_from_remote, get_remote, get_parent from .bdg_tools import get_anomalous_density_data +from .ldau import get_ldaupot_text # expose structure finder from VoronoiCalculation diff --git a/aiida_kkr/tools/common_workfunctions.py b/aiida_kkr/tools/common_workfunctions.py index 3b8a3484..746c2d99 100644 --- a/aiida_kkr/tools/common_workfunctions.py +++ b/aiida_kkr/tools/common_workfunctions.py @@ -386,11 +386,12 @@ def get_inputs_common( inputs.metadata.label = '' if serial: - if _sched in ['slurm', 'pbspro']: + # check for old aiida name (e.g. "slurm") and new aiida name ("core.slurm") of the scheduler + if _sched in ['core.slurm', 'slurm', 'core.pbspro', 'pbspro']: # overwrite settings for serial run options['withmpi'] = False - options['resources'] = {'num_machines': 1} - if _sched in ['sge']: + options['resources'] = {'num_machines': 1, 'tot_num_mpiprocs': 1} + if _sched in ['core.sge', 'sge']: options['withmpi'] = False options['resources'] = {'parallel_env': 'smpslots', 'tot_num_mpiprocs': 1} else: diff --git a/aiida_kkr/tools/find_parent.py b/aiida_kkr/tools/find_parent.py index 2b7431eb..73e8e172 100644 --- a/aiida_kkr/tools/find_parent.py +++ b/aiida_kkr/tools/find_parent.py @@ -30,7 +30,11 @@ def get_remote(parent_folder): try: parent_folder_tmp = parent_folder_tmp0.get_incoming().get_node_by_label('remote_folder') except NotExistent: - parent_folder_tmp = parent_folder_tmp0 + try: + # check if GFhost_folder is there, this is the case for a KkrimpCalculation + parent_folder_tmp = parent_folder_tmp0.get_incoming().get_node_by_label('GFhost_folder') + except NotExistent: + parent_folder_tmp = parent_folder_tmp0 return parent_folder_tmp diff --git a/aiida_kkr/tools/jij_tools.py b/aiida_kkr/tools/jij_tools.py index 8183a4fb..02c7642c 100644 --- a/aiida_kkr/tools/jij_tools.py +++ b/aiida_kkr/tools/jij_tools.py @@ -334,6 +334,11 @@ def parse_jij_calc_data( jijs_shells_x, jijs_shells_y, jijs_shells_z, shells, cell, get_sites(structure), alat, verbose=verbose ) + # sort arrays + isort = np.lexsort(jijs_expanded[:, :5][:, ::-1].transpose()) + jijs_expanded = jijs_expanded[isort] + positions_expanded = positions_expanded[isort] + # create an auxiliary structure that contains only the sites which are used in the Jij step # (i.e. we drop all sites where we don't have couplings) struc_jij_sites, mappings_back, mu_s = get_jij_structure(structure, jijs_expanded, jij_calc) diff --git a/aiida_kkr/tools/ldau.py b/aiida_kkr/tools/ldau.py new file mode 100644 index 00000000..388bd9db --- /dev/null +++ b/aiida_kkr/tools/ldau.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +""" +This module contains tools used in LDA+U calculations +""" + + +def get_ldaupot_text(ldau_settings, ef_Ry, natom, initialize=True, return_luj=False): + """ + create the text for the ldaupot file + """ + from masci_tools.io.common_functions import get_Ry2eV + + eV2Ry = 1. / get_Ry2eV() + + # these lists are extracted from ldau_settings node and then written to the ldaupot file + iatoms_ldau = [] + lopt = [] + ueff = [] + jeff = [] + eref = [] + + # extract values from ldau_settings + for key, val in ldau_settings.items(): + if 'iatom' in key: + iatoms_ldau.append(int(key.split('=')[1])) + lopt.append(val['L']) + # add values in Ry units + jeff.append(val['J'] * eV2Ry) + ueff.append(val['U'] * eV2Ry) + eref.append(val['Eref_EF'] * eV2Ry + ef_Ry) + + if initialize: + # this means we initialize this file + ldaurun = 0 + else: + # this means wldau etc are reused (need to be added to the file) + ldaurun = 1 + + # collect text which is written to ldaupot + txt = [f'{ldaurun} '] + txt_lopt, txt_jeff, txt_ueff, txt_eref = [], [], [], [] + ii = 0 + for iatom in range(natom): + if iatom not in iatoms_ldau: + txt_lopt += [f'{-1} '] + txt_jeff += [f'{0.0} '] + txt_ueff += [f'{0.0} '] + txt_eref += [f'{0.0} '] + else: + txt_lopt += [f'{lopt[ii]} '] + txt_jeff += [f'{jeff[ii]} '] + txt_ueff += [f'{ueff[ii]} '] + txt_eref += [f'{eref[ii]} '] + ii += 1 + txt += ['\n'] + txt_lopt + ['\n'] + txt_ueff + ['\n'] + txt_jeff + ['\n'] + txt_eref + txt += ['\nwldau\nuldau\nphi\n'] + + # add initial matrices + if initialize and 'initial_matrices' in ldau_settings.keys(): + # save for consistency check + nldauatoms = ii + + # change first number from 0 to 1 to signal reading-in instead of calculating initial matrices + txt[0] = '1 ' + + # remove last dummy line, will be replaced with starting values now + txt[-1] = '\n' + txt_wldau = ['wldau\n'] + txt_uldau = ['uldau\n'] + txt_phi = ['phi\n'] + ii = 0 + for iatom in range(natom): + if iatom in iatoms_ldau: + txt_wldau += [f'atom {iatom+1}\n'] + ldau_settings['initial_matrices'][f'iatom={iatom}']['wldau'] + txt_uldau += [f'atom {iatom+1}\n'] + ldau_settings['initial_matrices'][f'iatom={iatom}']['uldau'] + txt_phi += [f'atom {iatom+1}\n'] + ldau_settings['initial_matrices'][f'iatom={iatom}']['phi'] + ii += 1 # count number of atoms + + # consistency check + if nldauatoms != ii: + raise ValueError('initial_matrices input inconsistent') + + # add additional lines to txt + txt = txt + txt_wldau + txt_uldau + txt_phi + + if not return_luj: + return txt + else: + return txt, lopt, jeff, ueff, eref, iatoms_ldau diff --git a/aiida_kkr/tools/plot_kkr.py b/aiida_kkr/tools/plot_kkr.py index a74caa6d..63a1d6ea 100644 --- a/aiida_kkr/tools/plot_kkr.py +++ b/aiida_kkr/tools/plot_kkr.py @@ -13,10 +13,51 @@ __copyright__ = (u'Copyright (c), 2018, Forschungszentrum Jülich GmbH, ' 'IAS-1/PGI-1, Germany. All rights reserved.') __license__ = 'MIT license, see LICENSE.txt file' -__version__ = '0.7.1' +__version__ = '0.7.2' __contributors__ = ('Philipp Rüßmann') +def get_datetime_from_str(calc, verbose=False): + """ + Return a datetime object from the last time a calculation was checked by the scheduler. + + Every calculation should have the 'scheduler_lastchecktime' attribute which has the + following format: '2023-11-08T22:44:13.543215+00:00'. + This is converted to a datetime object that can be sorted. + """ + from datetime import datetime + # get last time stamp of scheduler from calculation attribute + try: + last_time_on_computer = calc.attributes['scheduler_lastchecktime'] + except: + raise ValueError('Failed to get "scheduler_lastchecktime" from calculation.') + # parse date and time from string + date = last_time_on_computer.split('T')[0] + time = last_time_on_computer.split('T')[1].split('.')[0] + # change format + datetime_str = date[2:].replace('-', '/') + ' ' + time + # convert to datetime object + datetime_object = datetime.strptime(datetime_str, '%y/%m/%d %H:%M:%S') + + if verbose: + print(datetime_object) # printed in default format + + #return datetime object of the last time the calculation was checked + return datetime_object + + +def get_sorting_indices(calcs): + """ + Get the sorting index for a list of calculations. + + For each calculation the datetime object of the last time the scheduler checked the + calculation is extracted. This is then sorted and the sorting index array is returned. + """ + datetimes = [get_datetime_from_str(calc) for calc in calcs] + isort = np.array(datetimes).argsort() + return isort + + def remove_empty_atoms(show_empty_atoms, structure, silent=False): # check if empty sphere need to be removed for plotting (ase structgure cannot be constructed for alloys or vacancies) #print('in remove empty atoms:', structure.has_vacancies, ('X' in [i.kind_name for i in structure.sites]) ) @@ -341,6 +382,8 @@ class plot_kkr(object): :type switch_xy: bool :param iatom: list of atom indices which are supposed to be plotted (default: [], i.e. show all atoms) :type iatom: list + :param debug: activate debug output + :type debug: bool additional keyword arguments are passed onto the plotting function which allows, for example, to change the markers used in a DOS plot to crosses via `marker='x'` @@ -359,8 +402,17 @@ def __init__(self, nodes=None, **kwargs): from aiida import load_profile load_profile() + # used to keep track of structure plotting self.sview = None + # debug mode + self.debug = False + if 'debug' in kwargs: + self.debug = kwargs.pop('debug') + print('start plot_kkr') + print('kwargs:', kwargs) + + # grouping of node if a list of nodes is the input instead of a single node groupmode = False if type(nodes) == list: if len(nodes) > 1: @@ -1006,8 +1058,12 @@ def plot_voro_calc(self, node, **kwargs): def plot_kkrimp_calc(self, node, return_rms=False, return_stot=False, plot_rms=True, **kwargs): """plot things from a kkrimp Calculation node""" + if self.debug: + print('in plot_kkrimp_calc') + print('kwargs:', kwargs) + # plot impurity cluster - if kwargs.get('strucplot', True): + if kwargs.get('strucplot', False): if _has_ase_notebook(): self.sview = plot_imp_cluster(node, **kwargs) else: @@ -1048,7 +1104,7 @@ def plot_kkrimp_calc(self, node, return_rms=False, return_stot=False, plot_rms=T else: ptitle = f'pk= {node.pk}' - self.make_kkrimp_rmsplot([rms], [stot], [0], rms_goal, ptitle, **kwargs) + self.make_kkrimp_rmsplot([rms], [stot], [node], rms_goal, ptitle, **kwargs) # now return values return_any, return_list = False, [] @@ -1074,10 +1130,14 @@ def plot_kkrimp_sub_wc(self, node, **kwargs): """plot things from a kkrimp_sub_wc workflow""" from aiida_kkr.calculations import KkrimpCalculation + if self.debug: + print('in plot_kkrimp_sub_wc') + print('kwargs:', kwargs) + impcalcs = [i.node for i in node.get_outgoing(node_class=KkrimpCalculation).all()] # plot impurity cluster - if len(impcalcs) > 0 and kwargs.get('strucplot', True): + if len(impcalcs) > 0 and kwargs.get('strucplot', False): if _has_ase_notebook(): self.sview = plot_imp_cluster(impcalcs[0], **kwargs) else: @@ -1088,10 +1148,9 @@ def plot_kkrimp_sub_wc(self, node, **kwargs): kwargs.pop(k) # extract rms from calculations - rms_all, pks_all, stot_all = [], [], [] + rms_all, stot_all = [], [] rms_goal = None for impcalc in impcalcs: - pks_all.append(impcalc.pk) rms_tmp, rms_goal_tmp, stot_tmp = self.plot_kkrimp_calc( impcalc, return_rms=True, return_stot=True, plot_rms=False ) @@ -1108,9 +1167,9 @@ def plot_kkrimp_sub_wc(self, node, **kwargs): else: ptitle = f'pk= {node.pk}' - self.make_kkrimp_rmsplot(rms_all, stot_all, pks_all, rms_goal, ptitle, **kwargs) + self.make_kkrimp_rmsplot(rms_all, stot_all, impcalcs, rms_goal, ptitle, **kwargs) - def make_kkrimp_rmsplot(self, rms_all, stot_all, pks_all, rms_goal, ptitle, **kwargs): + def make_kkrimp_rmsplot(self, rms_all, stot_all, list_of_impcalcs, rms_goal, ptitle, **kwargs): """ plot rms and total spin moment of kkrimp calculation or series of kkrimp calculations """ @@ -1141,12 +1200,14 @@ def make_kkrimp_rmsplot(self, rms_all, stot_all, pks_all, rms_goal, ptitle, **kw # plotting of convergence properties (rms etc.) if len(rms_all) > 0: # sort rms values and flatten array - reorder_rms = array(pks_all).argsort() + reorder_rms = get_sorting_indices(list_of_impcalcs) rms, niter_calcs, stot = [], [0], [] - for i in array(rms_all)[reorder_rms]: + rms_all_sorted = [rms_all[i] for i in reorder_rms] + for i in rms_all_sorted: rms += list(i) niter_calcs.append(len(i) - 0.5) - for i in array(stot_all)[reorder_rms]: + stot_sorted = [stot_all[i] for i in reorder_rms] + for i in stot_sorted: if i is not None: stot += list(i) # now plot @@ -1190,6 +1251,10 @@ def plot_kkrimp_dos_wc(self, node, **kwargs): from masci_tools.vis.kkr_plot_dos import dosplot from matplotlib.pyplot import show, figure, title, xticks, xlabel, axvline + if self.debug: + print('in plot_kkrimp_dos_wc') + print('kwargs:', kwargs) + interpol, all_atoms, l_channels, sum_spins, switch_xy = True, False, True, False, False ptitle = None if 'ptitle' in list(kwargs.keys()): diff --git a/aiida_kkr/tools/tools_kkrimp.py b/aiida_kkr/tools/tools_kkrimp.py index 0028927d..2ba5e914 100644 --- a/aiida_kkr/tools/tools_kkrimp.py +++ b/aiida_kkr/tools/tools_kkrimp.py @@ -592,3 +592,15 @@ def write_scoef_full_imp_cls(imp_info_node, path, rescale_alat=None): # write scoef file write_scoef(imp_cls, path) + + +def get_imp_info_from_parent(parent_calc): + """ + Returns impurity_info node from inputs to parent_calc calculation node + + Returns None if no input node of this name is found. + """ + imp_info = None + if 'impurity_info' in parent_calc.get_incoming().all_link_labels(): + imp_info = parent_calc.get_incoming().get_node_by_label('impurity_info') + return imp_info diff --git a/aiida_kkr/workflows/__init__.py b/aiida_kkr/workflows/__init__.py index 0d8659b7..1e6fbe26 100644 --- a/aiida_kkr/workflows/__init__.py +++ b/aiida_kkr/workflows/__init__.py @@ -15,3 +15,4 @@ from ._combine_imps import combine_imps_wc from ._decimation import kkr_decimation_wc from .jijs import kkr_jij_wc +from .imp_BdG import kkrimp_BdG_wc diff --git a/aiida_kkr/workflows/_combine_imps.py b/aiida_kkr/workflows/_combine_imps.py index 4b798cae..e5b384fd 100644 --- a/aiida_kkr/workflows/_combine_imps.py +++ b/aiida_kkr/workflows/_combine_imps.py @@ -4,7 +4,7 @@ """ from aiida.engine import WorkChain, if_, ToContext, calcfunction -from aiida.orm import load_node, Dict, WorkChainNode, Int, RemoteData, Bool, ArrayData +from aiida.orm import load_node, Dict, WorkChainNode, Int, RemoteData, Bool, ArrayData, CalcJobNode from aiida_kkr.calculations import KkrCalculation, KkrimpCalculation from aiida_kkr.workflows import kkr_imp_sub_wc, kkr_flex_wc, kkr_imp_wc from aiida_kkr.tools.combine_imps import ( @@ -19,9 +19,12 @@ __copyright__ = (u'Copyright (c), 2020, Forschungszentrum Jülich GmbH, ' 'IAS-1/PGI-1, Germany. All rights reserved.') __license__ = 'MIT license, see LICENSE.txt file' -__version__ = '0.3.1' +__version__ = '0.3.2' __contributors__ = (u'Philipp Rüßmann , Rubel Mozumder') +# activate debug writeout +_debug = False + class combine_imps_wc(WorkChain): """ @@ -130,9 +133,9 @@ def define(cls, spec): workflows or of an `KkrimpCalculation`. Use these output Dict nodes: - * for `kkr_imp_wc`: single_imp_worlfow.outputs.workflow_info - * for `kkr_imp_sub_wc`: single_imp_worlfow.outputs.workflow_info - * for `KkrimpCalculation`: single_imp_worlfow.outputs.output_parameters + * for `kkr_imp_wc`: single_imp_workfow.outputs.workflow_info + * for `kkr_imp_sub_wc`: single_imp_workfow.outputs.workflow_info + * for `KkrimpCalculation`: single_imp_workfow.outputs.output_parameters """ ) @@ -378,12 +381,21 @@ def extract_imps_info_exact_cluster(self): self.report(f'DEBUG: The is the imps_info_in_exact_cluster dict: {imps_info_in_exact_cluster}\n') return imps_info_in_exact_cluster + def get_impinfo_from_hostGF(self, imp_calc): + """ + Extract impurity infor node from the incoming host GF folder + """ + GF_input = imp_calc.inputs.host_Greenfunction_folder + parent_calc = GF_input.get_incoming(node_class=CalcJobNode).first().node + impinfo = parent_calc.inputs.impurity_info + return impinfo + def imps_info_exact_cluster_2imps(self, single_imp1_wc, single_imp2_wc, offset_imp2): """ This construct a python dict keeping info about two single inpurities with respect to the original host structure e.i. before transforming the center to the first impurity position. """ - impinfo1 = single_imp1_wc.inputs.impurity_info - impinfo2 = single_imp2_wc.inputs.impurity_info + impinfo1 = self.get_impinfo_from_hostGF(single_imp1_wc) + impinfo2 = self.get_impinfo_from_hostGF(single_imp2_wc) # imp_info_in_exact_cluster keeps the exact data before creating the cluster will help to add more imps later. imps_info_in_exact_cluster = { 'Zimps': [], @@ -479,13 +491,16 @@ def create_big_cluster(self): # pylint: disable=inconsistent-return-statements imp2 = self.ctx.imp2 single_single = self.ctx.single_vs_single if single_single: - impinfo1 = imp1.inputs.impurity_info + if _debug: + print('DEBUG:', list(imp1.inputs)) + impinfo1 = self.get_impinfo_from_hostGF(imp1) + # impinfo1 = imp1.inputs.impurity_info else: if imp1.process_class == self.__class__: imp1 = imp1.get_outgoing(node_class=kkr_imp_sub_wc).all()[0].node impinfo1 = imp1.inputs.impurity_info self.report(f'DEBUG: impinfo1 : {impinfo1.get_dict()} .') - impinfo2 = imp2.inputs.impurity_info + impinfo2 = self.get_impinfo_from_hostGF(imp2) host_structure = self.ctx.host_structure offset_imp2 = self.inputs.offset_imp2 @@ -592,9 +607,11 @@ def run_gf_writeout(self): #take gf_writeout directly from input to KkrimpCalculation gf_writeout_calc = self.ctx.imp1.inputs.host_Greenfunction_folder.get_incoming(node_class=KkrCalculation ).first().node - if self.ctx.imp1.process_class == kkr_imp_sub_wc: + if (self.ctx.imp1.process_class == kkr_imp_sub_wc or self.ctx.imp1.process_class == KkrimpCalculation): imp1_sub = self.ctx.imp1 else: + if _debug: + print('DEBUG:', self.ctx.imp1, list(self.ctx.imp1.inputs)) imp1_sub = self.ctx.imp1.get_outgoing(node_class=kkr_imp_sub_wc).first().node if gf_writeout_calc is None: gf_writeout_calc = imp1_sub.inputs.remote_data.get_incoming(node_class=KkrCalculation).first().node diff --git a/aiida_kkr/workflows/bs.py b/aiida_kkr/workflows/bs.py index 0b5c02b7..726c2d9e 100644 --- a/aiida_kkr/workflows/bs.py +++ b/aiida_kkr/workflows/bs.py @@ -20,7 +20,7 @@ __copyright__ = (u'Copyright (c), 2020, Forschungszentrum Jülich GmbH, ' 'IAS-1/PGI-1, Germany. All rights reserved.') __license__ = 'MIT license, see LICENSE.txt file' -__version__ = '0.1.6' +__version__ = '0.1.7' __contributors__ = (u'Rubel Mozumder', u'Philipp Rüßmann') @@ -133,6 +133,22 @@ def define(cls, spec): required=False, help='Overwrite some input parameters of the parent KKR calculation.' ) + # expose LDAU input node + 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. +""" + ) # Here outputs are defined spec.output('results_wf', valid_type=Dict, required=True) @@ -448,6 +464,11 @@ def get_BS(self): noco_angles = parent_calc.inputs.initial_noco_angles self.report(f'extract nonco angles and use from parent ({noco_angles})') + # LDA+U settings + if 'settings_LDAU' in self.inputs: + self.report('Add settings_LDAU input node') + inputs.settings_LDAU = self.inputs.settings_LDAU + BS_run = self.submit(KkrCalculation, **inputs) self.ctx.last_calc = BS_run diff --git a/aiida_kkr/workflows/dos.py b/aiida_kkr/workflows/dos.py index 0b167574..d58bac0b 100644 --- a/aiida_kkr/workflows/dos.py +++ b/aiida_kkr/workflows/dos.py @@ -26,7 +26,7 @@ __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.8.3' +__version__ = '0.8.4' __contributors__ = u'Philipp Rüßmann' @@ -122,6 +122,22 @@ def define(cls, spec): required=False, help='Overwrite some input parameters of the parent KKR calculation.' ) + # expose LDAU input node + spec.input( + 'settings_LDAU', + valid_type=orm.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('results_wf', valid_type=orm.Dict, required=True, help='Results collected by the workflow.') @@ -428,6 +444,11 @@ def get_dos(self): inputs['initial_noco_angles'] = noco_angles self.report(f'extract nonco angles and use from parent ({noco_angles})') + # LDA+U settings + if 'settings_LDAU' in self.inputs: + self.report('Add settings_LDAU input node') + inputs.settings_LDAU = self.inputs.settings_LDAU + # run the DOS calculation self.report('INFO: doing calculation') dosrun = self.submit(KkrCalculation, **inputs) diff --git a/aiida_kkr/workflows/gf_writeout.py b/aiida_kkr/workflows/gf_writeout.py index 4cc21a11..f5fc9036 100644 --- a/aiida_kkr/workflows/gf_writeout.py +++ b/aiida_kkr/workflows/gf_writeout.py @@ -8,22 +8,22 @@ from aiida.engine import WorkChain, ToContext, if_ from masci_tools.io.kkr_params import kkrparams from aiida_kkr.tools.common_workfunctions import test_and_get_codenode, get_parent_paranode, update_params_wf, get_inputs_kkr -from aiida_kkr.calculations.kkr import KkrCalculation -from aiida_kkr.calculations import KkrimpCalculation +from aiida_kkr.calculations import KkrCalculation, KkrimpCalculation, VoronoiCalculation from aiida.engine import CalcJob from aiida.orm import CalcJobNode -from masci_tools.io.common_functions import get_Ry2eV +from masci_tools.io.common_functions import get_Ry2eV, get_ef_from_potfile from aiida.orm import WorkChainNode, RemoteData, StructureData, Dict, FolderData from aiida.common.exceptions import InputValidationError from aiida_kkr.tools.save_output_nodes import create_out_dict_node from aiida_kkr.tools.common_workfunctions import get_username from aiida_kkr.workflows.dos import kkr_dos_wc +from aiida_kkr.workflows.bs import set_energy_params import os __copyright__ = (u'Copyright (c), 2018, Forschungszentrum Jülich GmbH, ' 'IAS-1/PGI-1, Germany. All rights reserved.') __license__ = 'MIT license, see LICENSE.txt file' -__version__ = '0.5.5' +__version__ = '0.5.6' __contributors__ = (u'Fabian Bertoldo', u'Philipp Rüßmann') # ToDo: add more default values to wf_parameters @@ -88,7 +88,7 @@ def define(cls, spec): # Take input of the workflow or use defaults defined above super(kkr_flex_wc, cls).define(spec) - spec.input('kkr', valid_type=Code, required=True) + spec.input('kkr', valid_type=Code, required=False) spec.input('options', valid_type=Dict, required=False, default=lambda: Dict(dict=cls._options_default)) spec.input('wf_parameters', valid_type=Dict, required=False) spec.input('remote_data', valid_type=RemoteData, required=True) @@ -134,6 +134,9 @@ def define(cls, spec): 'ERROR_KKR_CALCULATION_FAILED', message='ERROR: KKR calculation to write out kkrflex files unsuccessful' ) + spec.exit_code( + 107, 'ERROR_NO_EF_FOUND', message='ERROR: Could not extract value for Fermi level from parent calculation' + ) # specify the outputs #spec.output('remote_folder', valid_type=RemoteData) @@ -328,12 +331,6 @@ def set_params_flex(self): self.report(f'INFO: overwriting KKR parameter: {key} with {val} from params_kkr_overwrite input node') input_links['params_kkr_overwrite'] = self.inputs.params_kkr_overwrite - runopt = [i.strip() for i in runopt] - if 'KKRFLEX' not in runopt: - runopt.append('KKRFLEX') - - updatedict['RUNOPT'] = runopt - self.report(f'INFO: RUNOPT set to: {runopt}') if 'wf_parameters' in self.inputs: @@ -341,24 +338,26 @@ def set_params_flex(self): remote_data_parent = self.inputs.remote_data parent_calc = remote_data_parent.get_incoming(link_label_filter='remote_folder').first().node ef = parent_calc.outputs.output_parameters.get_dict().get('fermi_energy') + # check if ef needs to be taken from a voronoi parent + if ef is None: + objects = parent_calc.outputs.retrieved.list_object_names() + fname = VoronoiCalculation._OUT_POTENTIAL_voronoi + if fname in objects: + with parent_calc.outputs.retrieved.open(fname) as _f: + ef = get_ef_from_potfile(_f) + if ef is None: + return self.exit_codes.ERROR_NO_EF_FOUND # pylint: disable=no-member if self.ctx.dos_run: - # convert emin, emax to Ry units - emin = ef + self.ctx.dos_params_dict['emin'] / get_Ry2eV() - emax = ef + self.ctx.dos_params_dict['emax'] / get_Ry2eV() - # set dos params - for key, val in { - 'EMIN': emin, - 'EMAX': emax, - 'NPT2': self.ctx.dos_params_dict['nepts'], - 'NPOL': 0, - 'NPT1': 0, - 'NPT3': 0, - 'BZDIVIDE': self.ctx.dos_params_dict['kmesh'], - 'IEMXD': self.ctx.dos_params_dict['nepts'], - 'TEMPR': self.ctx.dos_params_dict['tempr'] - }.items(): + # possibly remove keys which are overwritten from DOS params + for key in ['BZDIVIDE', 'NPT2', 'EMIN', 'EMAX', 'TEMPR']: + if key in updatedict: + updatedict.pop(key) + # now add the dos settings to the updatedict + for key, val in self.ctx.dos_params_dict.items(): updatedict[key] = val + # set the energy contour properly (i.e. changes units from eV to Ry etc) + updatedict = set_energy_params(updatedict, ef, kkrparams()) elif self.ctx.ef_shift != 0: # get Fermi energy shift in eV ef_shift = self.ctx.ef_shift #set new E_F in eV @@ -372,10 +371,13 @@ def set_params_flex(self): updatedict['ef_set'] = ef_new #construct the final param node containing all of the params - updatenode = Dict(updatedict) - updatenode.label = label + 'KKRparam_flex' - updatenode.description = descr + 'KKR parameter node extracted from parent parameters and wf_parameter and options input node.' - paranode_flex = update_params_wf(self.ctx.input_params_KKR, updatenode, **input_links) + if updatedict != {}: + updatenode = Dict(updatedict) + updatenode.label = label + 'KKRparam_flex' + updatenode.description = descr + 'KKR parameter node extracted from parent parameters and wf_parameter and options input node.' + paranode_flex = update_params_wf(self.ctx.input_params_KKR, updatenode, **input_links) + else: + paranode_flex = self.ctx.input_params_KKR self.ctx.flex_kkrparams = paranode_flex self.ctx.flex_runopt = runopt @@ -459,9 +461,6 @@ def move_kkrflex_files(self): abspath_tmat_new = os.path.join(gf_upload_path, KkrimpCalculation._KKRFLEX_TMAT) abspath_gmat_new = os.path.join(gf_upload_path, KkrimpCalculation._KKRFLEX_GREEN) - self.report(f'contents of gf_upload_path: {os.listdir(gf_upload_path)}') - self.report(f'contents of remote: {os.listdir(abspath_remote)}') - # create a symlink to to original dir to be able to find it easily connection.symlink(os.path.join(abspath_remote, 'out_kkr'), os.path.join(gf_upload_path, 'out_kkr')) # copy files diff --git a/aiida_kkr/workflows/imp_BdG.py b/aiida_kkr/workflows/imp_BdG.py new file mode 100644 index 00000000..6f695b94 --- /dev/null +++ b/aiida_kkr/workflows/imp_BdG.py @@ -0,0 +1,465 @@ +# Workflow for impurity BdG calculation from converged normal state impurity portential and BdG host calculation + +from aiida.engine import WorkChain, ToContext, if_ +from aiida.orm import Dict, RemoteData, Code, CalcJobNode, WorkChainNode, Float, Bool, XyData, SinglefileData +from aiida_kkr.workflows import kkr_imp_wc, kkr_imp_dos_wc +from aiida_kkr.tools.find_parent import get_calc_from_remote +from aiida_kkr.tools.common_workfunctions import test_and_get_codenode + +__copyright__ = (u'Copyright (c), 2022, Forschungszentrum Jülich GmbH, ' + 'IAS-1/PGI-1, Germany. All rights reserved.') +__license__ = 'MIT license, see LICENSE.txt file' +__version__ = '0.1.1' +__contributors__ = (u'David Antognini Silva, Philipp Rüßmann') + +# TODO: add _wf_default parameters and activate get_wf_defaults method +# TODO: add input interdependencies in the workchain description +# TODO: add lmdos output node + + +class kkrimp_BdG_wc(WorkChain): + """ + Workchain for one-shot BdG impurity DOS calculation from a converged normal state host calculation. + 1) Normal state impurity potential scf + 2) One-shot BdG DOS calcluation + a) Writing out of the BdG kkrflex DOS files + b) One-shot BdG impurity DOS + The intermediate steps (1 & 2a) can be skipped by providing the corresponding nodes as inputs to the workflow. + + inputs:: + :param options: (Dict), computer options + :param impurity_info: (Dict), information about the impurity cluster + :param voronoi: (Code), Voronoi code for creating the impurity starting potential + :param kkr: (Code), KKR host code for the writing out kkrflex files + :param kkrimp: (Code), KKR impurity code for the normal state impurity scf and BdG impurity DOS calculation + :param BdG_settings: (Dict), set BdG parameters + + :param imp_scf.startpot: (SinglefileData), converged impurity potential, skips the impurity scf calculation if provided + :param imp_scf.wf_parameters: (Dict), parameters for the kkr impurity scf + :param imp_scf.gf_writeout.params_kkr_overwrite: (Dict), set some input parameters of the KKR calculation for the GF writeout step of impurity scf workchain + :param imp_scf.gf_writeout.options: (Dict), computer settings + :param imp_scf.scf.params_overwrite: (Dict), set some input parameters of the KKR impurity scf + :param imp_scf.options: (Dict), computer settings + :param imp_scf.remote_data_host: (RemoteData), parent folder of converged host normal state KkrCalculation + + :param dos.wf_parameters: (Dict), parameters for the DOS calculation + :param dos.gf_dos_remote: (RemoteData), node of precomputed host GF for DOS energy contour + :param dos.gf_writeout.params_kkr_overwrite: (Dict), set some input parameters of the KKR calculation for the GF writeout step of imßpurity dos workchain + :param dos.gf_writeout.host_remote: (RemoteData), parent folder of kkrflex writeout step for DOS calculation + :param dos.gf_writeout.kkr: (Code), KKR code for writing out of kkrflex files for impurity DOS calculation + :param dos.gf_writeout.options: (Dict), computer settings + :param dos.options: (Dict), computer settings + + returns:: + :return workflow_info: (Dict), Information on workflow results + :return output_parameters: (Dict), output parameters of the workflow + :return dos_data: (XyData), impurity DOS data output node + :return dos_data_interpol: (XyData), interpolated DOS data output node + :return impurity_potential: (SinglefileData), converged normal state impurity potential node + :return gf_host_BdG: (RemoteData), kkrflex writeout step files of DOS calculation + """ + + _wf_version = __version__ + + # _wf_default = { + # 'default': None, #Put in here default input parameters + # } + _options_default = { + 'max_wallclock_seconds': 36000, + 'resources': { + 'num_machines': 1 + }, + 'withmpi': True, + 'queue_name': '' + } + + # @classmethod + # def get_wf_defaults(self, silent=False): + # """ + # Return the default values of the workflow parameters (wf_parameters input node) + # """ + # if not silent: + # print(f'Version of the kkrimp_BdG_wc workflow: {self._wf_version}') + # return self._wf_default.copy() + + @classmethod + def define(cls, spec): + """ + Layout of the workflow, defines the input nodes and the outline of the workchain + """ + super(kkrimp_BdG_wc, cls).define(spec) + + spec.input( + 'options', + valid_type=Dict, + required=False, + default=lambda: Dict(dict=cls._options_default), + help= + 'Computer options (walltime etc.) passed onto KkrCalculation, fall back to settings from parent calculation if not given' + ) + + spec.input( + 'impurity_info', + valid_type=Dict, + required=False, + help='Information of the impurity like position in the unit cell, screening cluster, atom type.' + ) + + spec.input('kkr', valid_type=Code, required=False, help='KKRhost code, needed to run the KkrCalculation') + + spec.input( + 'kkrimp', valid_type=Code, required=True, help='KKRimp code used to converge the impurity calculation' + ) + + spec.input( + 'voronoi', + valid_type=Code, + required=False, + help='Voronoi code used to create the impurity starting potential.' + ) + + spec.input( + 'calc_DOS', + valid_type=Bool, + required=False, + default=lambda: Bool(False), + help='Set this to TRUE to calculate DOS' + ) + + spec.input( + 'BdG_settings', + valid_type=Dict, + default=lambda: Dict( + dict={ + # 'DELTA_BDG': 1e-4, # 1.36 meV # could be set in the future? + 'USE_BdG': True, + 'USE_E_SYMM_BDG': True, + # 'FIX_NONCO_ANGLES': True, # could be set in the future? + } + ), + help='Define BdG parameters' + ) + + # expose inputs for impurity normal state scf + spec.expose_inputs( + kkr_imp_wc, + namespace='imp_scf', + include=('startpot', 'wf_parameters', 'gf_writeout', 'scf.params_overwrite', 'scf.initial_noco_angles') + ) + spec.inputs['imp_scf']['gf_writeout']['kkr'].required = False + spec.input('imp_scf.options', required=False, help='computer options for impurity scf step') + + spec.input( + 'imp_scf.remote_data_host', + valid_type=RemoteData, + required=False, + help='Parent folder of previously converged host normal state KkrCalculation' + ) + + # inputs for impurity BdG scf + spec.expose_inputs( + kkr_imp_wc, + namespace='BdG_scf', + include=('startpot', 'remote_data_gf', 'gf_writeout', 'scf.initial_noco_angles') + ) + spec.inputs['BdG_scf']['gf_writeout']['kkr'].required = False + spec.input('BdG_scf.options', required=False, help='computer options for BdG impurity scf step') + + spec.input( + 'BdG_scf.remote_data_host', required=False, help='Parent folder of previously converged BdG KkrCalculation' + ) + + # inputs for impurity dos + spec.expose_inputs( + kkr_imp_dos_wc, + namespace='dos', + include=('wf_parameters', 'gf_dos_remote', 'gf_writeout', 'initial_noco_angles') + ) + + spec.input( + 'dos.gf_writeout.host_remote', + valid_type=RemoteData, + required=False, + help='Parent calculation from where the GF writeout starts. ' + ) + + spec.input( + 'dos.gf_writeout.kkr', + valid_type=Code, + required=False, + help='KKRhost code used to create DOS kkrflex files' + ) + + spec.input('dos.options', valid_type=Dict, required=False, help='Computer options for DOS step') + + # Here outputs are defined + + spec.output('workflow_info', valid_type=Dict, required=False) + spec.output('output_parameters', valid_type=Dict, required=False) + spec.output('dos_data', required=False, valid_type=XyData) + spec.output('dos_data_interpol', required=False, valid_type=XyData) + spec.output('impurity_potential', valid_type=SinglefileData) + spec.output('gf_host_BdG', valid_type=RemoteData, required=False) + + # Here outlines are being specified + spec.outline( + # For initialiging workflow + cls.start, + cls.validate_input, + if_(cls.do_imp_pot_calc)(cls.imp_pot_calc), + if_(cls.do_BdG_scf)(cls.imp_BdG_calc), + if_(cls.do_calc_DOS)(cls.DOS_calc), + cls.results + ) + + # Define all possible error messages + spec.exit_code( + 100, 'ERROR_KKRCODE_NOT_CORRECT', 'The code you provided for kkr does not use the plugin kkr.kkr' + ) + spec.exit_code( + 101, 'ERROR_KKRIMPCODE_NOT_CORRECT', 'The code you provided for kkrimp does not use the plugin kkr.kkrimp' + ) + spec.exit_code( + 102, 'ERROR_VORONOICODE_NOT_CORRECT', + 'The code you provided for voronoi does not use the plugin kkr.voronoi' + ) + + spec.exit_code(200, 'ERROR_INVALID_PARENT', 'Parent calculation is not valid') + + #Now here we define all the functions from the outline + + def start(self): + """ + Set up context of the workflow + """ + self.report(f'INFO: started KKR BdG impurity version {self._wf_version}') + + def validate_input(self): + """ + validate inputs + """ + + # validate for kkr code + if 'kkr' in self.inputs: + try: + test_and_get_codenode(self.inputs.kkr, 'kkr.kkr', use_exceptions=True) + except ValueError: + return self.exit_codes.ERROR_KKRCODE_NOT_CORRECT # pylint: disable=no-member + + # validate for kkrimp code, always done because this is a required input + try: + test_and_get_codenode(self.inputs.kkrimp, 'kkr.kkrimp', use_exceptions=True) + except ValueError: + return self.exit_codes.ERROR_KKRIMPCODE_NOT_CORRECT # pylint: disable=no-member + + # validate for voronoi code + if 'voronoi' in self.inputs: + try: + test_and_get_codenode(self.inputs.voronoi, 'kkr.voro', use_exceptions=True) + except ValueError: + return self.exit_codes.ERROR_VORONOICODE_NOT_CORRECT # pylint: disable=no-member + + def do_imp_pot_calc(self): + """ + run impurity potential calculation only if impurity potential not provided already + """ + if not 'startpot' in self.inputs.imp_scf: + return True + + def imp_pot_calc(self): + """ + run normal state impurity scf calculation + """ + + builder = kkr_imp_wc.get_builder() + + builder.impurity_info = self.inputs.impurity_info + builder.voronoi = self.inputs.voronoi + builder.kkr = self.inputs.kkr + builder.kkrimp = self.inputs.kkrimp + builder.remote_data_host = self.inputs.imp_scf.remote_data_host + builder.wf_parameters = self.inputs.imp_scf.wf_parameters + if 'options' in self.inputs.imp_scf: + builder.options = self.inputs.imp_scf.options + else: + builder.options = self.inputs.options + if 'initial_noco_angles' in self.inputs.imp_scf: + builder.scf.initial_noco_angles = self.inputs.imp_scf.initial_noco_angles # pylint: disable=no-member + + if 'gf_writeout' in self.inputs.imp_scf: + if 'options' in self.inputs.imp_scf.gf_writeout: + builder.gf_writeout.options = self.inputs.imp_scf.gf_writeout.options # pylint: disable=no-member + if 'kkr' in self.inputs.imp_scf.gf_writeout: + builder.kkr = self.inputs.imp_scf.gf_writeout.kkr + if 'params_kkr_overwrite' in self.inputs.imp_scf.gf_writeout: + builder.params_kkr_overwrite = self.inputs.imp_scf.gf_writeout.params_kkr_overwrite + builder.gf_writeout.kkr = builder.kkr # pylint: disable=no-member + + if 'params_overwrite' in self.inputs.imp_scf.scf: + builder.scf.params_overwrite = self.inputs.imp_scf.scf.params_overwrite # pylint: disable=no-member + + imp_calc = self.submit(builder) + + return ToContext(last_imp_calc=imp_calc) + + def imp_BdG_calc(self): + """ + run BdG one-shot impurity calculation + """ + + builder = kkr_imp_wc.get_builder() + if 'impurity_info' in self.inputs: + builder.impurity_info = self.inputs.impurity_info + builder.voronoi = self.inputs.voronoi + builder.kkr = self.inputs.kkr + builder.kkrimp = self.inputs.kkrimp + + if 'startpot' in self.inputs.imp_scf: + builder.startpot = self.inputs.imp_scf.startpot + else: + builder.startpot = self.ctx.last_imp_calc.outputs.converged_potential + + if 'remote_data_gf' in self.inputs.BdG_scf: + builder.remote_data_gf = self.inputs.BdG_scf.remote_data_gf + + if 'gf_writeout' in self.inputs.BdG_scf: + if 'kkr' in self.inputs.BdG_scf.gf_writeout: + builder.kkr = self.inputs.BdG_scf.gf_writeout.kkr + if 'params_kkr_overwrite' in self.inputs.BdG_scf.gf_writeout: + builder.params_kkr_overwrite = self.inputs.BdG_scf.gf_writeout.params_kkr_overwrite + if 'kkr' in self.inputs: + builder.gf_writeout.kkr = builder.kkr # pylint: disable=no-member + if 'initial_noco_angles' in self.inputs.BdG_scf: + builder.scf.initial_noco_angles = self.inputs.BdG_scf.initial_noco_angles # pylint: disable=no-member + + builder.remote_data_host = self.inputs.BdG_scf.remote_data_host + + if 'options' in self.inputs.BdG_scf: + builder.options = self.inputs.BdG_scf.options + else: + if 'options' in self.inputs.imp_scf: + builder.options = self.inputs.imp_scf + else: + builder.options = self.inputs.options + + settings = kkr_imp_wc.get_wf_defaults()[1] + settings['strmix'] = 0.01 + settings['aggrmix'] = 0.01 + settings['nsteps'] = 1 + settings['kkr_runmax'] = 1 + settings['mag_init'] = True + builder.wf_parameters = Dict(dict=settings) + BdG_params = self.inputs.BdG_settings.get_dict() + builder.scf.params_overwrite = Dict(dict=BdG_params) # pylint: disable=no-member + + imp_calc_BdG = self.submit(builder) + + return ToContext(last_imp_calc_BdG=imp_calc_BdG) + + def do_BdG_scf(self): + """ + run BdG scf step only if BdG impurity potential not provided and DOS calculation is not planned + """ + # BdG scf not implemented in the KKRimp code yet + return False + # if (not 'startpot' in self.inputs.BdG_scf) and (not self.inputs.calc_DOS): + # return True + + def do_calc_DOS(self): + """ + check if DOS calculation should be done + """ + if self.inputs.calc_DOS: + return True + + def DOS_calc(self): + """ + run DOS calculation + """ + from aiida_kkr.workflows import kkr_imp_dos_wc + + builder = kkr_imp_dos_wc.get_builder() + + if 'kkr' in self.inputs: + builder.kkr = self.inputs.kkr + builder.kkrimp = self.inputs.kkrimp + + #define computer options + if 'options' in self.inputs.dos: + builder.options = self.inputs.dos.options + else: + if 'options' in self.inputs.BdG_scf: + builder.options = self.inputs.BdG_scf.options + else: + if 'options' in self.inputs.imp_scf: + builder.options = self.inputs.imp_scf.options + else: + builder.options = self.inputs.options + + # set nonco angles + if 'initial_noco_angles' in self.inputs.dos: + builder.initial_noco_angles = self.inputs.dos.initial_noco_angles + + # skip BdG step and just use the starting potential instead? + # faster and same accuracy?! + if 'startpot' in self.inputs.BdG_scf: + builder.imp_pot_sfd = self.inputs.BdG_scf.startpot + else: + if not self.inputs.calc_DOS: + builder.imp_pot_sfd = self.ctx.last_imp_calc_BdG.outputs.converged_potential + else: + if 'startpot' in self.inputs.imp_scf: + builder.imp_pot_sfd = self.inputs.imp_scf.startpot + else: + builder.imp_pot_sfd = self.ctx.last_imp_calc.outputs.converged_potential + + if 'impurity_info' in self.inputs: + builder.impurity_info = self.inputs.impurity_info + + builder.wf_parameters = Dict(dict=kkr_imp_dos_wc.get_wf_defaults()) + + if 'wf_parameters' in self.inputs.dos: + builder.wf_parameters = self.inputs.dos.wf_parameters + + BdG_params = self.inputs.BdG_settings.get_dict() + builder.BdG.params_overwrite = Dict(dict=BdG_params) # pylint: disable=no-member + + if 'gf_writeout' in self.inputs.dos: + # set optional inputs + if 'kkr' in self.inputs.dos.gf_writeout: + builder.kkr = self.inputs.dos.gf_writeout.kkr + if 'params_kkr_overwrite' in self.inputs.dos.gf_writeout: + builder.params_kkr_overwrite = self.inputs.dos.gf_writeout.params_kkr_overwrite + if 'host_remote' in self.inputs.dos.gf_writeout: + builder.host_remote = self.inputs.dos.gf_writeout.host_remote + if 'options' in self.inputs.dos.gf_writeout: + builder.gf_writeout.options = self.inputs.dos.gf_writeout.options # pylint: disable=no-member + if 'kkr' in self.inputs: + builder.gf_writeout.kkr = builder.kkr # pylint: disable=no-member + if 'gf_dos_remote' in self.inputs.dos: + builder.gf_dos_remote = self.inputs.dos.gf_dos_remote + + DOS_calc = self.submit(builder) + + return ToContext(DOS_node=DOS_calc) + + def results(self): + """ + return the results nodes of the workchain + """ + if self.inputs.calc_DOS: + self.out('dos_data', self.ctx.DOS_node.outputs.dos_data) + self.out('dos_data_interpol', self.ctx.DOS_node.outputs.dos_data_interpol) + else: + if 'startpot' not in self.inputs.BdG_scf: + self.out('workflow_info', self.ctx.last_imp_calc_BdG.outputs.workflow_info) + self.out('output_parameters', self.ctx.last_imp_calc_BdG.outputs.last_calc_output_parameters) + if 'remote_data_gf' not in self.inputs.BdG_scf: + self.out('gf_host_BdG', self.ctx.last_imp_calc_BdG.outputs.remote_data_gf) + else: + self.out('gf_host_BdG', self.inputs.BdG_scf.remote_data_gf) + + if 'startpot' not in self.inputs.imp_scf: + self.out('impurity_potential', self.ctx.last_imp_calc.outputs.converged_potential) + else: + self.out('impurity_potential', self.inputs.imp_scf.startpot) diff --git a/aiida_kkr/workflows/jijs.py b/aiida_kkr/workflows/jijs.py index a8c2415c..f39bbc82 100644 --- a/aiida_kkr/workflows/jijs.py +++ b/aiida_kkr/workflows/jijs.py @@ -19,7 +19,7 @@ __copyright__ = (u'Copyright (c), 2022, Forschungszentrum Jülich GmbH, ' 'IAS-1/PGI-1, Germany. All rights reserved.') __license__ = 'MIT license, see LICENSE.txt file' -__version__ = '0.1.1' +__version__ = '0.1.2' __contributors__ = (u'Philipp Rüßmann') @@ -417,8 +417,8 @@ def _get_jijrad(self): # maybe use value provided in input instead para = {k.lower(): v for k, v in self.ctx.parent_calc.inputs.parameters.get_dict().items()} - if 'use_alat_input' in para: - alat_ang = para.get('ALATBASIS') * BOHR_A + if 'use_alat_input' in para or 'use_input_alat' in para: + alat_ang = para.get('alatbasis') * BOHR_A # now have Jij radius in alat units jijrad = jijrad_ang / alat_ang diff --git a/aiida_kkr/workflows/kkr_imp.py b/aiida_kkr/workflows/kkr_imp.py index 1433ec04..f5583a56 100644 --- a/aiida_kkr/workflows/kkr_imp.py +++ b/aiida_kkr/workflows/kkr_imp.py @@ -13,14 +13,14 @@ from aiida_kkr.tools import test_and_get_codenode, neworder_potential_wf, update_params_wf from aiida_kkr.workflows.gf_writeout import kkr_flex_wc from aiida_kkr.workflows.voro_start import kkr_startpot_wc -from aiida_kkr.workflows.kkr_imp_sub import kkr_imp_sub_wc, clean_sfd +from aiida_kkr.workflows.kkr_imp_sub import kkr_imp_sub_wc import numpy as np from aiida_kkr.tools.save_output_nodes import create_out_dict_node __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.9.0' +__version__ = '0.9.2' __contributors__ = (u'Fabian Bertoldo', u'Philipp Rüßmann') #TODO: generalize workflow to multiple impurities #TODO: add additional checks for the input @@ -99,14 +99,15 @@ def define(cls, spec): namespace='scf', include=( # 'kkrimp', - # 'options', + 'options', # 'wf_parameters', - 'params_overwrite' + 'params_overwrite', + 'initial_noco_angles' ) ) # define the inputs of the workflow - spec.input('kkr', valid_type=Code, required=True, help='KKRhost code used to run GF writeout step.') + spec.input('kkr', valid_type=Code, required=False, help='KKRhost code used to run GF writeout step.') spec.input( 'voronoi', valid_type=Code, @@ -179,6 +180,7 @@ def define(cls, spec): required=False, help='Set starting potential (e.g. from preconverged calculation' ) + spec.expose_inputs(kkr_flex_wc, namespace='gf_writeout', include=('params_kkr_overwrite', 'options', 'kkr')) # structure of the workflow spec.outline( @@ -337,7 +339,6 @@ def start(self): self.ctx.hfield = wf_dict.get('hfield', self._wf_default['hfield']) self.ctx.init_pos = wf_dict.get('init_pos', self._wf_default['init_pos']) self.ctx.accuracy_params = wf_dict.get('accuracy_params', self._wf_default['accuracy_params']) - self.ctx.do_final_cleanup = wf_dict.get('do_final_cleanup', self._wf_default['do_final_cleanup']) # set up new parameter dict to pass to kkrimp subworkflow later self.ctx.kkrimp_params_dict = Dict({ 'nsteps': self.ctx.nsteps, @@ -353,15 +354,11 @@ def start(self): 'hfield': self.ctx.hfield, 'init_pos': self.ctx.init_pos, 'accuracy_params': self.ctx.accuracy_params, - 'do_final_cleanup': self.ctx.do_final_cleanup }) # retrieve option for kkrlfex files self.ctx.retrieve_kkrflex = wf_dict.get('retrieve_kkrflex', self._wf_default['retrieve_kkrflex']) - # list of things that are cleaned if everything ran through - self.ctx.sfd_final_cleanup = [] - # report the chosen parameters to the user self.report( 'INFO: use the following parameter:\n' @@ -491,6 +488,12 @@ def run_gf_writeout(self): if 'params_kkr_overwrite' in self.inputs: builder.params_kkr_overwrite = self.inputs.params_kkr_overwrite + if 'gf_writeout' in self.inputs: + if 'options' in self.inputs.gf_writeout: + builder.options = self.inputs.gf_writeout.options + if 'params_kkr_overwrite' in self.inputs.gf_writeout: + builder.params_kkr_overwrite = self.inputs.gf_writeout.params_kkr_overwrite + # maybe set kkrflex_retrieve wf_params_gf = {} if not self.ctx.retrieve_kkrflex: @@ -716,11 +719,10 @@ def construct_startpot(self): settings_label = f'startpot_KKRimp for imp_info node {imp_info.pk}' settings_description = f'starting potential for impurity info: {imp_info}' + settings = Dict({ - 'pot1': potname_converged, 'out_pot': potname_imp, 'neworder': neworder_pot1, - 'pot2': potname_impvorostart, 'replace_newpos': replacelist_pot2, 'label': settings_label, 'description': settings_description @@ -732,8 +734,6 @@ def construct_startpot(self): # add starting potential for kkrimp calculation to context self.ctx.startpot_kkrimp = startpot_kkrimp - # add to list for final cleanup - self.ctx.sfd_final_cleanup.append(startpot_kkrimp) self.report( 'INFO: created startpotential (pid: {}) for the impurity calculation ' @@ -782,8 +782,13 @@ def run_kkrimp_scf(self): builder.remote_data = gf_remote if 'remote_data_gf_Efshift' in self.inputs: builder.remote_data_Efshift = self.inputs.remote_data_gf_Efshift - if 'params_overwrite' in self.inputs.scf: - builder.params_overwrite = self.inputs.scf.params_overwrite + if 'scf' in self.inputs: + if 'params_overwrite' in self.inputs.scf: + builder.params_overwrite = self.inputs.scf.params_overwrite + if 'options' in self.inputs.scf: + builder.options = self.inputs.scf.options + if 'initial_noco_angles' in self.inputs.scf: + builder.initial_noco_angles = self.inputs.scf.initial_noco_angles builder.wf_parameters = kkrimp_params future = self.submit(builder) @@ -844,10 +849,6 @@ def return_results(self): self.out('converged_potential', self.ctx.kkrimp_scf_sub.outputs.host_imp_pot) self.out('remote_data_gf', self.ctx.gf_remote) - # cleanup things that are not needed anymore - if self.ctx.do_final_cleanup: - self.final_cleanup() - # print final message before exiting self.report('INFO: created 3 output nodes for the KKR impurity workflow.') self.report( @@ -860,23 +861,6 @@ def return_results(self): self.report(self.exit_codes.ERROR_KKRIMP_SUB_WORKFLOW_FAILURE) # pylint: disable=no-member return self.exit_codes.ERROR_KKRIMP_SUB_WORKFLOW_FAILURE # pylint: disable=no-member - def final_cleanup(self): - """ - Remove unneeded files to save space - """ - for sfd in self.ctx.sfd_final_cleanup: - clean_sfd(sfd) - if self.ctx.create_startpot: - kkr_startpot = self.ctx.last_voro_calc - vorocalc = kkr_startpot.outputs.last_voronoi_remote.get_incoming(link_label_filter=u'remote_folder' - ).first().node - ret = vorocalc.outputs.retrieved - for fname in ret.list_object_names(): - if fname not in [VoronoiCalculation._OUTPUT_FILE_NAME, VoronoiCalculation._OUT_POTENTIAL_voronoi]: - # delete all except vor default output file - with ret.open(fname) as f: - ret.delete_object(fname, force=True) - def error_handler(self): """Capture errors raised in validate_input""" if self.ctx.exit_code is not None: diff --git a/aiida_kkr/workflows/kkr_imp_dos.py b/aiida_kkr/workflows/kkr_imp_dos.py index e55499c2..0e72914d 100644 --- a/aiida_kkr/workflows/kkr_imp_dos.py +++ b/aiida_kkr/workflows/kkr_imp_dos.py @@ -5,7 +5,7 @@ some helper methods to do so with AiiDA """ -from aiida.orm import Code, load_node, CalcJobNode, Float, Int, Str, Dict, RemoteData, SinglefileData, XyData +from aiida.orm import Code, load_node, CalcJobNode, Float, Int, Str, Dict, RemoteData, SinglefileData, XyData, Bool from aiida.plugins import DataFactory from aiida.engine import if_, ToContext, WorkChain, calcfunction from aiida.common import LinkType @@ -21,9 +21,12 @@ __copyright__ = (u'Copyright (c), 2019, Forschungszentrum Jülich GmbH, ' 'IAS-1/PGI-1, Germany. All rights reserved.') __license__ = 'MIT license, see LICENSE.txt file' -__version__ = '0.6.10' +__version__ = '0.7.0' __contributors__ = (u'Fabian Bertoldo', u'Philipp Rüßmann') +# activate verbose output, for debugging only +_VERBOSE_ = True + #TODO: improve workflow output node structure #TODO: generalise search for imp_info and conv_host from startpot @@ -60,9 +63,8 @@ class kkr_imp_dos_wc(WorkChain): } # execute KKR with mpi or without _wf_default = { - 'clean_impcalc_retrieved': True, # remove output of KKRimp calculation after successful parsing of DOS files 'jij_run': False, # calculate Jij's energy resolved - 'lmdos': False, # caculate l,m or only l-resolved DOS + 'lmdos': False, # calculate also (l,m) or only l-resolved DOS 'retrieve_kkrflex': True, # retrieve kkrflex files to repository or leave on remote computer only } # add defaults of dos_params since they are passed onto that workflow @@ -147,12 +149,18 @@ def define(cls, spec): help='Settings for LDA+U run (see KkrimpCalculation for details).' ) + spec.expose_inputs(kkr_imp_sub_wc, namespace='BdG', include=('params_overwrite')) + spec.expose_inputs(kkr_imp_sub_wc, include=('initial_noco_angles')) + spec.expose_inputs(kkr_flex_wc, namespace='gf_writeout', include=('params_kkr_overwrite', 'options')) + # specify the outputs spec.output('workflow_info', valid_type=Dict) spec.output('last_calc_output_parameters', valid_type=Dict) spec.output('last_calc_info', valid_type=Dict) spec.output('dos_data', valid_type=XyData) spec.output('dos_data_interpol', valid_type=XyData) + spec.output('dos_data_lm', valid_type=XyData, required=False) + spec.output('dos_data_interpol_lm', valid_type=XyData, required=False) spec.output('gf_dos_remote', valid_type=XyData, required=False, help='RemoteData node of the computed host GF.') # Here the structure of the workflow is defined @@ -201,6 +209,8 @@ def start(self): """ self.report(f'INFO: started KKR impurity DOS workflow version {self._workflowversion}') + if _VERBOSE_: + self.report(f'inputs: {self.inputs}') # input both wf and options parameters if 'wf_parameters' in self.inputs: @@ -243,10 +253,6 @@ def start(self): if k not in self.ctx.dos_params_dict.keys(): self.ctx.dos_params_dict[k] = v - self.ctx.cleanup_impcalc_output = wf_dict.get( - 'clean_impcalc_retrieved', self._wf_default['clean_impcalc_retrieved'] - ) - # set workflow parameters for the KKR impurity calculation self.ctx.jij_run = wf_dict.get('jij_run', self._wf_default['jij_run']) @@ -286,72 +292,15 @@ def validate_input(self): if 'imp_pot_sfd' in inputs: # check if input potential has incoming return link - if len(inputs.imp_pot_sfd.get_incoming(link_type=LinkType.RETURN).all()) < 1: - self.report( - 'input potential not from kkrimp workflow: take remote_data folder of host system from input' - ) - if 'impurity_info' in inputs and 'host_remote' in inputs: - self.ctx.imp_info = inputs.impurity_info - self.ctx.conv_host_remote = inputs.host_remote - else: - message = 'WARNING: startpot has no parent and can not find a converged host RemoteData node' - print(message) - self.report(message) - if 'impurity_info' not in inputs: - message = '`impurity_info` optional input node not given but needed in this case.' - print(message) - self.report(message) - if 'host_remote' not in inputs: - message = '`host_remote` optional input node not given but needed in this case.' - print(message) - self.report(message) - inputs_ok = False - self.ctx.errors.append(1) + if len(inputs.imp_pot_sfd.get_incoming(link_type=LinkType.RETURN).all()) == 0: + inputs_ok = self._imp_pot_not_from_wf(inputs_ok) else: - # if return ink is found get input nodes automatically - message = 'INFO: get converged host RemoteData node and impurity_info node from database' - print(message) - self.report(message) - self.ctx.kkr_imp_wf = inputs.imp_pot_sfd.get_incoming(node_class=kkr_imp_sub_wc).first().node - message = f'INFO: found underlying kkr impurity workflow (pk: {self.ctx.kkr_imp_wf.pk})' - print(message) - self.report(message) - self.ctx.imp_info = self.ctx.kkr_imp_wf.inputs.impurity_info - message = f'INFO: found impurity_info node (pk: {self.ctx.imp_info.pk})' - print(message) - self.report(message) - if 'remote_data' in self.ctx.kkr_imp_wf.inputs: - remote_data_gf_writeout = self.ctx.kkr_imp_wf.inputs.remote_data - gf_writeout_calc = remote_data_gf_writeout.get_incoming(node_class=CalcJobNode).first().node - self.ctx.conv_host_remote = gf_writeout_calc.inputs.parent_folder - message = 'INFO: imported converged_host_remote (pk: {}) and impurity_info from database'.format( - self.ctx.conv_host_remote.pk - ) - print(message) - self.report(message) - else: - self.ctx.conv_host_remote = self.ctx.kkr_imp_wf.inputs.gf_remote.inputs.remote_folder.inputs.parent_calc_folder.inputs.remote_folder.outputs.remote_folder - message = 'INFO: imported converged_host_remote (pk: {}) and impurity_info from database'.format( - self.ctx.conv_host_remote.pk - ) - print(message) - self.report(message) + gf_writeout_calc = self._imp_pot_from_wf() if 'gf_dos_remote' in self.inputs: self.ctx.skip_gfstep = True else: - if 'kkr' not in self.inputs: - self.report('[ERROR] `kkr` input node needed if `gf_dos_remote` is not given') - inputs_ok = False - self.ctx.errors.append(3) # raises ERROR_KKR_CODE_MISSING - if gf_writeout_calc is not None: - self.report('Use extraced host remote') - elif 'host_remote' not in self.inputs: - self.report('[ERROR] `host_remote` input node needed if `gf_dos_remote` is not given') - inputs_ok = False - self.ctx.errors.append(4) # raises ERROR_HOST_REMOTE_MISSING - else: - self.ctx.conv_host_remote = self.inputs.host_remote + inputs_ok = self._check_gf_writeout_inputs(inputs_ok, gf_writeout_calc) if 'imp_pot_sfd' in self.inputs and 'kkrimp_remote' in self.inputs: self.report('[ERROR] both `imp_pot_sfd` and `kkrimp_remote` node in inputs') @@ -374,6 +323,86 @@ def validate_input(self): return inputs_ok + def _imp_pot_not_from_wf(self, inputs_ok): + """ + input impurity potential is not from a kkrimp workflow + this means we need also have the `impurity_info` and `host_remote` input nodes + to be able to run the calculation + """ + self.report('input potential not from kkrimp workflow: take remote_data folder of host system from input') + inputs = self.inputs + if 'impurity_info' in inputs and 'host_remote' in inputs: + self.ctx.imp_info = inputs.impurity_info + self.ctx.conv_host_remote = inputs.host_remote + else: + message = 'WARNING: startpot has no parent and can not find a converged host RemoteData node' + self.report(message) + if 'impurity_info' not in inputs: + message = '`impurity_info` optional input node not given but needed in this case.' + self.report(message) + if 'host_remote' not in inputs: + message = '`host_remote` optional input node not given but needed in this case.' + self.report(message) + inputs_ok = False + self.ctx.errors.append(1) + + return inputs_ok + + def _imp_pot_from_wf(self): + """ + we try to extract the input nodes for the GF writeout step from the incoming links to the impurity potential + """ + # if return link is found get input nodes automatically + message = 'INFO: get converged host RemoteData node and impurity_info node from database' + self.report(message) + self.ctx.kkr_imp_wf = self.inputs.imp_pot_sfd.get_incoming(node_class=kkr_imp_sub_wc).first().node + message = f'INFO: found underlying kkr impurity workflow (pk: {self.ctx.kkr_imp_wf.pk})' + self.report(message) + self.ctx.imp_info = self.ctx.kkr_imp_wf.inputs.impurity_info + message = f'INFO: found impurity_info node (pk: {self.ctx.imp_info.pk})' + self.report(message) + + if 'remote_data' in self.ctx.kkr_imp_wf.inputs: + remote_data_gf_writeout = self.ctx.kkr_imp_wf.inputs.remote_data + gf_writeout_calc = remote_data_gf_writeout.get_incoming(node_class=CalcJobNode).first().node + self.ctx.conv_host_remote = gf_writeout_calc.inputs.parent_folder + message = 'INFO: imported converged_host_remote (pk: {}) and impurity_info from database'.format( + self.ctx.conv_host_remote.pk + ) + self.report(message) + else: + # follow links in DB upwards to get the remote_folder of the parent calculation + self.ctx.conv_host_remote = self.ctx.kkr_imp_wf.inputs.gf_remote.inputs.remote_folder.inputs.parent_calc_folder.inputs.remote_folder.outputs.remote_folder + message = 'INFO: imported converged_host_remote (pk: {}) and impurity_info from database'.format( + self.ctx.conv_host_remote.pk + ) + self.report(message) + + if 'host_remote' in self.inputs: + self.ctx.conv_host_remote = self.inputs.host_remote + + return gf_writeout_calc + + def _check_gf_writeout_inputs(self, inputs_ok, gf_writeout_calc): + """ + check if all necessary inputs are there for the GF writeout step + """ + if 'kkr' not in self.inputs: + self.report('[ERROR] `kkr` input node needed if `gf_dos_remote` is not given') + inputs_ok = False + self.ctx.errors.append(3) # raises ERROR_KKR_CODE_MISSING + + if gf_writeout_calc is not None: + self.report('Use extraced host remote') + elif 'host_remote' not in self.inputs: + self.report('[ERROR] `host_remote` input node needed if `gf_dos_remote` is not given') + inputs_ok = False + self.ctx.errors.append(4) # raises ERROR_HOST_REMOTE_MISSING + else: + self.ctx.conv_host_remote = self.inputs.host_remote + + return inputs_ok + def run_gfstep(self): """ Start GF writeout step with DOS energy contour @@ -408,6 +437,11 @@ def run_gfstep(self): builder.impurity_info = imp_info if 'params_kkr_overwrite' in self.inputs: builder.params_kkr_overwrite = self.inputs.params_kkr_overwrite + if 'gf_writeout' in self.inputs: + if 'params_kkr_overwrite' in self.inputs.gf_writeout: + builder.params_kkr_overwrite = self.inputs.params_kkr_overwrite + if 'options' in self.inputs.gf_writeout: + builder.options = self.inputs.options future = self.submit(builder) @@ -454,7 +488,6 @@ def run_imp_dos(self): 'dos_run': True, 'lmdos': self.ctx.lmdos, 'jij_run': self.ctx.jij_run, - 'do_final_cleanup': self.ctx.cleanup_impcalc_output }) kkrimp_params = self.ctx.kkrimp_params_dict label_imp = 'KKRimp DOS (GF: {}, imp_pot: {}, Zimp: {}, ilayer_cent: {})'.format( @@ -488,6 +521,11 @@ def run_imp_dos(self): self.report('Add settings_LDAU input node') builder.settings_LDAU = self.inputs.settings_LDAU + if 'params_overwrite' in self.inputs.BdG: + builder.params_overwrite = self.inputs.BdG.params_overwrite + if 'initial_noco_angles' in self.inputs: + builder.initial_noco_angles = self.inputs.initial_noco_angles + future = self.submit(builder) message = f'INFO: running DOS step for impurity system (pk: {future.pk})' @@ -555,13 +593,9 @@ def return_results(self): if dos_extracted: self.out('dos_data', dosXyDatas['dos_data']) self.out('dos_data_interpol', dosXyDatas['dos_data_interpol']) - # maybe cleanup retrieved folder of DOS calculation - if self.ctx.cleanup_impcalc_output: - message = 'INFO: cleanup after storing of DOS data' - print(message) - self.report(message) - pk_impcalc = self.ctx.kkrimp_dos.outputs.workflow_info['pks_all_calcs'][0] - cleanup_kkrimp_retrieved(pk_impcalc) + if self.ctx.lmdos: + self.out('dos_data_lm', dosXyDatas['dos_data_lm']) + self.out('dos_data_interpol_lm', dosXyDatas['dos_data_interpol_lm']) message = f'INFO: workflow_info node: {outputnode_t.uuid}' print(message) @@ -643,7 +677,7 @@ def extract_dos_data_from_folder(self, folder, last_calc): filelist = folder.list_object_names() # check if out_ldos* files are there and parse dos files - if 'out_ldos.interpol.atom=01_spin1.dat' in filelist: + if 'out_ldos.interpol.atom=01_spin1.dat' in filelist or 'out_ldos.interpol.atom=001_spin1.dat' in filelist: # extract EF and number of atoms from kkrflex_writeout calculation kkrflex_writeout = load_node(self.ctx.pk_flexcalc) parent_calc_kkr_converged = kkrflex_writeout.inputs.parent_folder.get_incoming(node_class=CalcJobNode @@ -652,7 +686,11 @@ def extract_dos_data_from_folder(self, folder, last_calc): last_calc_output_params = last_calc.outputs.output_parameters natom = last_calc_output_params.get_dict().get('number_of_atoms_in_unit_cell') # parse dosfiles using nspin, EF and Natom inputs - dosXyDatas = parse_impdosfiles(folder, Int(natom), Int(self.ctx.nspin), Float(ef)) + dosXyDatas = parse_impdosfiles(folder, Int(natom), Int(self.ctx.nspin), Float(ef), Bool(False)) + if self.ctx.lmdos: + dosXyDatas2 = parse_impdosfiles(folder, Int(natom), Int(self.ctx.nspin), Float(ef), Bool(True)) + dosXyDatas['dos_data_lm'] = dosXyDatas2['dos_data'] + dosXyDatas['dos_data_interpol_lm'] = dosXyDatas2['dos_data_interpol'] dos_extracted = True else: dos_extracted = False @@ -662,7 +700,7 @@ def extract_dos_data_from_folder(self, folder, last_calc): @calcfunction -def parse_impdosfiles(folder, natom, nspin, ef): +def parse_impdosfiles(folder, natom, nspin, ef, use_lmdos): """ Read `out_ldos*` files and create XyData node with l-resolved DOS (+node for interpolated DOS if files are found) @@ -684,12 +722,25 @@ def parse_impdosfiles(folder, natom, nspin, ef): dos, dos_int = [], [] for iatom in range(1, natom.value + 1): for ispin in range(1, nspin.value + 1): - with folder.open('out_ldos.atom=%0.2i_spin%i.dat' % (iatom, ispin)) as dosfile: - tmp = loadtxt(dosfile) - dos.append(tmp) - with folder.open('out_ldos.interpol.atom=%0.2i_spin%i.dat' % (iatom, ispin)) as dosfile: - tmp = loadtxt(dosfile) - dos_int.append(tmp) + name0 = 'out_ldos' + if use_lmdos.value: + name0 = 'out_lmdos' + try: + # old file names with 2 digits for atom index + with folder.open(name0 + '.atom=%0.2i_spin%i.dat' % (iatom, ispin)) as dosfile: + tmp = loadtxt(dosfile) + dos.append(tmp) + with folder.open(name0 + '.interpol.atom=%0.2i_spin%i.dat' % (iatom, ispin)) as dosfile: + tmp = loadtxt(dosfile) + dos_int.append(tmp) + except: + # new file names with 3 digits for atom numbers + with folder.open(name0 + '.atom=%0.3i_spin%i.dat' % (iatom, ispin)) as dosfile: + tmp = loadtxt(dosfile) + dos.append(tmp) + with folder.open(name0 + '.interpol.atom=%0.3i_spin%i.dat' % (iatom, ispin)) as dosfile: + tmp = loadtxt(dosfile) + dos_int.append(tmp) dos, dos_int = array(dos), array(dos_int) # convert to eV units @@ -706,6 +757,11 @@ def parse_impdosfiles(folder, natom, nspin, ef): dosnode.set_x(dos[:, :, 0], 'E-EF', 'eV') name = ['tot', 's', 'p', 'd', 'f', 'g'] + if use_lmdos.value: + name = [ + 'tot', 's', 'p1', 'p2', 'p3', 'd1', 'd2', 'd3', 'd4', 'd5', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'g1', + 'g2', 'g3', 'g4', 'g5', 'g6', 'g7', 'g8', 'g9' + ] name = name[:len(dos[0, 0, 1:]) - 1] + ['ns'] ylists = [[], [], []] @@ -730,22 +786,3 @@ def parse_impdosfiles(folder, natom, nspin, ef): output = {'dos_data': dosnode, 'dos_data_interpol': dosnode2} return output - - -def cleanup_kkrimp_retrieved(pk_impcalc): - """ - remove output_all.tar.gz from retrieved of impurity calculation identified by pk_impcalc - """ - from aiida.orm import load_node - from aiida_kkr.calculations import KkrimpCalculation - - # extract retrieved folder - doscalc = load_node(pk_impcalc) - ret = doscalc.outputs.retrieved - - # name of tarfile - tfname = KkrimpCalculation._FILENAME_TAR - - # remove tarfile from retreived dir - if tfname in ret.list_object_names(): - ret.delete_object(tfname, force=True) diff --git a/aiida_kkr/workflows/kkr_imp_sub.py b/aiida_kkr/workflows/kkr_imp_sub.py index ba80eb8a..4f7f8a3b 100644 --- a/aiida_kkr/workflows/kkr_imp_sub.py +++ b/aiida_kkr/workflows/kkr_imp_sub.py @@ -17,7 +17,7 @@ __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.9.4' +__version__ = '0.10.0' __contributors__ = (u'Fabian Bertoldo', u'Philipp Ruessmann') #TODO: work on return results function @@ -82,8 +82,6 @@ class kkr_imp_sub_wc(WorkChain): False, # specify if DOS should be calculated (!KKRFLEXFILES with energy contour necessary as GF_remote_data!) 'lmdos': True, # specify if DOS calculation should calculate l-resolved or l and m resolved output 'jij_run': False, # specify if Jijs should be calculated (!changes behavior of the code!!!) - 'do_final_cleanup': - True, # decide whether or not to clean up intermediate files (THIS BREAKS CACHABILITY!) # TODO: remove for aiida-core>2 # # Some parameter for direct solver (if None, use the same as in host code, otherwise overwrite) 'accuracy_params': { 'RADIUS_LOGPANELS': None, # where to set change of logarithmic to linear radial mesh @@ -132,6 +130,8 @@ def define(cls, spec): help='Dict of parameters that are given to the KKRimpCalculation. Overwrites automatically set values!' ) + spec.expose_inputs(KkrimpCalculation, include=('initial_noco_angles')) + # Here the structure of the workflow is defined spec.outline( cls.start, @@ -229,8 +229,6 @@ def start(self): self.ctx.last_remote = None # link to previous host impurity potential self.ctx.last_pot = None - # intermediate single file data objects that contain potential files which can be clean up at the end - self.ctx.sfd_pot_to_clean = [] # convergence info about rms etc. (used to determine convergence behavior) self.ctx.last_rms_all = [] self.ctx.rms_all_steps = [] @@ -253,9 +251,6 @@ def start(self): message = 'INFO: using default wf parameter' self.report(message) - # cleanup intermediate calculations (WARNING: THIS PREVENTS USING CACHING!!!) - self.ctx.do_final_cleanup = wf_dict.get('do_final_cleanup', self._wf_default['do_final_cleanup']) - # set option parameters from input, or defaults self.ctx.withmpi = options_dict.get('withmpi', self._options_default['withmpi']) self.ctx.resources = options_dict.get('resources', self._options_default['resources']) @@ -765,7 +760,6 @@ def run_kkrimp(self): emin = GF_out_params.get_dict().get('energy_contour_group').get('emin') # then use this value to get rid of all core states that are lower than emin (return the same input potential if no states have been removed imp_pot = kick_out_corestates_wf(imp_pot, Float(emin)) - self.ctx.sfd_pot_to_clean.append(imp_pot) if 'impurity_info' in self.inputs: message = 'INFO: using impurity_info node as input for kkrimp calculation' self.report(message) @@ -858,9 +852,10 @@ def run_kkrimp(self): # add LDA+U input node if it was set in parent calculation of last kkrimp_remote or from input port if self.ctx.settings_LDAU is not None: inputs['settings_LDAU'] = self.ctx.settings_LDAU - # set cleanup option - if int(aiida_core_version.split('.')[0]) < 2 and self.ctx.do_final_cleanup: - inputs['cleanup_outfiles'] = Bool(self.ctx.do_final_cleanup) + + # set nonco angles if given + if 'initial_noco_angles' in self.inputs: + inputs['initial_noco_angles'] = self.inputs.initial_noco_angles # run the KKR calculation message = 'INFO: doing calculation' @@ -895,7 +890,6 @@ def inspect_kkrimp(self): retrieved_folder = self.ctx.kkr.outputs.retrieved imp_pot_sfd = extract_imp_pot_sfd(retrieved_folder) self.ctx.last_pot = imp_pot_sfd - self.ctx.sfd_pot_to_clean.append(self.ctx.last_pot) print('use potfile sfd:', self.ctx.last_pot) except: message = 'ERROR: no output potential found' @@ -1199,19 +1193,6 @@ def return_results(self): """ self.report(message) - # cleanup of unnecessary files after convergence - # WARNING: THIS DESTROYS CACHABILITY OF THE WORKFLOW!!! - if self.ctx.do_final_cleanup: - if self.ctx.successful: - self.report('INFO: clean output of calcs') - remove_out_pot_impcalcs(self.ctx.successful, all_pks) - self.report('INFO: clean up raw_input folders') - clean_raw_input(self.ctx.successful, all_pks) - - # clean intermediate single file data which are not needed after successful run or after DOS run - if self.ctx.successful or self.ctx.dos_run: - self.final_cleanup() - self.report('INFO: done with kkr_scf workflow!\n') def error_handler(self): @@ -1219,17 +1200,6 @@ def error_handler(self): if self.ctx.exit_code is not None: return self.ctx.exit_code - def final_cleanup(self): - uuid_last_calc = self.ctx.last_pot.uuid - if not self.ctx.dos_run: - sfds_to_clean = [i for i in self.ctx.sfd_pot_to_clean if i.uuid != uuid_last_calc] - else: - # in case of DOS run we can also clean the last output sfd file since this is never used - sfds_to_clean = self.ctx.sfd_pot_to_clean - # now clean all sfd files that are not needed anymore - for sfd_to_clean in sfds_to_clean: - clean_sfd(sfd_to_clean) - def _overwrite_parameters_from_input(self, new_params): """Overwrite input parameters for KKRimpCalculation if found in input""" params_overwrite = self.inputs.params_overwrite.get_dict() @@ -1242,120 +1212,6 @@ def _overwrite_parameters_from_input(self, new_params): new_params[key] = val -def remove_out_pot_impcalcs(successful, pks_all_calcs, dry_run=False): - """ - Remove out_potential file from all but the last KKRimp calculation if workflow was successful - - Usage:: - - imp_wf = load_node(266885) # maybe start with outer workflow - pk_imp_scf = imp_wf.outputs.workflow_info['used_subworkflows'].get('kkr_imp_sub') - imp_scf_wf = load_node(pk_imp_scf) # this is now the imp scf sub workflow - successful = imp_scf_wf.outputs.workflow_info['successful'] - pks_all_calcs = imp_scf_wf.outputs.workflow_info['pks_all_calcs'] - """ - import tarfile, os - from aiida.orm import load_node - from aiida.common.folders import SandboxFolder - from aiida_kkr.calculations import KkrimpCalculation - - if dry_run: - print('test', successful, len(pks_all_calcs)) - - # name of tarfile - tfname = KkrimpCalculation._FILENAME_TAR - - # cleanup only if calculation was successful - if successful and len(pks_all_calcs) > 1: - # remove out_potential for calculations - # note that also last calc can be cleaned since output potential is stored in single file data - pks_for_cleanup = pks_all_calcs[:] - - # loop over all calculations - for pk in pks_for_cleanup: - if dry_run: - print('pk_for_cleanup:', pk) - # get getreived folder of calc - calc = load_node(pk) - ret = calc.outputs.retrieved - - # open tarfile if present - if tfname in ret.list_object_names(): - delete_and_retar = False - with ret.open(tfname) as tf: - tf_abspath = tf.name - - # create Sandbox folder which is used to temporarily extract output_all.tar.gz - tmpfolder = SandboxFolder() - tmpfolder_path = tmpfolder.abspath - with tarfile.open(tf_abspath) as tf: - tar_filenames = [ifile.name for ifile in tf.getmembers()] - # check if out_potential is in tarfile - if KkrimpCalculation._OUT_POTENTIAL in tar_filenames: - tf.extractall(tmpfolder_path) - delete_and_retar = True - - if delete_and_retar and not dry_run: - # delete out_potential - os.remove(os.path.join(tmpfolder_path, KkrimpCalculation._OUT_POTENTIAL)) - with tarfile.open(tf_abspath, 'w:gz') as tf: - # remove out_potential from list of files - tar_filenames = [i for i in tar_filenames if i != KkrimpCalculation._OUT_POTENTIAL] - for f in tar_filenames: - # create new tarfile without out_potential file - fabs = os.path.join(tmpfolder_path, f) - tf.add(fabs, arcname=os.path.basename(fabs)) - elif dry_run: - print('dry run:') - print('delete and retar?', delete_and_retar) - print('tmpfolder_path', tmpfolder_path) - - # clean up temporary Sandbox folder - if not dry_run: - tmpfolder.erase() - - -def clean_raw_input(successful, pks_calcs, dry_run=False): - """ - Clean raw_input directories that contain copies of shapefun and potential files - This however breaks provenance (strictly speaking) and therefore should only be done - for the calculations of a successfully finished workflow (see email on mailing list from 25.11.2019). - """ - from aiida.orm import load_node - from aiida_kkr.calculations import KkrimpCalculation - if successful: - for pk in pks_calcs: - node = load_node(pk) - # clean only nodes that are KkrimpCalculations - if node.process_class == KkrimpCalculation: - raw_input_folder = node._raw_input_folder - # clean potential and shapefun files - for filename in [KkrimpCalculation._POTENTIAL, KkrimpCalculation._SHAPEFUN]: - if filename in raw_input_folder.get_content_list(): - if dry_run: - print(f'clean {filename}') - else: - raw_input_folder.remove_path(filename) - elif dry_run: - print('no raw_inputs to clean') - - -def clean_sfd(sfd_to_clean, nkeep=30): - """ - Clean up potential file (keep only header) to save space in the repository - WARNING: this breaks cachability! - """ - with sfd_to_clean.open(sfd_to_clean.filename) as f: - txt = f.readlines() - # remove all lines after nkeep lines - txt2 = txt[:nkeep] - # add note to end of file - txt2 += [u'WARNING: REST OF FILE WAS CLEANED SO SAVE SPACE!!!\n'] - # overwrite file - with sfd_to_clean.open(sfd_to_clean.filename, 'w') as fnew: - fnew.writelines(txt2) - - @calcfunction def extract_imp_pot_sfd(retrieved_folder): """ diff --git a/aiida_kkr/workflows/voro_start.py b/aiida_kkr/workflows/voro_start.py index 262e1fa6..6484f93b 100644 --- a/aiida_kkr/workflows/voro_start.py +++ b/aiida_kkr/workflows/voro_start.py @@ -24,7 +24,7 @@ 'IAS-1/PGI-1, Germany. All rights reserved.' ) __license__ = 'MIT license, see LICENSE.txt file' -__version__ = '0.13.2' +__version__ = '0.13.3' __contributors__ = u'Philipp Rüßmann' eV2Ry = 1.0 / get_Ry2eV() @@ -495,8 +495,9 @@ def run_voronoi(self): self.ctx.r_cls = rcls_input updated_params = True update_list.append('RCLUSTZ') - elif self.ctx.r_cls > rcls_input: - # change rcls with iterations + elif self.ctx.nclsmin > 0 and self.ctx.r_cls > rcls_input: + # change rcls with iterations, do this only if nclsmin is >0 + # (0 or negative numbers trigger use of RCLUSTZ) updated_params = True update_list.append('RCLUSTZ') else: diff --git a/pyproject.toml b/pyproject.toml index 111924a0..9d1d27ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "aiida-kkr" -version = "2.0.1" +version = "2.1.0" description = "AiiDA plugin for the JuKKR codes" classifiers = [ "License :: OSI Approved :: MIT License", @@ -59,10 +59,10 @@ Documentation = "https://aiida-kkr.readthedocs.io" [project.optional-dependencies] pre-commit = [ - "pre-commit==3.2.2", - "yapf==0.32.0", + "pre-commit==3.5.0", + "yapf==0.33.0", "pylint==1.9.4; python_version<'3.0'", - "pylint==2.17.2; python_version>='3.0'", + "pylint==3.0.2; python_version>='3.0'", ] testing = [ "pgtest >= 1.3.0", @@ -117,6 +117,7 @@ include-package-data = false "kkr.imp" = "aiida_kkr.workflows.kkr_imp:kkr_imp_wc" "kkr.imp_sub" = "aiida_kkr.workflows.kkr_imp_sub:kkr_imp_sub_wc" "kkr.imp_dos" = "aiida_kkr.workflows.kkr_imp_dos:kkr_imp_dos_wc" +"kkr.imp_BdG" = "aiida_kkr.workflows.imp_BdG:kkrimp_BdG_wc" "kkr.decimation" = "aiida_kkr.workflows._decimation:kkr_decimation_wc" "kkr.jij" = "aiida_kkr.workflows.jijs:kkr_jij_wc" diff --git a/tests/data_dir/KkrCalculation-nodes-5e8261f1e3db59cfe2602957481376e3.tar.gz b/tests/data_dir/KkrCalculation-nodes-5e8261f1e3db59cfe2602957481376e3.tar.gz deleted file mode 100644 index 992df15f..00000000 Binary files a/tests/data_dir/KkrCalculation-nodes-5e8261f1e3db59cfe2602957481376e3.tar.gz and /dev/null differ diff --git a/tests/data_dir/KkrCalculation-nodes-9260ecff6d48ebee7adb680b907edcbc.tar.gz b/tests/data_dir/KkrCalculation-nodes-9260ecff6d48ebee7adb680b907edcbc.tar.gz new file mode 100644 index 00000000..79999073 Binary files /dev/null and b/tests/data_dir/KkrCalculation-nodes-9260ecff6d48ebee7adb680b907edcbc.tar.gz differ diff --git a/tests/data_dir/KkrCalculation-nodes-f544fdba7f3eb6ebd85322908203e977.tar.gz b/tests/data_dir/KkrCalculation-nodes-f544fdba7f3eb6ebd85322908203e977.tar.gz new file mode 100644 index 00000000..8fa70406 Binary files /dev/null and b/tests/data_dir/KkrCalculation-nodes-f544fdba7f3eb6ebd85322908203e977.tar.gz differ diff --git a/tests/data_dir/KkrCalculation-nodes-ff38d3ba605e86bfebabea80a103fab0.tar.gz b/tests/data_dir/KkrCalculation-nodes-ff38d3ba605e86bfebabea80a103fab0.tar.gz deleted file mode 100644 index d28410f5..00000000 Binary files a/tests/data_dir/KkrCalculation-nodes-ff38d3ba605e86bfebabea80a103fab0.tar.gz and /dev/null differ diff --git a/tests/data_dir/VoronoiCalculation-nodes-5285e1ed9ee855cfbfbf9fe11b2fca95.tar.gz b/tests/data_dir/VoronoiCalculation-nodes-5285e1ed9ee855cfbfbf9fe11b2fca95.tar.gz deleted file mode 100644 index 0b7d7410..00000000 Binary files a/tests/data_dir/VoronoiCalculation-nodes-5285e1ed9ee855cfbfbf9fe11b2fca95.tar.gz and /dev/null differ diff --git a/tests/data_dir/VoronoiCalculation-nodes-acab3366e24be2dbeddf6a16802cfaf2.tar.gz b/tests/data_dir/VoronoiCalculation-nodes-acab3366e24be2dbeddf6a16802cfaf2.tar.gz index 8c36a5ab..81df80c0 100644 Binary files a/tests/data_dir/VoronoiCalculation-nodes-acab3366e24be2dbeddf6a16802cfaf2.tar.gz and b/tests/data_dir/VoronoiCalculation-nodes-acab3366e24be2dbeddf6a16802cfaf2.tar.gz differ diff --git a/tests/data_dir/VoronoiCalculation-nodes-f69adf0c2a273e0c8fd8eecb66e3ba87.tar.gz b/tests/data_dir/VoronoiCalculation-nodes-f69adf0c2a273e0c8fd8eecb66e3ba87.tar.gz new file mode 100644 index 00000000..2e29a751 Binary files /dev/null and b/tests/data_dir/VoronoiCalculation-nodes-f69adf0c2a273e0c8fd8eecb66e3ba87.tar.gz differ diff --git a/tests/data_dir/combine_imps_wc-nodes-02242a4e7ea5890576f6c0156563f3cb.tar.gz b/tests/data_dir/combine_imps_wc-nodes-02242a4e7ea5890576f6c0156563f3cb.tar.gz deleted file mode 100644 index eefc4040..00000000 Binary files a/tests/data_dir/combine_imps_wc-nodes-02242a4e7ea5890576f6c0156563f3cb.tar.gz and /dev/null differ diff --git a/tests/data_dir/combine_imps_wc-nodes-7cc721a256b5eefea9370fbd806b0d66.tar.gz b/tests/data_dir/combine_imps_wc-nodes-7cc721a256b5eefea9370fbd806b0d66.tar.gz deleted file mode 100644 index 68eb4ed7..00000000 Binary files a/tests/data_dir/combine_imps_wc-nodes-7cc721a256b5eefea9370fbd806b0d66.tar.gz and /dev/null differ diff --git a/tests/data_dir/combine_imps_wc-nodes-e4259f310125ab74b05412bf4561dae9.tar.gz b/tests/data_dir/combine_imps_wc-nodes-e4259f310125ab74b05412bf4561dae9.tar.gz deleted file mode 100644 index 89b5432b..00000000 Binary files a/tests/data_dir/combine_imps_wc-nodes-e4259f310125ab74b05412bf4561dae9.tar.gz and /dev/null differ diff --git a/tests/data_dir/kkr_bs_wc-nodes-c49d8ff8323500d64a5212654338ca32.tar.gz b/tests/data_dir/kkr_bs_wc-nodes-c49d8ff8323500d64a5212654338ca32.tar.gz index de06ca9b..c478223e 100644 Binary files a/tests/data_dir/kkr_bs_wc-nodes-c49d8ff8323500d64a5212654338ca32.tar.gz and b/tests/data_dir/kkr_bs_wc-nodes-c49d8ff8323500d64a5212654338ca32.tar.gz differ diff --git a/tests/data_dir/kkr_decimation_wc-nodes-6182cfcd3085e4a55bd55181af921ffc.tar.gz b/tests/data_dir/kkr_decimation_wc-nodes-6182cfcd3085e4a55bd55181af921ffc.tar.gz index be1b5a4e..c52bd99b 100644 Binary files a/tests/data_dir/kkr_decimation_wc-nodes-6182cfcd3085e4a55bd55181af921ffc.tar.gz and b/tests/data_dir/kkr_decimation_wc-nodes-6182cfcd3085e4a55bd55181af921ffc.tar.gz differ diff --git a/tests/data_dir/kkr_decimation_wc-nodes-53e415bc45e6632f828e2d8517f43bd5.tar.gz b/tests/data_dir/kkr_decimation_wc-nodes-d34a49ba73f1a75ec1d6bacedfc95edc.tar.gz similarity index 90% rename from tests/data_dir/kkr_decimation_wc-nodes-53e415bc45e6632f828e2d8517f43bd5.tar.gz rename to tests/data_dir/kkr_decimation_wc-nodes-d34a49ba73f1a75ec1d6bacedfc95edc.tar.gz index 2f5bf81a..bd2e8295 100644 Binary files a/tests/data_dir/kkr_decimation_wc-nodes-53e415bc45e6632f828e2d8517f43bd5.tar.gz and b/tests/data_dir/kkr_decimation_wc-nodes-d34a49ba73f1a75ec1d6bacedfc95edc.tar.gz differ diff --git a/tests/data_dir/kkr_decimation_wc-nodes-ffd60ad4072a4de15b757a46505a27f6.tar.gz b/tests/data_dir/kkr_decimation_wc-nodes-ffd60ad4072a4de15b757a46505a27f6.tar.gz index 0af8fa54..2fe5e449 100644 Binary files a/tests/data_dir/kkr_decimation_wc-nodes-ffd60ad4072a4de15b757a46505a27f6.tar.gz and b/tests/data_dir/kkr_decimation_wc-nodes-ffd60ad4072a4de15b757a46505a27f6.tar.gz differ diff --git a/tests/data_dir/kkr_dos_wc-nodes-069b654a4a88756a6c9cfad20109f0e6.tar.gz b/tests/data_dir/kkr_dos_wc-nodes-069b654a4a88756a6c9cfad20109f0e6.tar.gz index f039951f..f04dba39 100644 Binary files a/tests/data_dir/kkr_dos_wc-nodes-069b654a4a88756a6c9cfad20109f0e6.tar.gz and b/tests/data_dir/kkr_dos_wc-nodes-069b654a4a88756a6c9cfad20109f0e6.tar.gz differ diff --git a/tests/data_dir/kkr_eos_wc-nodes-9ae61af91481fb5c1d0f21474c08450f.tar.gz b/tests/data_dir/kkr_eos_wc-nodes-9ae61af91481fb5c1d0f21474c08450f.tar.gz new file mode 100644 index 00000000..83dd80f2 Binary files /dev/null and b/tests/data_dir/kkr_eos_wc-nodes-9ae61af91481fb5c1d0f21474c08450f.tar.gz differ diff --git a/tests/data_dir/kkr_eos_wc-nodes-f65e9f2aa917107694f3fd7938d0e9bc.tar.gz b/tests/data_dir/kkr_eos_wc-nodes-f65e9f2aa917107694f3fd7938d0e9bc.tar.gz deleted file mode 100644 index da988a66..00000000 Binary files a/tests/data_dir/kkr_eos_wc-nodes-f65e9f2aa917107694f3fd7938d0e9bc.tar.gz and /dev/null differ diff --git a/tests/data_dir/kkr_flex_wc-nodes-94b44d25a7af0e584343419207f3a7e1.tar.gz b/tests/data_dir/kkr_flex_wc-nodes-94b44d25a7af0e584343419207f3a7e1.tar.gz index b407f173..f1e0bbc9 100644 Binary files a/tests/data_dir/kkr_flex_wc-nodes-94b44d25a7af0e584343419207f3a7e1.tar.gz and b/tests/data_dir/kkr_flex_wc-nodes-94b44d25a7af0e584343419207f3a7e1.tar.gz differ diff --git a/tests/data_dir/kkr_imp_dos_wc-nodes-cca7ba6db0aaec50a18461115a47d096.tar.gz b/tests/data_dir/kkr_imp_dos_wc-nodes-05066f1f4c9bc35ea08a631b005688fa.tar.gz similarity index 78% rename from tests/data_dir/kkr_imp_dos_wc-nodes-cca7ba6db0aaec50a18461115a47d096.tar.gz rename to tests/data_dir/kkr_imp_dos_wc-nodes-05066f1f4c9bc35ea08a631b005688fa.tar.gz index a5413efc..1a966af9 100644 Binary files a/tests/data_dir/kkr_imp_dos_wc-nodes-cca7ba6db0aaec50a18461115a47d096.tar.gz and b/tests/data_dir/kkr_imp_dos_wc-nodes-05066f1f4c9bc35ea08a631b005688fa.tar.gz differ diff --git a/tests/data_dir/kkr_imp_sub_wc-nodes-6227d9003b63b76d1fd41bd5322771b5.tar.gz b/tests/data_dir/kkr_imp_sub_wc-nodes-6227d9003b63b76d1fd41bd5322771b5.tar.gz index e8b6fb6c..6553f649 100644 Binary files a/tests/data_dir/kkr_imp_sub_wc-nodes-6227d9003b63b76d1fd41bd5322771b5.tar.gz and b/tests/data_dir/kkr_imp_sub_wc-nodes-6227d9003b63b76d1fd41bd5322771b5.tar.gz differ diff --git a/tests/data_dir/kkr_imp_wc-nodes-4e7fa222d8fbe143b13363013103a8e3.tar.gz b/tests/data_dir/kkr_imp_wc-nodes-4e7fa222d8fbe143b13363013103a8e3.tar.gz deleted file mode 100644 index b86e926e..00000000 Binary files a/tests/data_dir/kkr_imp_wc-nodes-4e7fa222d8fbe143b13363013103a8e3.tar.gz and /dev/null differ diff --git a/tests/data_dir/kkr_imp_wc-nodes-767eddfa3c34e0942d764a7e986aed8d.tar.gz b/tests/data_dir/kkr_imp_wc-nodes-767eddfa3c34e0942d764a7e986aed8d.tar.gz index d18894d8..7ccc993a 100644 Binary files a/tests/data_dir/kkr_imp_wc-nodes-767eddfa3c34e0942d764a7e986aed8d.tar.gz and b/tests/data_dir/kkr_imp_wc-nodes-767eddfa3c34e0942d764a7e986aed8d.tar.gz differ diff --git a/tests/data_dir/kkr_imp_wc-nodes-797c0232bef253293c7b437933e0917c.tar.gz b/tests/data_dir/kkr_imp_wc-nodes-797c0232bef253293c7b437933e0917c.tar.gz deleted file mode 100644 index 935eae36..00000000 Binary files a/tests/data_dir/kkr_imp_wc-nodes-797c0232bef253293c7b437933e0917c.tar.gz and /dev/null differ diff --git a/tests/data_dir/kkr_jij_wc-nodes-7316580c3630215166fd2681063d792e.tar.gz b/tests/data_dir/kkr_jij_wc-nodes-7316580c3630215166fd2681063d792e.tar.gz index 1e47bcd8..04f87fff 100644 Binary files a/tests/data_dir/kkr_jij_wc-nodes-7316580c3630215166fd2681063d792e.tar.gz and b/tests/data_dir/kkr_jij_wc-nodes-7316580c3630215166fd2681063d792e.tar.gz differ diff --git a/tests/data_dir/kkr_jij_wc-nodes-b8b421acfdbc243b6c9a1728b88e7612.tar.gz b/tests/data_dir/kkr_jij_wc-nodes-b8b421acfdbc243b6c9a1728b88e7612.tar.gz index fc653c8f..fc20dfed 100644 Binary files a/tests/data_dir/kkr_jij_wc-nodes-b8b421acfdbc243b6c9a1728b88e7612.tar.gz and b/tests/data_dir/kkr_jij_wc-nodes-b8b421acfdbc243b6c9a1728b88e7612.tar.gz differ diff --git a/tests/data_dir/kkr_scf_wc-nodes-31a2e00e231215133475de79d47f7c0b.tar.gz b/tests/data_dir/kkr_scf_wc-nodes-31a2e00e231215133475de79d47f7c0b.tar.gz deleted file mode 100644 index df081cc8..00000000 Binary files a/tests/data_dir/kkr_scf_wc-nodes-31a2e00e231215133475de79d47f7c0b.tar.gz and /dev/null differ diff --git a/tests/data_dir/kkr_scf_wc-nodes-d05fa9e643de2b1ab7b6a47d296d38b3.tar.gz b/tests/data_dir/kkr_scf_wc-nodes-d05fa9e643de2b1ab7b6a47d296d38b3.tar.gz new file mode 100644 index 00000000..3db9c457 Binary files /dev/null and b/tests/data_dir/kkr_scf_wc-nodes-d05fa9e643de2b1ab7b6a47d296d38b3.tar.gz differ diff --git a/tests/data_dir/kkr_startpot_wc-nodes-1ed37a7933d82b3d30ac32b22f552cf7.tar.gz b/tests/data_dir/kkr_startpot_wc-nodes-1ed37a7933d82b3d30ac32b22f552cf7.tar.gz new file mode 100644 index 00000000..74dc73ec Binary files /dev/null and b/tests/data_dir/kkr_startpot_wc-nodes-1ed37a7933d82b3d30ac32b22f552cf7.tar.gz differ diff --git a/tests/data_dir/kkr_startpot_wc-nodes-befe411f798bb7b99df445c996b0569d.tar.gz b/tests/data_dir/kkr_startpot_wc-nodes-befe411f798bb7b99df445c996b0569d.tar.gz deleted file mode 100644 index 722e4cef..00000000 Binary files a/tests/data_dir/kkr_startpot_wc-nodes-befe411f798bb7b99df445c996b0569d.tar.gz and /dev/null differ diff --git a/tests/data_dir/kkr_startpot_wc-nodes-ecc140da728e6cc186a60f0f9c11de63.tar.gz b/tests/data_dir/kkr_startpot_wc-nodes-ecc140da728e6cc186a60f0f9c11de63.tar.gz new file mode 100644 index 00000000..3a0ece56 Binary files /dev/null and b/tests/data_dir/kkr_startpot_wc-nodes-ecc140da728e6cc186a60f0f9c11de63.tar.gz differ diff --git a/tests/data_dir/kkr_startpot_wc-nodes-f4ae50904e5a532195c4b35f6af2c543.tar.gz b/tests/data_dir/kkr_startpot_wc-nodes-f4ae50904e5a532195c4b35f6af2c543.tar.gz deleted file mode 100644 index 3ccac49d..00000000 Binary files a/tests/data_dir/kkr_startpot_wc-nodes-f4ae50904e5a532195c4b35f6af2c543.tar.gz and /dev/null differ diff --git a/tests/run_all.sh b/tests/run_all.sh index 19fdc79b..baa30ccb 100755 --- a/tests/run_all.sh +++ b/tests/run_all.sh @@ -110,7 +110,9 @@ if [[ ! -z "$RUN_ALL" ]]; then # now workflow tests pytest --cov-report=$repfmt --cov=../ --cov-append --ignore=jukkr workflows/ $addopt elif [[ ! -z "$GITHUB_SUITE" ]]; then - pytest --cov-report=$repfmt --cov=../ --cov-report xml:coverage.xml --ignore=workflows --ignore=jukkr --mpl -p no:warnings $addopt + if [[ -z "$SKIP_NOWORK" ]]; then + pytest --cov-report=$repfmt --cov=../ --cov-report xml:coverage.xml --ignore=workflows --ignore=jukkr --mpl -p no:warnings $addopt + fi pytest --cov-report=$repfmt --cov-append --cov=../ -x ./workflows/test_vorostart_wc.py \ ./workflows/test_scf_wc_simple.py \ ./workflows/test_dos_wc.py \ @@ -119,10 +121,10 @@ elif [[ ! -z "$GITHUB_SUITE" ]]; then ./workflows/test_jij_wc.py \ ./workflows/test_eos.py \ ./workflows/test_decimate.py \ - ./workflows/test_kkrimp_sub_wc.py \ - ./workflows/test_kkrimp_dos_wc.py \ - ./workflows/test_kkrimp_full_wc.py \ - ./workflows/test_combine_imps.py \ + # ./workflows/test_kkrimp_sub_wc.py \ + # ./workflows/test_kkrimp_dos_wc.py \ + # ./workflows/test_kkrimp_full_wc.py \ + # ./workflows/test_combine_imps.py \ $addopt else # tests without running actual calculations diff --git a/tests/test_entrypoints.py b/tests/test_entrypoints.py index aa6d7437..2fe24160 100644 --- a/tests/test_entrypoints.py +++ b/tests/test_entrypoints.py @@ -198,3 +198,10 @@ def test_kkrimp_sub_workchain_entry_point(self): wf = WorkflowFactory('kkr.imp_sub') assert wf == kkr_imp_sub_wc + + def test_kkrimp_BdG_workchain_entry_point(self): + from aiida_kkr.workflows.imp_BdG import kkrimp_BdG_wc + from aiida.plugins import WorkflowFactory + + wf = WorkflowFactory('kkr.imp_BdG') + assert wf == kkrimp_BdG_wc diff --git a/tests/tools/test_find_parent.py b/tests/tools/test_find_parent.py index d6c3c6b5..a3a8cc23 100755 --- a/tests/tools/test_find_parent.py +++ b/tests/tools/test_find_parent.py @@ -23,6 +23,21 @@ def test_find_parent_structure(): assert voro_parent.uuid == '559b9d9b-3525-402e-9b24-ecd8b801853c' +def test_find_structure_kkrimp(): + """ + find parent structure from a KkrimpCalculation + """ + import_with_migration('files/db_dump_kkrimp_out.tar.gz') + kkrimp_calc = load_node('eab8db1b-2cc7-4b85-a524-0df4ff2b7da6') + + # now find voronoi parent and structure + struc, voro_parent = find_parent_structure(kkrimp_calc) + + # check result + assert struc.uuid == 'e51ee6a1-bd27-4901-9612-7bac256bf117' + assert voro_parent.uuid == '559b9d9b-3525-402e-9b24-ecd8b801853c' + + def test_get_calc_from_remote(): """ find parent calc from remote diff --git a/tests/workflows/test_bs_wc/test_bs_wc_Cu.npz b/tests/workflows/test_bs_wc/test_bs_wc_Cu.npz index 004fcfbf..8ba417d6 100644 Binary files a/tests/workflows/test_bs_wc/test_bs_wc_Cu.npz and b/tests/workflows/test_bs_wc/test_bs_wc_Cu.npz differ diff --git a/tests/workflows/test_combine_imps.py b/tests/workflows/test_combine_imps.py index 42934def..0268fdc5 100755 --- a/tests/workflows/test_combine_imps.py +++ b/tests/workflows/test_combine_imps.py @@ -9,6 +9,9 @@ from aiida_kkr.workflows import combine_imps_wc from ..conftest import import_with_migration +# activate debug mode? +_debug = True + def write_graph(node, label=''): #if create_graph_file: @@ -25,11 +28,15 @@ def write_graph(node, label=''): def get_single_imp_inputs(): # import single imp calculations - group_pk = import_with_migration(test_dir / 'data_dir/kkr_imp_wc-nodes-4e7fa222d8fbe143b13363013103a8e3.tar.gz') + group_pk = import_with_migration(test_dir / 'data_dir/kkr_imp_sub_wc-nodes-6227d9003b63b76d1fd41bd5322771b5.tar.gz') + if _debug: + print(group_pk, [i.label for i in load_group(group_pk).nodes]) for node in load_group(group_pk).nodes: - if node.label == 'kkrimp_scf full Cu host_in_host': + if 'KKRimp calculation step 4' in node.label: imp1 = node - imp1_out = imp1.outputs.workflow_info + if _debug: + print(imp1, list(imp1.outputs), list(imp1.inputs)) + imp1_out = imp1.outputs.output_parameters imp2_out = imp1_out # use the same impurity and create a dimer return imp1_out, imp2_out diff --git a/tests/workflows/test_jij_wc/test_jij.npz b/tests/workflows/test_jij_wc/test_jij.npz index a526dac9..c868a693 100644 Binary files a/tests/workflows/test_jij_wc/test_jij.npz and b/tests/workflows/test_jij_wc/test_jij.npz differ diff --git a/tests/workflows/test_jij_wc/test_jij_soc.npz b/tests/workflows/test_jij_wc/test_jij_soc.npz index 6a84528c..cf4ad51c 100644 Binary files a/tests/workflows/test_jij_wc/test_jij_soc.npz and b/tests/workflows/test_jij_wc/test_jij_soc.npz differ