From e5917197a3e775306b8f8744b11b21ce3fda2b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20R=C3=BC=C3=9Fmann?= Date: Fri, 10 May 2024 07:59:39 +0000 Subject: [PATCH] Pre-commit fixes --- aiida_kkr/tools/tools_STM_scan.py | 148 ++++++------ aiida_kkr/workflows/kkr_STM.py | 349 ++++++++++++++--------------- aiida_kkr/workflows/kkr_imp_sub.py | 2 +- tests/test_entrypoints.py | 4 +- 4 files changed, 248 insertions(+), 255 deletions(-) diff --git a/aiida_kkr/tools/tools_STM_scan.py b/aiida_kkr/tools/tools_STM_scan.py index f8999497..65e7b4b4 100644 --- a/aiida_kkr/tools/tools_STM_scan.py +++ b/aiida_kkr/tools/tools_STM_scan.py @@ -19,6 +19,7 @@ ############################################################################## # combine impurty clusters + def convert_to_imp_cls(host_structure, imp_info): """ convert imp info to rcls form @@ -230,77 +231,76 @@ def create_combined_potential_node_cf(add_position, host_remote, imp_potential_n ##################################################################### -# STM pathfinder +# STM pathfinder def STM_pathfinder(host_structure): from aiida_kkr.tools import find_parent_structure from ase.spacegroup import Spacegroup - - """This function is used to help visualize the scanned positions + """This function is used to help visualize the scanned positions and the symmetries that are present in the system """ - """ - inputs: + inputs: host_struture : RemoteData : The Remote data contains all the information needed to create the path to scan - - outputs: + + outputs: struc_info : Dict : Dictionary containing the structural information of the film matrices : Array : Array containing the matrices that generate the symmetries of the system - + """ - + def info_creation(structure): from ase.spacegroup import get_spacegroup - # List of the Bravais vectors + # List of the Bravais vectors vec_list = structure.cell.tolist() - + # Find the Bravais vectors that are in plane vectors - plane_vectors = {'plane_vectors':[], 'space_group':''} + plane_vectors = {'plane_vectors': [], 'space_group': ''} for vec in vec_list: # Is this sufficient to find all the in-plane vectors? if vec[2] == 0: plane_vectors['plane_vectors'].append(vec[:2]) - + space_symmetry = get_spacegroup(ase_struc) plane_vectors['space_group'] = space_symmetry.no - + return plane_vectors - + def symmetry_finder(struc_info): # Here we get the symmetry operations that are possible symmetry_matrices = Spacegroup(struc_info['space_group']) - + # Reduce the dimensionality, we only want the 2D matrices matrices = [] for element in symmetry_matrices.get_rotations(): matrices.append(element[:2, :2]) - + # Uniqueness of the elements must be preserved unique_matrices = [] for matrix in matrices: if not any(np.array_equal(matrix, m) for m in unique_matrices): unique_matrices.append(matrix) - + return unique_matrices - + struc = find_parent_structure(host_structure) # clone the structure since it has already been saved in AiiDA and cannot be modified supp_struc = struc.clone() - + # If the structure is not periodic in every direction we force it to be. supp_struc.pbc = (True, True, True) - - # ASE struc + + # ASE struc ase_struc = supp_struc.get_ase() - + # Structural informations are stored here struc_info = info_creation(ase_struc) - + # The structural informations are then used to find the symmetries of the system symm_matrices = symmetry_finder(struc_info) - - return struc_info, symm_matrices + + return struc_info, symm_matrices + @engine.calcfunction def STM_pathfinder_cf(host_structure): @@ -318,50 +318,52 @@ def STM_pathfinder_cf(host_structure): def lattice_generation(x_len, y_len, rot, vec): - import math - + import math """ x_len : int : value to create points between - x and x. y_len : int : value to create points between - y and y. rot : list : list of the rotation matrices given by the symmetry of the system. vec : list : list containing the two Bravais vectors. """ - + # Here we create a grid in made of points whic are the linear combination of the lattice vectors lattice_points = [] - - for i in range(-x_len, x_len+1): + + for i in range(-x_len, x_len + 1): lattice_points_col = [] - for j in range(-y_len, y_len+1): - p = [i*x + j*y for x,y in zip(vec[0], vec[1])] + for j in range(-y_len, y_len + 1): + p = [i * x + j * y for x, y in zip(vec[0], vec[1])] lattice_points_col.append(p) lattice_points.append(lattice_points_col) - + # Eliminiatio of the symmetrical sites points_to_eliminate = [] - - for i in range(-x_len, x_len+1): - for j in range(-y_len, y_len+1): + + for i in range(-x_len, x_len + 1): + for j in range(-y_len, y_len + 1): if lattice_points[i][j][0] >= 0 and lattice_points[i][j][1] >= 0: for element in rot[1:]: point = np.dot(element, lattice_points[i][j]) - if point[0] >= 0 and point[1] >=0: + if point[0] >= 0 and point[1] >= 0: continue else: points_to_eliminate.append(point) - + points_to_scan = [] - + for i in range(-x_len, x_len + 1): for j in range(-y_len, y_len + 1): eliminate = False for elem in points_to_eliminate: # Since there can be some numerical error in the dot product we use the isclose function - if (math.isclose(elem[0], lattice_points[i][j][0], abs_tol=1e-4) and math.isclose(elem[1], lattice_points[i][j][1], abs_tol=1e-4)): + if ( + math.isclose(elem[0], lattice_points[i][j][0], abs_tol=1e-4) and + math.isclose(elem[1], lattice_points[i][j][1], abs_tol=1e-4) + ): eliminate = True if not eliminate: points_to_scan.append(lattice_points[i][j]) - + return points_to_eliminate, points_to_scan @@ -373,48 +375,50 @@ def lattice_plot(plane_vectors, symm_vec, symm_matrices, grid_length_x, grid_len from aiida_kkr.tools import lattice_generation import matplotlib.pyplot as plt from matplotlib.lines import Line2D - - origin = np.array([[0, 0],[0, 0]]) + + origin = np.array([[0, 0], [0, 0]]) # Generation of the points to plot - unused , used = lattice_generation(grid_length_x, grid_length_y, symm_matrices, plane_vectors) - + unused, used = lattice_generation(grid_length_x, grid_length_y, symm_matrices, plane_vectors) + # Plotting of the points for element in unused: plt.scatter(element[0], element[1], marker='s', s=130, c='#33638DFF') - + for element in used: - plt.scatter(element[0], element[1], marker='D', s=130, c='#FDE725FF') - - # Plot of the crystal symmetry directions, tag must be activated. + plt.scatter(element[0], element[1], marker='D', s=130, c='#FDE725FF') + + # Plot of the crystal symmetry directions, tag must be activated. if symm_vec: import numpy.linalg as lin - + for element in symm_matrices: eig_val, eig_vec = lin.eig(element) - + for element in eig_vec: - plt.quiver(*origin, element[0], element[1], alpha=1, color='#B8DE29FF', angles='xy', scale_units='xy', scale=1) - + plt.quiver( + *origin, element[0], element[1], alpha=1, color='#B8DE29FF', angles='xy', scale_units='xy', scale=1 + ) + # Plot of the Bravais lattice for element in plane_vectors: plt.quiver(*origin, element[0], element[1], color='#3CBB75FF', angles='xy', scale_units='xy', scale=1) - + legend_elements = [ - Line2D([0], [0], color='#33638DFF', lw=2, label='Unscanned Sites', marker='s'), - Line2D([0], [0], color='#FDE725FF', lw=2, label='Scanned Sites', marker='D'), - Line2D([0], [0], color='#3CBB75FF', lw=2, label='Bravais lattice'), + Line2D([0], [0], color='#33638DFF', lw=2, label='Unscanned Sites', marker='s'), + Line2D([0], [0], color='#FDE725FF', lw=2, label='Scanned Sites', marker='D'), + Line2D([0], [0], color='#3CBB75FF', lw=2, label='Bravais lattice'), ] plt.legend(handles=legend_elements, bbox_to_anchor=(0.75, -0.15)) - + plt.title('Lattice plot and symmetry directions') plt.ylabel('y direction') plt.xlabel('x direction') #plt.xticks(np.arange(-grid_length, grid_length, float(plane_vectors[0][0]))) #plt.set_cmap(cmap) - plt.grid(linestyle='--') + plt.grid(linestyle='--') plt.show() - - + + ########################################################## # find linear combination coefficients @@ -423,31 +427,31 @@ def find_linear_combination_coefficients(plane_vectors, vectors): from operator import itemgetter """This helper function takes the planar vectors and a list of vectors and return the coefficients in the base of the planar vectors""" - + # Formulate the system of equations Ax = b A = np.vstack((plane_vectors[0], plane_vectors[1])).T # Solve the system of equations using least squares method data = [] for element in vectors: - b = element + b = element # We use the least square mean error procedure to evaulate the units of da and db # lstsq returns: coeff, residue, rank, and singular value # We only need the coefficient. data.append(np.linalg.lstsq(A, b, rcond=None)) - + indices = [] for element in data: supp = [] for elem in element[0]: - # Here we round to an integer, this is because of the numerical error - # which is present inside the calculation. + # Here we round to an integer, this is because of the numerical error + # which is present inside the calculation. supp.append(round(elem)) indices.append(supp) - + # Before returning the indices, we reorder them first from the lowest to the highest valued - # on the x axis and then from the lowest to the highest on the y axis. - - indices = sorted(indices, key=itemgetter(0,1)) - + # on the x axis and then from the lowest to the highest on the y axis. + + indices = sorted(indices, key=itemgetter(0, 1)) + return indices diff --git a/aiida_kkr/workflows/kkr_STM.py b/aiida_kkr/workflows/kkr_STM.py index 098f8492..ffa3714c 100644 --- a/aiida_kkr/workflows/kkr_STM.py +++ b/aiida_kkr/workflows/kkr_STM.py @@ -16,68 +16,70 @@ __contributors__ = (u'Raffaele Aliberti, David Antognini Silva, Philipp Rüßmann') _VERBOSE_ = True + class kkr_STM_wc(WorkChain): """ Workchain for the Simulation of a (superconducting) STM tip around a magnetic impurity. The workchain uses the (converged) impurity calculation of a host system and combines them - with vacuum sites in positions + with vacuum sites in positions inputs:: :param options: (Dict), computer options :param tip_position: (Dict), specify the position of the STM tip :param impurity_info: (Dict), information about the impurity cluster - :param host_calc: (RemoteData), information about the host structure of the sample + :param host_calc: (RemoteData), information about the host structure of the sample :param wf_parameters: (Dict), parameters that are used to run the workflow :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 - + returns:: - + :return workflow_info: (Dict), Information of workflow results like success, last result node, list with convergence behavior - :return STM_dos_data: (XYData), Returns the plot of the lmDOS of the calculation + :return STM_dos_data: (XYData), Returns the plot of the lmDOS of the calculation :retrun STM_lmdos_data: (XYData), Returns the interpolated lmDOS of the calculation""" - + # TO DO: Add the initialize step. # TO DO: Add check that between the ilayer and the actual number of layers in the structure. # TO DO: Add to the outputs the calculated imp_info and imp_potential. # TO DO: Add BdG options for the builder run - + _wf_version = __version__ _wf_label = 'STM_wc' _wf_description = 'Workflow for simulating an STM measurement' _options_default = { 'queue_name': '', # Queue name to submit jobs too - 'resources': {'num_machines': 1, 'num_mpiprocs_per_machine': 48, 'num_cores_per_mpiproc': 1}, # resources to allocate for the job - 'max_wallclock_seconds': 3600*2, # walltime after which the job gets killed (gets parsed to KKR)} + 'resources': { + 'num_machines': 1, + 'num_mpiprocs_per_machine': 48, + 'num_cores_per_mpiproc': 1 + }, # resources to allocate for the job + 'max_wallclock_seconds': 3600 * 2, # walltime after which the job gets killed (gets parsed to KKR)} 'custom_scheduler_commands': '', # some additional scheduler commands 'withmpi': True } # execute KKR with mpi or without _wf_default = { 'jij_run': False, # calculate Jij's energy resolved - 'lmdos': True, # calculate also (l,m) or only l-resolved DOS, for the STM wf this is alwyas set on True as default + 'lmdos': + True, # calculate also (l,m) or only l-resolved DOS, for the STM wf this is alwyas set on True as default 'retrieve_kkrflex': False, # retrieve kkrflex files to repository or leave on remote computer only } # add defaults of dos_params since they are passed onto that workflow _wf_default['dos_params'] = kkr_imp_dos_wc.get_wf_defaults()['dos_params'] - + @classmethod def define(cls, spec): """ Layout of the workflow, defines the input nodes and the outline of the workchain """ super(kkr_STM_wc, cls).define(spec) - - spec.input('kkr', valid_type=Code, required=False, - help='KKRhost code, needed if gf_dos_remote is not given.' - ) - - spec.input('kkrimp', valid_type=Code, required=True, - help='KKRimp code, always needed.' - ) - + + spec.input('kkr', valid_type=Code, required=False, help='KKRhost code, needed if gf_dos_remote is not given.') + + spec.input('kkrimp', valid_type=Code, required=True, help='KKRimp code, always needed.') + spec.input( 'options', valid_type=Dict, @@ -85,7 +87,7 @@ def define(cls, spec): default=lambda: Dict(dict=cls._options_default), help='Computer options (resources, quene name, etc.).' ) - + spec.input( 'wf_parameters', valid_type=Dict, @@ -93,147 +95,133 @@ def define(cls, spec): default=lambda: orm.Dict(dict=cls._wf_default), help='Workflow parameter (see `kkr_dos_wc.get_wf_defaults()`).' ) - + spec.input( 'tip_position', valid_type=Dict, required=False, - # Find a way to create an area of this position. - default=lambda:Dict({'ilayer': 0, 'nx': 0, 'ny': 0}), + # Find a way to create an area of this position. + default=lambda: Dict({ + 'ilayer': 0, + 'nx': 0, + 'ny': 0 + }), # In the previous line we have set to study the first layer # nx is the number of (symmetric) steps that we take in the x-direction starting from the impurity # ny is the number of (symmetric) steps that we take in the y-direction starting from the impurity # (0,0) correspond to calculate the DOS only on the impurity site - help= - 'How many sites will be scanned in the x and y direction. And the layer that is being scanned.' + help='How many sites will be scanned in the x and y direction. And the layer that is being scanned.' ) - + spec.input( 'imp_info', valid_type=Dict, required=True, - help= - 'Information of the impurity like position in the unit cell, screening cluster, atom type.' + help='Information of the impurity like position in the unit cell, screening cluster, atom type.' ) - + spec.input( 'host_calc', valid_type=RemoteData, required=False, - help= - 'The information about the clean host structure is required in order to continue the cluster' + help='The information about the clean host structure is required in order to continue the cluster' 'Inside a bigger host structure with empty sites.' ) - + spec.input( 'host_remote', valid_type=RemoteData, required=True, help='Remote Data containing the remote folder from the outputs of the host calculation', ) - + spec.input( 'imp_potential_node', valid_type=SinglefileData, required=True, - help= - 'Impurity potential node', + help='Impurity potential node', ) - + spec.input( 'remote_data', valid_type=RemoteData, required=False, - help= - 'Remote data from a converged kkr calculation, required for the gf writeout step', + help='Remote data from a converged kkr calculation, required for the gf writeout step', ) - + spec.input( 'kkrflex_files', valid_type=RemoteData, required=False, - help= - 'with this input we can directly load the gf_dos files without calculating them' + help='with this input we can directly load the gf_dos files without calculating them' ) - - # Here we expose the inputs for the GF calculations step. + + # Here we expose the inputs for the GF calculations step. # One parameter which is crucial is the NSHELD, which determines the impurity cluster radius. spec.expose_inputs(kkr_flex_wc, namespace='gf_writeout', include=('params_kkr_overwrite')) - + # Specify the possible outputs spec.output('tip_position', valid_type=Dict) - + spec.output('STM_dos_data', valid_type=XyData, required=True) - + spec.output('STM_dos_data_lmdos', valid_type=XyData, required=True) - + #spec.output('workflow_info', valid_type=Dict) - + spec.output('kkrflexfiles', valid_type=RemoteData) - + spec.output('combined_imp_info', valid_type=Dict) - + spec.output('combined_imp_potential', valid_type=SinglefileData) - + # Define all possible error messages - - spec.exit_code( - 100, 'ERROR_STM_POSITION_NOT_VALID', 'The position provided for the STM probe are incorrect' - ) - spec.exit_code( - 101, 'ERROR_IMP_INFO_NOT_CORRECT', 'The node provided for the impurity info is not valid' - ) - spec.exit_code( - 102, 'ERROR_NO_IMP_POT_SFD', 'No impurity node has been given in the intput' - ) - spec.exit_code( - 103, 'ERROR_NO_IMPURITY_INFO', 'No impurity info has been given in the input' - ) + + spec.exit_code(100, 'ERROR_STM_POSITION_NOT_VALID', 'The position provided for the STM probe are incorrect') + spec.exit_code(101, 'ERROR_IMP_INFO_NOT_CORRECT', 'The node provided for the impurity info is not valid') + spec.exit_code(102, 'ERROR_NO_IMP_POT_SFD', 'No impurity node has been given in the intput') + spec.exit_code(103, 'ERROR_NO_IMPURITY_INFO', 'No impurity info has been given in the input') spec.exit_code( - 104, 'ERROR_NO_DATA_FOR_THE_GF_STEP', """Neither the kkrflex files nor the KKR builder have been given. Please + 104, 'ERROR_NO_DATA_FOR_THE_GF_STEP', + """Neither the kkrflex files nor the KKR builder have been given. Please provide already converged kkrflex files, or the kkr builder to evaluate them""" ) - spec.exit_code( - 201, 'ERROR_IMP_SUB_WORKFLOW_FAILURE', 'A step in the kkr_imp_dos workflow has failed' - ) - - + spec.exit_code(201, 'ERROR_IMP_SUB_WORKFLOW_FAILURE', 'A step in the kkr_imp_dos workflow has failed') + spec.outline( # For initializing workflow cls.start, # We first aggregate all the impurity data # The gf is then used to evaluate the STM lmdos #cls.gf_writeout_run, - - cls.STM_lmdos_run, + cls.STM_lmdos_run, # Data aggregator, used to make the final result more user friendly # cls.finalize_results, - cls.results ) - + def combine_potentials(self, impurity_to_combine, da, db): from aiida_kkr.tools.tools_STM_scan import get_imp_info_add_position_cf """ - Here we want to combine the impurity information and the host information + Here we want to combine the impurity information and the host information """ - - imp_info = self.inputs.imp_info #(impurity to combine) + + imp_info = self.inputs.imp_info #(impurity to combine) host_remote = self.inputs.host_remote - # Since the objects in AiiDA are immutable we have to create a new dictionary and then convert + # Since the objects in AiiDA are immutable we have to create a new dictionary and then convert # it to the right AiiDA type tip_position = {} tip_position['ilayer'] = self.inputs.tip_position['ilayer'] tip_position['da'] = da tip_position['db'] = db - + combined_imp_info = get_imp_info_add_position_cf(tip_position, host_remote, imp_info) # Add check to see if imp_cls is there if 'imp_cls' in impurity_to_combine: - + new_combined_imp_info = {} - + for key, value in impurity_to_combine.items(): if key == 'Zimp': new_combined_imp_info[key] = impurity_to_combine[key] @@ -242,17 +230,17 @@ def combine_potentials(self, impurity_to_combine, da, db): # Here we have lists of list that we need to confront new_combined_imp_info[key] = impurity_to_combine[key] set_tmp = [set(row) for row in impurity_to_combine[key]] - + new_combined_imp_info[key] += [row for row in combined_imp_info[key] if set(row) not in set_tmp] - + new_combined_imp_info = orm.Dict(dict=new_combined_imp_info) else: - - new_combined_imp_info = combined_imp_info - + + new_combined_imp_info = combined_imp_info + return new_combined_imp_info - + def combine_nodes(self, node_to_combine, da, db): from aiida_kkr.tools.tools_STM_scan import create_combined_potential_node_cf """ @@ -260,47 +248,48 @@ def combine_nodes(self, node_to_combine, da, db): and from the impurity potential """ #imp_potential_node = self.inputs.imp_potential_node # (node_to_combine). - host_remote = self.inputs.host_remote # the remote host structure remains the same. - - # Since the objects in AiiDA are immutable we have to create a new dictionary and then convert + host_remote = self.inputs.host_remote # the remote host structure remains the same. + + # Since the objects in AiiDA are immutable we have to create a new dictionary and then convert # it to the right AiiDA type - + tip_position = {} - tip_position['ilayer'] = self.inputs.tip_position['ilayer'] # for now we require that the z position remains the same. + tip_position['ilayer'] = self.inputs.tip_position['ilayer' + ] # for now we require that the z position remains the same. tip_position['da'] = da tip_position['db'] = db - combined_node = create_combined_potential_node_cf(tip_position, host_remote, node_to_combine) + combined_node = create_combined_potential_node_cf(tip_position, host_remote, node_to_combine) return combined_node - + def start(self): """ Initialise context and some parameters """ - + self.report(f'INFO: started STM workflow version {self._wf_version}') if _VERBOSE_: self.report(f'inputs: {self.inputs}') - + # Input both wf and options parameters # We check if the inputs for the wc where given - # Otherwise we assign the default values + # Otherwise we assign the default values if 'options' in self.inputs: options_dict = self.inputs.options.get_dict() # empty dictionary evaluate as false to Python if options_dict == {}: options_dict = self._options_default self.report('INFO: using default options parameters') - + if 'wf_parameters' in self.inputs: wf_param_dict = self.inputs.wf_parameters.get_dict() # empty dictionary evaluate as false to Python if wf_param_dict == {}: options_dict = self._wf_default self.report('INFO: usign defalut wf parameters') - + # In this section we assign the computational resources to the builder - + self.ctx.withmpi = options_dict.get('withmpi', self._options_default['withmpi']) self.ctx.resources = options_dict.get('resources', self._options_default['resources']) self.ctx.max_wallclock_seconds = options_dict.get( @@ -321,11 +310,11 @@ def start(self): # Set workflow parameters for the KKR imputrity calculations """This part is really important, this should always be set to True for an STM calculation""" self.ctx.lmdos = wf_param_dict.get('lmdos', self._wf_default['lmdos']) - + self.ctx.retrieve_kkrflex = wf_param_dict.get('retrieve_kkrflex', self._wf_default['retrieve_kkrflex']) self.ctx.dos_params_dict = wf_param_dict.get('dos_params', self._wf_default['dos_params']) - + # fill missing key, value pairs with defaults for k, v in self._wf_default['dos_params'].items(): if k not in self.ctx.dos_params_dict.keys(): @@ -337,7 +326,7 @@ def start(self): # set workflow label and description self.ctx.description_wf = self.inputs.get('description', self._wf_description) self.ctx.label_wf = self.inputs.get('label', self._wf_label) - + message = f""" INFO: use the following parameter: withmpi: {self.ctx.withmpi} @@ -355,169 +344,169 @@ def start(self): self.ctx.successful = True self.ctx.errors = [] self.ctx.formula = '' - - + def validate_input(self): - + inputs = self.inputs inputs_ok = True gf_writeout_calc = None - + if not 'imp_potential_node' in inputs: inputs_ok = False return self.exit_codes.ERROR_NO_IMP_POT_SFD - + if not 'imp_info' in inputs: inputs_ok = False return self.exit_codes.ERROR_NO_IMP_INFO - + if not 'kkrflex_files' and 'kkr' in inputs: inputs_ok = False return self.exit_codes.ERROR_NO_DATA_FOR_THE_GF_STEP - + def impurity_cluster_evaluation(self): from aiida_kkr.tools import tools_STM_scan - + # Here we create an impurity cluster that has inside all the positions on which the STM will scan - - # We now want to iterate over several in-plane positions. + + # We now want to iterate over several in-plane positions. # These are the number of vectors in which we want to move the STM tip. x = self.inputs.tip_position['nx'] y = self.inputs.tip_position['ny'] - - impurity_info = self.inputs.imp_info # for the first step we combine the impurity info from the input - imp_potential_node = self.inputs.imp_potential_node # for the first step we combine the impurity node from the input - - # Information of the host structure + + impurity_info = self.inputs.imp_info # for the first step we combine the impurity info from the input + imp_potential_node = self.inputs.imp_potential_node # for the first step we combine the impurity node from the input + + # Information of the host structure struc_info, symm_matrices = tools_STM_scan.STM_pathfinder(host_remote) - + # Path creation step. (The the identity operator is present, but will be excluded) unused_pos, used_pos = tools_STM_scan.lattice_generation(x, y, symm_matrices, struc_info['plane_vectors']) - + # Since the combine tools use the element already in the units of da and db, we use a helper function # to have the indices of the linear combination of the used position vectors in the base of the Bravais lattice. - + coeff = tools_STM_scan.find_linear_combination_coefficients(struc_info['plane_vectors'], used_pos) - - for element in coeff: + + for element in coeff: tmp_imp_info = self.combine_potentials(impurity_info, element[0], element[1]) impurity_info = tmp_imp_info - + # Aggregation the impurity nodes tmp_imp_pot = self.combine_nodes(imp_potential_node, element[0], element[1]) imp_potential_node = tmp_imp_pot - - return impurity_info, imp_potential_node + + return impurity_info, imp_potential_node def STM_lmdos_run(self): """In this part of the worflow we want to simulate the lmdos which a STM is able to measure """ - - # First we would like to distinguish between an impurity dos and a normal state calculation + + # First we would like to distinguish between an impurity dos and a normal state calculation builder = kkr_imp_dos_wc.get_builder() - + # Code loading - builder.kkrimp = self.inputs.kkrimp # needed for the kkr_imp_dos_wc - + builder.kkrimp = self.inputs.kkrimp # needed for the kkr_imp_dos_wc + # Builder options builder.options = self.ctx.options_params_dict - + # Check if the kkrflex files are already given in the outputs if 'kkrflex_files' in self.inputs: builder.gf_dos_remote = self.inputs.kkrflex_files else: - builder.kkr = self.inputs.kkr # needed to evaluate the kkr_flex files in the DOS step - + builder.kkr = self.inputs.kkr # needed to evaluate the kkr_flex files in the DOS step + # NSHELD is the parameter that controls the radius in the impurity cluster. - # The bigger the scanning position, the greater it must be set. + # The bigger the scanning position, the greater it must be set. if 'gf_writeout' in self.inputs: if 'params_kkr_overwrite' in self.inputs.gf_writeout: builder.gf_writeout.params_kkr_overwrite = self.inputs.gf_writeout.params_kkr_overwrite else: # This is a big value of NSHELD to make sure that most calculations work builder.gf_writeout.params_kkr_overwrite = Dict(dict={'NSHELD': 1500}) - - self.ctx.kkrimp_params_dict = Dict(dict={ - 'nsteps': 1, - 'kkr_runmax': 1, - 'dos_run': True, - 'lmdos': self.ctx.lmdos, - 'jij_run': self.ctx.jij_run, - 'dos_params': self.ctx.dos_params_dict - }) - + + self.ctx.kkrimp_params_dict = Dict( + dict={ + 'nsteps': 1, + 'kkr_runmax': 1, + 'dos_run': True, + 'lmdos': self.ctx.lmdos, + 'jij_run': self.ctx.jij_run, + 'dos_params': self.ctx.dos_params_dict + } + ) + # We want to set the energy to the Fermi level - self.ctx.kkrimp_params_dict['dos_params']['emin'] = 0-0.005 - self.ctx.kkrimp_params_dict['dos_params']['emax'] = 0+0.005 - + self.ctx.kkrimp_params_dict['dos_params']['emin'] = 0 - 0.005 + self.ctx.kkrimp_params_dict['dos_params']['emax'] = 0 + 0.005 + # Finally we overwrite the number of energy points to 1 # This is because we want many epoints around the impurity position - - self.ctx.kkrimp_params_dict['dos_params']['nepts'] = 7 # Here 7 because of the interpolated files that aren't generated - + + self.ctx.kkrimp_params_dict['dos_params'][ + 'nepts'] = 7 # Here 7 because of the interpolated files that aren't generated + #builder.metadata.label = label_imp # pylint: disable=no-member #builder.metadata.description = description_imp # pylint: disable=no-member - - builder.wf_parameters = self.ctx.kkrimp_params_dict + + builder.wf_parameters = self.ctx.kkrimp_params_dict # Host remote files that will be used for the actual plot step. builder.host_remote = self.inputs.host_remote - - + # Here we create the impurity cluster for the STM scanning tool impurity_info, imp_pot_sfd = self.impurity_cluster_evaluation() - + # impurity info for the workflow - builder.impurity_info = impurity_info + builder.impurity_info = impurity_info builder.imp_pot_sfd = imp_pot_sfd - + x = self.inputs.tip_position['nx'] y = self.inputs.tip_position['ny'] - + calc = self.submit(builder) message = f"""INFO: running DOS step for an STM measurement (pk: {calc.pk}) at position (ilayer: {self.inputs.tip_position['ilayer']}, da: {x}, db: {y} )""" - + print(message) self.report(message) - - # Save the calculated impurity cluster and impurity info in the context + + # Save the calculated impurity cluster and impurity info in the context self.ctx.impurity_info = impurity_info self.ctx.imp_pot_sfd = imp_pot_sfd - - return ToContext(STM_data = calc) - + + return ToContext(STM_data=calc) + def results(self): - + if not self.ctx.STM_data.is_finished_ok: - + message = 'ERROR: sub workflow for STM calculation failed' print(message) self.report(message) return self.exit_codes.ERROR_IMP_SUB_WORKFLOW_FAILURE - + else: # Declaring the output - self.out("STM_dos_data", self.ctx.STM_data.outputs.dos_data) - self.out("STM_dos_data_lmdos", self.ctx.STM_data.outputs.dos_data_lm) + self.out('STM_dos_data', self.ctx.STM_data.outputs.dos_data) + self.out('STM_dos_data_lmdos', self.ctx.STM_data.outputs.dos_data_lm) #self.out("workflow_info", self.ctx.STM_lmdos.outputs.workflow_info) - self.out("tip_position", self.inputs.tip_position) + self.out('tip_position', self.inputs.tip_position) try: - self.out("kkrflexfiles",self.ctx.STM_data.outputs.gf_dos_remote) + self.out('kkrflexfiles', self.ctx.STM_data.outputs.gf_dos_remote) except: pass - - self.out("combined_imp_info", self.ctx.impurity_info) - self.out("combined_imp_potential", self.ctx.imp_pot_sfd) - - + + self.out('combined_imp_info', self.ctx.impurity_info) + self.out('combined_imp_potential', self.ctx.imp_pot_sfd) + message = 'INFO: created output nodes for KKR STM workflow.' print(message) self.report(message) - + self.report( '\n' '|------------------------------------------------------------------------------------------------------------------|\n' '|-----------------------------------------| Done with the STM workflow! |------------------------------------------|\n' '|------------------------------------------------------------------------------------------------------------------|' - ) \ No newline at end of file + ) diff --git a/aiida_kkr/workflows/kkr_imp_sub.py b/aiida_kkr/workflows/kkr_imp_sub.py index bc9b810d..a71f7863 100644 --- a/aiida_kkr/workflows/kkr_imp_sub.py +++ b/aiida_kkr/workflows/kkr_imp_sub.py @@ -933,7 +933,7 @@ def inspect_kkrimp(self): rms_LDAU_all_iter_last_calc = list(last_calc_output['convergence_group']['rms_LDAU_all_iterations']) self.ctx.last_rms_LDAU_all = rms_LDAU_all_iter_last_calc except: - pass + pass # add lists of last iterations self.ctx.last_rms_all = rms_all_iter_last_calc diff --git a/tests/test_entrypoints.py b/tests/test_entrypoints.py index 934ff150..36830e2b 100644 --- a/tests/test_entrypoints.py +++ b/tests/test_entrypoints.py @@ -205,10 +205,10 @@ def test_kkrimp_BdG_workchain_entry_point(self): wf = WorkflowFactory('kkr.imp_BdG') assert wf == kkrimp_BdG_wc - + def test_kkr_STM_workchain_entry_point(self): from aiida_kkr.workflows.kkr_STM import kkr_STM_wc from aiida.plugins import WorkflowFactory - + wf = WorkflowFactory('kkr.STM') assert wf == kkr_STM_wc