def prepare_for_submission(self, folder): """ This is the routine to be called when you want to create the input files and related stuff with a plugin. :param folder: a aiida.common.folders.Folder subclass where the plugin should put all its files. """ if 'structure' in self.inputs: pmg_structure = self.inputs.structure.get_pymatgen_molecule() else: # If structure is not specified, it is read from the chk file pmg_structure = None # Generate the input file input_string = GaussianCalculation._render_input_string_from_params( self.inputs.parameters.get_dict(), pmg_structure) with open(folder.get_abs_path(self.INPUT_FILE), "w") as out_file: out_file.write(input_string) settings = self.inputs.settings.get_dict( ) if "settings" in self.inputs else {} # create code info codeinfo = CodeInfo() codeinfo.cmdline_params = settings.pop("cmdline", []) codeinfo.code_uuid = self.inputs.code.uuid codeinfo.stdin_name = self.INPUT_FILE codeinfo.stdout_name = self.OUTPUT_FILE codeinfo.withmpi = self.inputs.metadata.options.withmpi # create calculation info calcinfo = CalcInfo() calcinfo.remote_copy_list = [] calcinfo.local_copy_list = [] calcinfo.uuid = self.uuid calcinfo.cmdline_params = codeinfo.cmdline_params calcinfo.stdin_name = self.INPUT_FILE calcinfo.stdout_name = self.OUTPUT_FILE calcinfo.codes_info = [codeinfo] calcinfo.retrieve_list = [self.OUTPUT_FILE] # symlink or copy to parent calculation calcinfo.remote_symlink_list = [] calcinfo.remote_copy_list = [] if "parent_calc_folder" in self.inputs: comp_uuid = self.inputs.parent_calc_folder.computer.uuid remote_path = self.inputs.parent_calc_folder.get_remote_path() copy_info = (comp_uuid, remote_path, self.PARENT_FOLDER_NAME) if (self.inputs.code.computer.uuid == comp_uuid): # if running on the same computer - make a symlink # if not - copy the folder calcinfo.remote_symlink_list.append(copy_info) else: calcinfo.remote_copy_list.append(copy_info) return calcinfo
def prepare_for_submission(self, folder): """ Create input files. :param folder: aiida.common.folders.Folder subclass where the plugin should put all its files. """ # create input files: d3 structure = self.inputs.get('structure', None) try: d3_content = D3(self.inputs.parameters.get_dict(), structure) except (ValueError, NotImplementedError) as err: raise InputValidationError( "an input file could not be created from the parameters: {}". format(err)) with folder.open(self._INPUT_FILE_NAME, "w") as f: d3_content.write(f) # create input files: fort.9 with self.inputs.wavefunction.open(mode="rb") as f: folder.create_file_from_filelike(f, self._WAVEFUNCTION_FILE_NAME, mode="wb") # Prepare CodeInfo object for aiida codeinfo = CodeInfo() codeinfo.code_uuid = self.inputs.code.uuid codeinfo.stdin_name = self._INPUT_FILE_NAME codeinfo.stdout_name = self._OUTPUT_FILE_NAME codeinfo.withmpi = False # Prepare CalcInfo object for aiida calcinfo = CalcInfo() calcinfo.uuid = self.uuid calcinfo.codes_info = [codeinfo] calcinfo.local_copy_list = [] calcinfo.remote_copy_list = [] calcinfo.retrieve_list = [self._PROPERTIES_FILE_NAME] calcinfo.local_copy_list = [] return calcinfo
def prepare_for_submission(self, folder): """ This is the routine to be called when you want to create the input files and related stuff with a plugin. :param folder: a aiida.common.folders.Folder subclass where the plugin should put all its files. """ # create calc info calcinfo = CalcInfo() calcinfo.remote_copy_list = [] calcinfo.local_copy_list = [] # The main input try: input_string = GaussianCalculation._render_input_string_from_params( self.inputs.parameters.get_dict(), self.inputs.structure ) # If structure is not specified the user might want to restart from a chk except AttributeError: input_string = GaussianCalculation._render_input_string_from_params( self.inputs.parameters.get_dict(), None ) # Parse additional link1 sections if "extra_link1_sections" in self.inputs: for l1_name, l1_params in self.inputs.extra_link1_sections.items(): input_string += "--Link1--\n" # The link1 secions don't support their own geometries. input_string += GaussianCalculation._render_input_string_from_params( l1_params.get_dict(), None ) with open(folder.get_abs_path(self.INPUT_FILE), "w") as out_file: out_file.write(input_string) settings = self.inputs.settings.get_dict() if "settings" in self.inputs else {} # create code info codeinfo = CodeInfo() codeinfo.cmdline_params = settings.pop("cmdline", []) codeinfo.code_uuid = self.inputs.code.uuid codeinfo.stdin_name = self.INPUT_FILE codeinfo.stdout_name = self.OUTPUT_FILE codeinfo.withmpi = self.inputs.metadata.options.withmpi # create calculation info calcinfo.uuid = self.uuid calcinfo.cmdline_params = codeinfo.cmdline_params calcinfo.stdin_name = self.INPUT_FILE calcinfo.stdout_name = self.OUTPUT_FILE calcinfo.codes_info = [codeinfo] calcinfo.retrieve_list = [self.OUTPUT_FILE] # symlink or copy to parent calculation calcinfo.remote_symlink_list = [] calcinfo.remote_copy_list = [] if "parent_calc_folder" in self.inputs: comp_uuid = self.inputs.parent_calc_folder.computer.uuid remote_path = self.inputs.parent_calc_folder.get_remote_path() copy_info = (comp_uuid, remote_path, "parent_calc") if ( self.inputs.code.computer.uuid == comp_uuid ): # if running on the same computer - make a symlink # if not - copy the folder calcinfo.remote_symlink_list.append(copy_info) else: calcinfo.remote_copy_list.append(copy_info) return calcinfo
def prepare_for_submission( self, folder): # noqa: MC0001 - is mccabe too complex funct - """ Create the input files from the input nodes passed to this instance of the `CalcJob`. :param folder: an `aiida.common.folders.Folder` to temporarily write files on disk :return: `aiida.common.datastructures.CalcInfo` instance """ code = self.inputs.code ldos_folder = self.inputs.ldos_folder value = self.inputs.value spin_option = self.inputs.spin_option mode = self.inputs.mode # As internal convention, the keys of the settings dict are uppercase if 'settings' in self.inputs: settings = self.inputs.settings.get_dict() settings_dict = {str(k).upper(): v for (k, v) in settings.items()} else: settings_dict = {} # List of files for restart remote_copy_list = [] # ======================== Creation of input file ========================= # Input file is only necessary for the old versions of plstm. # For the new versions, all is done through command line (next section). # To have easy access to inputs metadata options metadataoption = self.inputs.metadata.options # input_filename access input_filename = folder.get_abs_path(metadataoption.input_filename) # Getting the prefix from ldos_folder if "prefix" in ldos_folder.creator.attributes: prefix = str(ldos_folder.creator.get_attribute("prefix")) else: self.report( "No prefix detected from the remote folder, set 'aiida' as prefix" ) prefix = "aiida" ldosfile = prefix + ".LDOS" # Convert height to bohr... if mode.value == "constant-height": vvalue = value.value / 0.529177 else: vvalue = value.value with open(input_filename, 'w') as infile: infile.write(f"{prefix}\n") infile.write("ldos\n") infile.write(f"{mode.value}\n") infile.write(f"{vvalue:.5f}\n") infile.write("unformatted\n") # ============================== Code and Calc info =============================== # Code information object is used to set up the the bash line that launches siesta # (CMDLINE and input output files). # Calc information object is to set up thee list of files to retrieve. # The presence of a 'ldos_folder' is mandatory, to get the LDOS file as indicated in # the self._restart_copy_from attribute. (this is not technically a restart, though) # It will be copied to the current calculation's working folder. remote_copy_list.append( (ldos_folder.computer.uuid, os.path.join(ldos_folder.get_remote_path(), self._restart_copy_from), self._restart_copy_to)) # Empty command line by default. Why use 'pop' ? cmdline_params = settings_dict.pop('CMDLINE', []) # Code information object. Sets the command line codeinfo = CodeInfo() if mode.value == "constant-height": cmdline_params = (list(cmdline_params) + ['-z', '{0:.5f}'.format(vvalue)]) else: cmdline_params = (list(cmdline_params) + ['-i', '{0:.5f}'.format(vvalue)]) if spin_option.value != "q": cmdline_params = (list(cmdline_params) + ['-s', str(spin_option.value), ldosfile]) else: cmdline_params = (list(cmdline_params) + [ldosfile]) codeinfo.cmdline_params = list(cmdline_params) codeinfo.stdin_name = metadataoption.input_filename codeinfo.stdout_name = metadataoption.output_filename codeinfo.code_uuid = code.uuid # Calc information object. Important for files to copy, retrieve, etc calcinfo = CalcInfo() calcinfo.uuid = str(self.uuid) calcinfo.local_copy_list = [ ] #No local files to copy (no pseudo for instance) calcinfo.remote_copy_list = remote_copy_list calcinfo.codes_info = [codeinfo] # Retrieve by default: the output file and the plot file. Some logic to understand which # is the plot file will be in parser, here we put to retrieve every file ending in *.STM calcinfo.retrieve_list = [] calcinfo.retrieve_list.append(metadataoption.output_filename) calcinfo.retrieve_list.append("*.STM") # Any other files specified in the settings dictionary settings_retrieve_list = settings_dict.pop('ADDITIONAL_RETRIEVE_LIST', []) calcinfo.retrieve_list += settings_retrieve_list return calcinfo
def prepare_for_submission(self, folder): # create calculation info calcinfo = CalcInfo() calcinfo.uuid = self.uuid calcinfo.codes_info = [] calcinfo.retrieve_list = [] calcinfo.retrieve_temporary_list = [] calcinfo.prepend_text = "export GAUSS_MEMDEF=%dMB\n" % self.inputs.gauss_memdef calcinfo.local_copy_list = [] if "stencil" in self.inputs: calcinfo.local_copy_list.append( (self.inputs.stencil.uuid, self.inputs.stencil.filename, 'stencil.txt')) for key, params in self.inputs.parameters.get_dict().items(): cube_name = key + ".cube" kind_str = params["kind"] npts = params["npts"] # create code info codeinfo = CodeInfo() codeinfo.cmdline_params = [] codeinfo.cmdline_params.append( str(self.inputs.metadata.options.resources['tot_num_mpiprocs']) ) codeinfo.cmdline_params.append(kind_str) codeinfo.cmdline_params.append(self.PARENT_FOLDER_NAME + "/" + self.DEFAULT_INPUT_FILE) codeinfo.cmdline_params.append(cube_name) if npts == -1: if 'stencil' not in self.inputs: self.report( "Warning: npts: -1 set but no stencil provided, using -2" ) codeinfo.cmdline_params.append("-2") else: codeinfo.cmdline_params.append(str(npts)) codeinfo.stdin_name = "stencil.txt" else: codeinfo.cmdline_params.append(str(npts)) codeinfo.code_uuid = self.inputs.code.uuid codeinfo.withmpi = self.inputs.metadata.options.withmpi calcinfo.codes_info.append(codeinfo) if self.inputs.retrieve_cubes.value: calcinfo.retrieve_list.append(cube_name) else: calcinfo.retrieve_temporary_list.append(cube_name) # symlink or copy to parent calculation calcinfo.remote_symlink_list = [] calcinfo.remote_copy_list = [] comp_uuid = self.inputs.parent_calc_folder.computer.uuid remote_path = self.inputs.parent_calc_folder.get_remote_path() copy_info = (comp_uuid, remote_path, self.PARENT_FOLDER_NAME) if self.inputs.code.computer.uuid == comp_uuid: # if running on the same computer - make a symlink # if not - copy the folder calcinfo.remote_symlink_list.append(copy_info) else: calcinfo.remote_copy_list.append(copy_info) return calcinfo
def prepare_for_submission(self, tempfolder): """ Create the input files from the input nodes passed to this instance of the `CalcJob`. :param tempfolder: an `aiida.common.folders.Folder` to temporarily write files on disk :return: `aiida.common.datastructures.CalcInfo` instance """ ##################################################### # BEGINNING OF INITIAL INPUT CHECK # # All input ports that are defined via spec.input # # are checked by default, only need to asses their # # presence in case they are optional # ##################################################### code = self.inputs.code structure = self.inputs.structure parameters = self.inputs.parameters if 'kpoints' in self.inputs: kpoints = self.inputs.kpoints else: kpoints = None if 'basis' in self.inputs: basis = self.inputs.basis else: basis = None if 'settings' in self.inputs: settings = self.inputs.settings.get_dict() settings_dict = _uppercase_dict(settings, dict_name='settings') else: settings_dict = {} if 'bandskpoints' in self.inputs: bandskpoints = self.inputs.bandskpoints else: bandskpoints = None if 'parent_calc_folder' in self.inputs: parent_calc_folder = self.inputs.parent_calc_folder else: parent_calc_folder = None pseudos = self.inputs.pseudos kinds = [kind.name for kind in structure.kinds] if set(kinds) != set(pseudos.keys()): raise ValueError( 'Mismatch between the defined pseudos and the list of kinds of the structure.\n', 'Pseudos: {} \n'.format(', '.join(list(pseudos.keys()))), 'Kinds: {}'.format(', '.join(list(kinds))), ) # List of the file to copy in the folder where the calculation # runs, for instance pseudo files local_copy_list = [] # List of files for restart remote_copy_list = [] ############################## # END OF INITIAL INPUT CHECK # ############################## # ============== Preprocess of input parameters =============== # There should be a warning for duplicated (canonicalized) keys # in the original dictionary in the script input_params = FDFDict(parameters.get_dict()) # Look for blocked keywords and # add the proper values to the dictionary for blocked_key in self._aiida_blocked_keywords: canonical_blocked = FDFDict.translate_key(blocked_key) for key in input_params: if key == canonical_blocked: raise InputValidationError( "You cannot specify explicitly the '{}' flag in the " "input parameters".format( input_params.get_last_key(key))) input_params.update({'system-name': self._PREFIX}) input_params.update({'system-label': self._PREFIX}) input_params.update({'use-tree-timer': 'T'}) input_params.update({'xml-write': 'T'}) input_params.update({'number-of-species': len(structure.kinds)}) input_params.update({'number-of-atoms': len(structure.sites)}) # Regarding the lattice-constant parameter: # -- The variable "alat" is not typically kept anywhere, and # has already been used to define the vectors. # We need to specify that the units of these vectors are Ang... input_params.update({'lattice-constant': '1.0 Ang'}) # Note that this will break havoc with the band-k-points "pi/a" # option. The use of this option should be banned. # Note that the implicit coordinate convention of the Structure # class corresponds to the "Ang" convention in Siesta. # That is why the "atomic-coordinates-format" keyword is blocked # and reset. input_params.update({'atomic-coordinates-format': 'Ang'}) # ============== Preparation of input data =============== # ---------------- CELL_PARAMETERS ------------------------ cell_parameters_card = "%block lattice-vectors\n" for vector in structure.cell: cell_parameters_card += ("{0:18.10f} {1:18.10f} {2:18.10f}" "\n".format(*vector)) cell_parameters_card += "%endblock lattice-vectors\n" # --------------ATOMIC_SPECIES & PSEUDOS------------------- # I create the subfolder that will contain the pseudopotentials tempfolder.get_subfolder(self._PSEUDO_SUBFOLDER, create=True) # I create the subfolder with the output data tempfolder.get_subfolder(self._OUTPUT_SUBFOLDER, create=True) atomic_species_card_list = [] # Dictionary to get the atomic number of a given element datmn = dict([(v['symbol'], k) for k, v in six.iteritems(elements)]) spind = {} spcount = 0 for kind in structure.kinds: spcount += 1 # species count spind[kind.name] = spcount atomic_species_card_list.append("{0:5} {1:5} {2:5}\n".format( spind[kind.name], datmn[kind.symbol], kind.name.rjust(6))) ps = pseudos[kind.name] # Add this pseudo file to the list of files to copy, with # the appropiate name. In the case of sub-species # (different kind.name but same kind.symbol, e.g., # 'C_surf', sharing the same pseudo with 'C'), we will # copy the file ('C.psf') twice, once as 'C.psf', and once # as 'C_surf.psf'. This is required by Siesta. # ... list of tuples with format ('node_uuid', 'filename', relativedestpath') # We probably should be pre-pending 'self._PSEUDO_SUBFOLDER' in the # last slot, for generality... if isinstance(ps, PsfData): local_copy_list.append((ps.uuid, ps.filename, kind.name + ".psf")) elif isinstance(ps, PsmlData): local_copy_list.append((ps.uuid, ps.filename, kind.name + ".psml")) else: pass atomic_species_card_list = (["%block chemicalspecieslabel\n"] + list(atomic_species_card_list)) atomic_species_card = "".join(atomic_species_card_list) atomic_species_card += "%endblock chemicalspecieslabel\n" # Free memory del atomic_species_card_list # --------------------- ATOMIC_POSITIONS ----------------------- atomic_positions_card_list = [ "%block atomiccoordinatesandatomicspecies\n" ] countatm = 0 for site in structure.sites: countatm += 1 atomic_positions_card_list.append( "{0:18.10f} {1:18.10f} {2:18.10f} {3:4} {4:6} {5:6}\n".format( site.position[0], site.position[1], site.position[2], spind[site.kind_name], site.kind_name.rjust(6), countatm)) atomic_positions_card = "".join(atomic_positions_card_list) del atomic_positions_card_list # Free memory atomic_positions_card += "%endblock atomiccoordinatesandatomicspecies\n" # -------------------- K-POINTS ---------------------------- # It is optional, if not specified, gamma point only is performed, # this is default of siesta if kpoints is not None: # # Get a mesh for sampling # NOTE that there is not yet support for the 'kgrid-cutoff' # option in Siesta. # try: mesh, offset = kpoints.get_kpoints_mesh() has_mesh = True except AttributeError: raise InputValidationError("K-point sampling for scf " "must be given in mesh form") kpoints_card_list = ["%block kgrid_monkhorst_pack\n"] # # This will fail if has_mesh is False (for the case of a list), # since in that case 'offset' is undefined. # kpoints_card_list.append("{0:6} {1:6} {2:6} {3:18.10f}\n".format( mesh[0], 0, 0, offset[0])) kpoints_card_list.append("{0:6} {1:6} {2:6} {3:18.10f}\n".format( 0, mesh[1], 0, offset[1])) kpoints_card_list.append("{0:6} {1:6} {2:6} {3:18.10f}\n".format( 0, 0, mesh[2], offset[2])) kpoints_card = "".join(kpoints_card_list) kpoints_card += "%endblock kgrid_monkhorst_pack\n" del kpoints_card_list # ----------------- K-POINTS-FOR-BANDS ---------------------- #Two possibility are supported in Siesta: BandLines ad BandPoints #At the moment the user can't choose directly one of the two options #BandsLine is set automatically if bandskpoints has labels, #BandsPoints if bandskpoints has no labels #BandLinesScale =pi/a is not supported at the moment because currently #a=1 always. BandLinesScale ReciprocalLatticeVectors is always set if bandskpoints is not None: bandskpoints_card_list = [ "BandLinesScale ReciprocalLatticeVectors\n" ] if bandskpoints.labels == None: bandskpoints_card_list.append("%block BandPoints\n") for s in bandskpoints.get_kpoints(): bandskpoints_card_list.append( "{0:8.3f} {1:8.3f} {2:8.3f} \n".format( s[0], s[1], s[2])) fbkpoints_card = "".join(bandskpoints_card_list) fbkpoints_card += "%endblock BandPoints\n" else: bandskpoints_card_list.append("%block BandLines\n") savs = [] listforbands = bandskpoints.get_kpoints() for s, m in bandskpoints.labels: savs.append(s) rawindex = 0 for s, m in bandskpoints.labels: rawindex = rawindex + 1 x, y, z = listforbands[s] if rawindex == 1: bandskpoints_card_list.append( "{0:3} {1:8.3f} {2:8.3f} {3:8.3f} {4:1} \n".format( 1, x, y, z, m)) else: bandskpoints_card_list.append( "{0:3} {1:8.3f} {2:8.3f} {3:8.3f} {4:1} \n".format( s - savs[rawindex - 2], x, y, z, m)) fbkpoints_card = "".join(bandskpoints_card_list) fbkpoints_card += "%endblock BandLines\n" del bandskpoints_card_list # ================ Operations for restart ======================= # The presence of a 'parent_calc_folder' input node signals # that we want to get something from there, as indicated in the # self._restart_copy_from attribute. # In Siesta's case, for now, it is just the density-matrix file # # It will be copied to the current calculation's working folder. # NOTE: This mechanism is not flexible enough. # Maybe we should pass the information about which file(s) to # copy in the metadata 'options' dictionary if parent_calc_folder is not None: remote_copy_list.append( (parent_calc_folder.computer.uuid, os.path.join(parent_calc_folder.get_remote_path(), self._restart_copy_from), self._restart_copy_to)) input_params.update({'dm-use-save-dm': "T"}) # ====================== FDF file creation ======================== # To have easy access to inputs metadata options metadataoption = self.inputs.metadata.options # input_filename = self.inputs.metadata.options.input_filename input_filename = tempfolder.get_abs_path(metadataoption.input_filename) with open(input_filename, 'w') as infile: # here print keys and values tp file # for k, v in sorted(six.iteritems(input_params)): for k, v in sorted(input_params.get_filtered_items()): infile.write("%s %s\n" % (k, v)) # Basis set info is processed just like the general # parameters section. Some discipline is needed to # put any basis-related parameters (including blocks) # in the basis dictionary in the input script. # if basis is not None: infile.write("#\n# -- Basis Set Info follows\n#\n") for k, v in six.iteritems(basis.get_dict()): infile.write("%s %s\n" % (k, v)) # Write previously generated cards now infile.write("#\n# -- Structural Info follows\n#\n") infile.write(atomic_species_card) infile.write(cell_parameters_card) infile.write(atomic_positions_card) if kpoints is not None: infile.write("#\n# -- K-points Info follows\n#\n") infile.write(kpoints_card) if bandskpoints is not None: infile.write("#\n# -- Bandlines/Bandpoints Info follows\n#\n") infile.write(fbkpoints_card) # Write max wall-clock time infile.write("#\n# -- Max wall-clock time block\n#\n") infile.write("max.walltime {}".format( metadataoption.max_wallclock_seconds)) # ====================== Code and Calc info ======================== # Code information object and Calc information object are now # only used to set up the CMDLINE (the bash line that launches siesta) # and to set up the list of files to retrieve. cmdline_params = settings_dict.pop('CMDLINE', []) codeinfo = CodeInfo() codeinfo.cmdline_params = list(cmdline_params) codeinfo.stdin_name = metadataoption.input_filename codeinfo.stdout_name = metadataoption.output_filename codeinfo.code_uuid = code.uuid calcinfo = CalcInfo() calcinfo.uuid = str(self.uuid) if cmdline_params: calcinfo.cmdline_params = list(cmdline_params) calcinfo.local_copy_list = local_copy_list calcinfo.remote_copy_list = remote_copy_list calcinfo.stdin_name = metadataoption.input_filename calcinfo.stdout_name = metadataoption.output_filename calcinfo.codes_info = [codeinfo] # Retrieve by default: the output file, the xml file, the # messages file, and the json timing file. # If bandskpoints, also the bands file is added to the retrieve list. calcinfo.retrieve_list = [] calcinfo.retrieve_list.append(metadataoption.output_filename) calcinfo.retrieve_list.append(self._DEFAULT_XML_FILE) calcinfo.retrieve_list.append(self._DEFAULT_JSON_FILE) calcinfo.retrieve_list.append(self._DEFAULT_MESSAGES_FILE) if bandskpoints is not None: calcinfo.retrieve_list.append(self._DEFAULT_BANDS_FILE) # Any other files specified in the settings dictionary settings_retrieve_list = settings_dict.pop('ADDITIONAL_RETRIEVE_LIST', []) calcinfo.retrieve_list += settings_retrieve_list return calcinfo
def prepare_for_submission( self, folder): # noqa: MC0001 - is mccabe too complex funct - """ Create the input files from the input nodes passed to this instance of the `CalcJob`. :param folder: an `aiida.common.folders.Folder` to temporarily write files on disk :return: `aiida.common.datastructures.CalcInfo` instance """ # ============================ Initializations ============================= # All input ports are validated, here asses their presence in case optional. code = self.inputs.code # self.initialize preprocess structure and basis. Decides whether use ions or pseudos structure, basis_dict, floating_species_names, ion_or_pseudo_str = self.initialize( ) ion_or_pseudo = self.inputs[ion_or_pseudo_str] parameters = self.inputs.parameters if 'kpoints' in self.inputs: kpoints = self.inputs.kpoints else: kpoints = None # As internal convention, the keys of the settings dict are uppercase if 'settings' in self.inputs: settings = self.inputs.settings.get_dict() settings_dict = {str(k).upper(): v for (k, v) in settings.items()} else: settings_dict = {} if 'bandskpoints' in self.inputs: bandskpoints = self.inputs.bandskpoints else: bandskpoints = None if 'parent_calc_folder' in self.inputs: parent_calc_folder = self.inputs.parent_calc_folder else: parent_calc_folder = None lua_inputs = self.inputs.lua if 'script' in lua_inputs: lua_script = lua_inputs.script else: lua_script = None if 'parameters' in lua_inputs: lua_parameters = lua_inputs.parameters else: lua_parameters = None if 'input_files' in lua_inputs: lua_input_files = lua_inputs.input_files else: lua_input_files = None if 'retrieve_list' in lua_inputs: lua_retrieve_list = lua_inputs.retrieve_list else: lua_retrieve_list = None # List of files to copy in the folder where the calculation runs, e.g. pseudo files local_copy_list = [] # List of files for restart remote_copy_list = [] # ================ Preprocess of input parameters ================= input_params = FDFDict(parameters.get_dict()) input_params.update( {'system-name': self.inputs.metadata.options.prefix}) input_params.update( {'system-label': self.inputs.metadata.options.prefix}) input_params.update({'use-tree-timer': 'T'}) input_params.update({'xml-write': 'T'}) input_params.update({'number-of-species': len(structure.kinds)}) input_params.update({'number-of-atoms': len(structure.sites)}) input_params.update({'geometry-must-converge': 'T'}) input_params.update({'lattice-constant': '1.0 Ang'}) input_params.update({'atomic-coordinates-format': 'Ang'}) if lua_script is not None: input_params.update({'md-type-of-run': 'Lua'}) input_params.update({'lua-script': lua_script.filename}) local_copy_list.append( (lua_script.uuid, lua_script.filename, lua_script.filename)) if lua_input_files is not None: # Copy the whole contents of the FolderData object for file in lua_input_files.list_object_names(): local_copy_list.append((lua_input_files.uuid, file, file)) if ion_or_pseudo_str == "ions": input_params.update({'user-basis': 'T'}) # NOTES: # 1) The lattice-constant parameter must be 1.0 Ang to impose the units and consider # that the dimenstions of the lattice vectors are already correct with no need of alat. # This breaks the band-k-points "pi/a" option. The use of this option is banned. # 2) The implicit coordinate convention of the StructureData class corresponds to the "Ang" # convention in Siesta. That is why "atomic-coordinates-format" is blocked and reset. # 3) The Siesta code doesn't raise any warining if the geometry is not converged, unless # the keyword geometry-must-converge is set. That's why it is always added. # ============================ Preparation of input data ================================= # -------------------------------- CELL_PARAMETERS --------------------------------------- cell_parameters_card = "%block lattice-vectors\n" for vector in structure.cell: cell_parameters_card += ("{0:18.10f} {1:18.10f} {2:18.10f}" "\n".format(*vector)) cell_parameters_card += "%endblock lattice-vectors\n" # ----------------------------ATOMIC_SPECIES & PSEUDOS/IONS------------------------------- atomic_species_card_list = [] # Dictionary to get the atomic number of a given element datmn = {v['symbol']: k for k, v in elements.items()} spind = {} spcount = 0 for kind in structure.kinds: spcount += 1 # species count spind[kind.name] = spcount atomic_number = datmn[kind.symbol] # Siesta expects negative atomic numbers for floating species if kind.name in floating_species_names: atomic_number = -atomic_number #Create the core of the chemicalspecieslabel block atomic_species_card_list.append("{0:5} {1:5} {2:5}\n".format( spind[kind.name], atomic_number, kind.name.rjust(6))) psp_or_ion = ion_or_pseudo[kind.name] # Add pseudo (ion) file to the list of files to copy (create), with the appropiate name. # In the case of sub-species (different kind.name but same kind.symbol, e.g., 'C_surf', # sharing the same pseudo with 'C'), we copy the file ('C.psf') twice, once as 'C.psf', # and once as 'C_surf.psf'. This is required by Siesta. # It is passed as list of tuples with format ('node_uuid', 'filename', 'relativedestpath'). # Since no subfolder is present in Siesta for pseudos, filename == relativedestpath. if isinstance(psp_or_ion, IonData): file_name = kind.name + ".ion" with folder.open(file_name, 'w', encoding='utf8') as handle: handle.write(psp_or_ion.get_content_ascii_format()) if isinstance(psp_or_ion, (PsfData, DeprecatedPsfData)): local_copy_list.append( (psp_or_ion.uuid, psp_or_ion.filename, kind.name + ".psf")) if isinstance(psp_or_ion, (PsmlData, DeprecatedPsmlData)): local_copy_list.append((psp_or_ion.uuid, psp_or_ion.filename, kind.name + ".psml")) atomic_species_card_list = (["%block chemicalspecieslabel\n"] + list(atomic_species_card_list)) atomic_species_card = "".join(atomic_species_card_list) atomic_species_card += "%endblock chemicalspecieslabel\n" # Free memory del atomic_species_card_list # -------------------------------------- ATOMIC_POSITIONS ----------------------------------- atomic_positions_card_list = [ "%block atomiccoordinatesandatomicspecies\n" ] countatm = 0 for site in structure.sites: countatm += 1 atomic_positions_card_list.append( "{0:18.10f} {1:18.10f} {2:18.10f} {3:4} {4:6} {5:6}\n".format( site.position[0], site.position[1], site.position[2], spind[site.kind_name], site.kind_name.rjust(6), countatm)) atomic_positions_card = "".join(atomic_positions_card_list) del atomic_positions_card_list # Free memory atomic_positions_card += "%endblock atomiccoordinatesandatomicspecies\n" # --------------------------------------- K-POINTS ---------------------------------------- # It is optional, if not specified, gamma point only is performed (default of siesta) if kpoints is not None: mesh, offset = kpoints.get_kpoints_mesh() kpoints_card_list = ["%block kgrid_monkhorst_pack\n"] kpoints_card_list.append("{0:6} {1:6} {2:6} {3:18.10f}\n".format( mesh[0], 0, 0, offset[0])) kpoints_card_list.append("{0:6} {1:6} {2:6} {3:18.10f}\n".format( 0, mesh[1], 0, offset[1])) kpoints_card_list.append("{0:6} {1:6} {2:6} {3:18.10f}\n".format( 0, 0, mesh[2], offset[2])) kpoints_card = "".join(kpoints_card_list) kpoints_card += "%endblock kgrid_monkhorst_pack\n" del kpoints_card_list # ------------------------------------ K-POINTS-FOR-BANDS ---------------------------------- # Two possibility are supported in Siesta: BandLines ad BandPoints. # User can't choose directly one of the two options, BandsLine is set automatically # if bandskpoints has labels, BandsPoints if bandskpoints has no labels. # BandLinesScale=pi/a not supported because a=1 always. BandLinesScale ReciprocalLatticeVectors # always set. if bandskpoints is not None: #the band line scale bandskpoints_card_list = [ "BandLinesScale ReciprocalLatticeVectors\n" ] #set the BandPoints if bandskpoints.labels is None: bandskpoints_card_list.append("%block BandPoints\n") for kpo in bandskpoints.get_kpoints(): bandskpoints_card_list.append( "{0:8.3f} {1:8.3f} {2:8.3f} \n".format( kpo[0], kpo[1], kpo[2])) fbkpoints_card = "".join(bandskpoints_card_list) fbkpoints_card += "%endblock BandPoints\n" #set the BandLines else: bandskpoints_card_list.append("%block BandLines\n") savindx = [] listforbands = bandskpoints.get_kpoints() for indx, label in bandskpoints.labels: savindx.append(indx) rawindex = 0 for indx, label in bandskpoints.labels: rawindex = rawindex + 1 x, y, z = listforbands[indx] if rawindex == 1: bandskpoints_card_list.append( "{0:3} {1:8.3f} {2:8.3f} {3:8.3f} {4:1} \n".format( 1, x, y, z, label)) else: bandskpoints_card_list.append( "{0:3} {1:8.3f} {2:8.3f} {3:8.3f} {4:1} \n".format( indx - savindx[rawindex - 2], x, y, z, label)) fbkpoints_card = "".join(bandskpoints_card_list) fbkpoints_card += "%endblock BandLines\n" del bandskpoints_card_list # ================================= Operations for restart ================================= # The presence of a 'parent_calc_folder' input node signals that we want to # get something from there, as indicated in the self._restart_copy_from attribute. # In Siesta's case, for now, just the density-matrix file is copied # to the current calculation's working folder. # ISSUE: Is this mechanism flexible enough? An alternative would be to # pass the information about which file(s) to copy in the metadata.options dictionary if parent_calc_folder is not None: remote_copy_list.append( (parent_calc_folder.computer.uuid, os.path.join(parent_calc_folder.get_remote_path(), self._restart_copy_from), self._restart_copy_to)) input_params.update({'dm-use-save-dm': "T"}) # ===================================== FDF file creation ==================================== # To have easy access to inputs metadata options metadataoption = self.inputs.metadata.options # input_filename = self.inputs.metadata.options.input_filename input_filename = folder.get_abs_path(metadataoption.input_filename) # Print to file with open(input_filename, 'w') as infile: # Parameters for k, v in sorted(input_params.get_filtered_items()): infile.write("%s %s\n" % (k, v)) # Basis set info is processed just like the general parameters section. if basis_dict: #It migh also be empty dict. In such case we do not write. infile.write("#\n# -- Basis Set Info follows\n#\n") for k, v in basis_dict.items(): infile.write("%s %s\n" % (k, v)) # Write previously generated cards now infile.write("#\n# -- Structural Info follows\n#\n") infile.write(atomic_species_card) infile.write(cell_parameters_card) infile.write(atomic_positions_card) if kpoints is not None: infile.write("#\n# -- K-points Info follows\n#\n") infile.write(kpoints_card) if bandskpoints is not None: infile.write("#\n# -- Bandlines/Bandpoints Info follows\n#\n") infile.write(fbkpoints_card) # Write max wall-clock time # This should prevent SiestaCalculation from being terminated by scheduler, however the # strategy is not 100% effective since SIESTA checks the simulation time versus max-walltime # only at the end of each SCF and geometry step. The scheduler might kill the process in between. infile.write("#\n# -- Max wall-clock time block\n#\n") infile.write( f"maxwalltime {metadataoption.max_wallclock_seconds}\n") # ================================== Lua parameters file =================================== if lua_parameters is not None: lua_config_filename = folder.get_abs_path("config.lua") # Generate a 'config.lua' file with Lua syntax with open(lua_config_filename, 'w') as f_lua: f_lua.write("--- Lua script parameters \n") for k, v in lua_parameters.get_dict().items(): if isinstance(v, str): f_lua.write('%s = "%s"\n' % (k, v)) else: f_lua.write("%s = %s\n" % (k, v)) # ============================= Code and Calc info ========================================= # Code information object and Calc information object are now # only used to set up the CMDLINE (the bash line that launches siesta) # and to set up the list of files to retrieve. cmdline_params = settings_dict.pop('CMDLINE', []) codeinfo = CodeInfo() codeinfo.cmdline_params = list(cmdline_params) codeinfo.stdin_name = metadataoption.input_filename codeinfo.stdout_name = metadataoption.output_filename codeinfo.code_uuid = code.uuid calcinfo = CalcInfo() calcinfo.uuid = str(self.uuid) calcinfo.local_copy_list = local_copy_list calcinfo.remote_copy_list = remote_copy_list calcinfo.codes_info = [codeinfo] # Retrieve by default: the output file, the xml file, the messages file, and the json timing file. # If bandskpoints, also the bands file is added to the retrieve list. calcinfo.retrieve_list = [] xml_file = str(metadataoption.prefix) + ".xml" bands_file = str(metadataoption.prefix) + ".bands" calcinfo.retrieve_list.append(metadataoption.output_filename) calcinfo.retrieve_list.append(xml_file) calcinfo.retrieve_list.append(self._JSON_FILE) calcinfo.retrieve_list.append(self._MESSAGES_FILE) calcinfo.retrieve_list.append(self._BASIS_ENTHALPY_FILE) calcinfo.retrieve_list.append("*.ion.xml") if bandskpoints is not None: calcinfo.retrieve_list.append(bands_file) if lua_retrieve_list is not None: calcinfo.retrieve_list += lua_retrieve_list.get_list() # If we ever want to avoid having the config.lua file in the repository, # since the information is already in the lua_parameters dictionary: # if lua_parameters is not None: # calcinfo.provenance_exclude_list = ['config.lua'] # Any other files specified in the settings dictionary settings_retrieve_list = settings_dict.pop('ADDITIONAL_RETRIEVE_LIST', []) calcinfo.retrieve_list += settings_retrieve_list return calcinfo
def _prepare_codeinfo(self): # Prepare CodeInfo object for aiida codeinfo = CodeInfo() codeinfo.code_uuid = self.inputs.code.uuid codeinfo.stdin_name = self.inputs.metadata.options.input_filename return codeinfo