def prepare_for_submission(self, folder): """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 """ if 'settings' in self.inputs: settings = _uppercase_dict(self.inputs.settings.get_dict(), dict_name='settings') else: settings = {} following_text = self._get_following_text() # Put the first-level keys as uppercase (i.e., namelist and card names) and the second-level keys as lowercase if 'parameters' in self.inputs: parameters = _uppercase_dict(self.inputs.parameters.get_dict(), dict_name='parameters') parameters = { k: _lowercase_dict(v, dict_name=k) for k, v in six.iteritems(parameters) } else: parameters = {} # Force default values for blocked keywords. NOTE: this is different from PW/CP for blocked in self._blocked_keywords: namelist = blocked[0].upper() key = blocked[1].lower() value = blocked[2] if namelist in parameters: if key in parameters[namelist]: raise exceptions.InputValidationError( "You cannot specify explicitly the '{}' key in the '{}' " 'namelist.'.format(key, namelist)) else: parameters[namelist] = {} parameters[namelist][key] = value # =================== NAMELISTS AND CARDS ======================== try: namelists_toprint = settings.pop('NAMELISTS') if not isinstance(namelists_toprint, list): raise exceptions.InputValidationError( "The 'NAMELISTS' value, if specified in the settings input node, must be a list of strings" ) except KeyError: # list of namelists not specified; do automatic detection namelists_toprint = self._default_namelists input_filename = self.inputs.metadata.options.input_filename with folder.open(input_filename, 'w') as infile: for namelist_name in namelists_toprint: infile.write(u'&{0}\n'.format(namelist_name)) # namelist content; set to {} if not present, so that we leave an empty namelist namelist = parameters.pop(namelist_name, {}) for key, value in sorted(six.iteritems(namelist)): infile.write(convert_input_to_namelist_entry(key, value)) infile.write(u'/\n') # Write remaning text now, if any infile.write(following_text) # Check for specified namelists that are not expected if parameters: raise exceptions.InputValidationError( 'The following namelists are specified in parameters, but are ' 'not valid namelists for the current type of calculation: ' '{}'.format(','.join(list(parameters.keys())))) remote_copy_list = [] local_copy_list = [] # copy remote output dir, if specified parent_calc_folder = self.inputs.get('parent_folder', None) if parent_calc_folder is not None: if isinstance(parent_calc_folder, RemoteData): parent_calc_out_subfolder = settings.pop( 'PARENT_CALC_OUT_SUBFOLDER', self._INPUT_SUBFOLDER) remote_copy_list.append( (parent_calc_folder.computer.uuid, os.path.join(parent_calc_folder.get_remote_path(), parent_calc_out_subfolder), self._OUTPUT_SUBFOLDER)) elif isinstance(parent_calc_folder, FolderData): # TODO: test me, especially with deep relative paths. for filename in parent_calc_folder.list_object_names(): local_copy_list.append( (parent_calc_folder.uuid, filename, os.path.join(self._OUTPUT_SUBFOLDER, filename))) elif isinstance(parent_calc_folder, SinglefileData): # TODO: test me single_file = parent_calc_folder local_copy_list.append((single_file.uuid, single_file.filename, single_file.filename)) codeinfo = datastructures.CodeInfo() codeinfo.cmdline_params = settings.pop('CMDLINE', []) codeinfo.stdin_name = self.inputs.metadata.options.input_filename codeinfo.stdout_name = self.inputs.metadata.options.output_filename codeinfo.code_uuid = self.inputs.code.uuid calcinfo = datastructures.CalcInfo() calcinfo.uuid = str(self.uuid) calcinfo.codes_info = [codeinfo] calcinfo.local_copy_list = local_copy_list calcinfo.remote_copy_list = remote_copy_list # Retrieve by default the output file and the xml file calcinfo.retrieve_list = [] calcinfo.retrieve_list.append( self.inputs.metadata.options.output_filename) settings_retrieve_list = settings.pop('ADDITIONAL_RETRIEVE_LIST', []) calcinfo.retrieve_list += settings_retrieve_list calcinfo.retrieve_list += self._internal_retrieve_list calcinfo.retrieve_singlefile_list = self._retrieve_singlefile_list # We might still have parser options in the settings dictionary: pop them. _pop_parser_options(self, settings) if settings: unknown_keys = ', '.join(list(settings.keys())) raise exceptions.InputValidationError( '`settings` contained unexpected keys: {}'.format( unknown_keys)) return calcinfo
def prepare_for_submission(self, folder): """Prepare the calculation job for submission by transforming input nodes into input files. In addition to the input files being written to the sandbox folder, a `CalcInfo` instance will be returned that contains lists of files that need to be copied to the remote machine before job submission, as well as file lists that are to be retrieved after job completion. :param folder: a sandbox folder to temporarily write files on disk. :return: :py:`~aiida.common.datastructures.CalcInfo` instance. """ # pylint: disable=too-many-branches,too-many-statements import numpy as np local_copy_list = [] remote_copy_list = [] remote_symlink_list = [] # Convert settings dictionary to have uppercase keys, or create an empty one if none was given. if 'settings' in self.inputs: settings_dict = _uppercase_dict(self.inputs.settings.get_dict(), dict_name='settings') else: settings_dict = {} first_structure = self.inputs.first_structure last_structure = self.inputs.last_structure # Check that the first and last image have the same cell if abs(np.array(first_structure.cell) - np.array(last_structure.cell)).max() > 1.e-4: raise InputValidationError('Different cell in the fist and last image') # Check that the first and last image have the same number of sites if len(first_structure.sites) != len(last_structure.sites): raise InputValidationError('Different number of sites in the fist and last image') # Check that sites in the initial and final structure have the same kinds if first_structure.get_site_kindnames() != last_structure.get_site_kindnames(): raise InputValidationError( 'Mismatch between the kind names and/or order between ' 'the first and final image' ) # Check that a pseudo potential was specified for each kind present in the `StructureData` # self.inputs.pw.pseudos is a plumpy.utils.AttributesFrozendict kindnames = [kind.name for kind in first_structure.kinds] if set(kindnames) != set(self.inputs.pw.pseudos.keys()): raise InputValidationError( 'Mismatch between the defined pseudos and the list of kinds of the structure.\nPseudos: {};\n' 'Kinds: {}'.format(', '.join(list(self.inputs.pw.pseudos.keys())), ', '.join(list(kindnames))) ) ############################## # END OF INITIAL INPUT CHECK # ############################## # Create the subfolder that will contain the pseudopotentials folder.get_subfolder(self._PSEUDO_SUBFOLDER, create=True) # Create the subfolder for the output data (sometimes Quantum ESPRESSO codes crash if the folder does not exist) folder.get_subfolder(self._OUTPUT_SUBFOLDER, create=True) # We first prepare the NEB-specific input file. neb_input_filecontent = self._generate_input_files(self.inputs.parameters, settings_dict) with folder.open(self.inputs.metadata.options.input_filename, 'w') as handle: handle.write(neb_input_filecontent) # We now generate the PW input files for each input structure local_copy_pseudo_list = [] for i, structure in enumerate([first_structure, last_structure]): # We need to a pass a copy of the settings_dict for each structure this_settings_dict = copy.deepcopy(settings_dict) pw_input_filecontent, this_local_copy_pseudo_list = PwCalculation._generate_PWCPinputdata( # pylint: disable=protected-access self.inputs.pw.parameters, this_settings_dict, self.inputs.pw.pseudos, structure, self.inputs.pw.kpoints ) local_copy_pseudo_list += this_local_copy_pseudo_list with folder.open(f'pw_{i + 1}.in', 'w') as handle: handle.write(pw_input_filecontent) # We need to pop the settings that were used in the PW calculations for key in list(settings_dict.keys()): if key not in list(this_settings_dict.keys()): settings_dict.pop(key) # We avoid to copy twice the same pseudopotential to the same filename local_copy_pseudo_list = set(local_copy_pseudo_list) # We check that two different pseudopotentials are not copied # with the same name (otherwise the first is overwritten) if len({filename for (uuid, filename, local_path) in local_copy_pseudo_list}) < len(local_copy_pseudo_list): raise InputValidationError('Same filename for two different pseudopotentials') local_copy_list += local_copy_pseudo_list # If present, add also the Van der Waals table to the pseudo dir. Note that the name of the table is not checked # but should be the one expected by Quantum ESPRESSO. vdw_table = self.inputs.get('pw.vdw_table', None) if vdw_table: local_copy_list.append( (vdw_table.uuid, vdw_table.filename, os.path.join(self._PSEUDO_SUBFOLDER, vdw_table.filename)) ) # operations for restart parent_calc_folder = self.inputs.get('parent_folder', None) symlink = settings_dict.pop('PARENT_FOLDER_SYMLINK', self._default_symlink_usage) # a boolean if symlink: if parent_calc_folder is not None: # I put the symlink to the old parent ./out folder remote_symlink_list.append(( parent_calc_folder.computer.uuid, os.path.join(parent_calc_folder.get_remote_path(), self._OUTPUT_SUBFOLDER, '*'), # asterisk: make individual symlinks for each file self._OUTPUT_SUBFOLDER )) # and to the old parent prefix.path remote_symlink_list.append(( parent_calc_folder.computer.uuid, os.path.join(parent_calc_folder.get_remote_path(), f'{self._PREFIX}.path'), f'{self._PREFIX}.path' )) else: # copy remote output dir and .path file, if specified 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._OUTPUT_SUBFOLDER, '*'), self._OUTPUT_SUBFOLDER )) # and copy the old parent prefix.path remote_copy_list.append(( parent_calc_folder.computer.uuid, os.path.join(parent_calc_folder.get_remote_path(), f'{self._PREFIX}.path'), f'{self._PREFIX}.path' )) # here we may create an aiida.EXIT file create_exit_file = settings_dict.pop('ONLY_INITIALIZATION', False) if create_exit_file: exit_filename = f'{self._PREFIX}.EXIT' with folder.open(exit_filename, 'w') as handle: handle.write('\n') calcinfo = CalcInfo() codeinfo = CodeInfo() calcinfo.uuid = self.uuid cmdline_params = settings_dict.pop('CMDLINE', []) calcinfo.local_copy_list = local_copy_list calcinfo.remote_copy_list = remote_copy_list calcinfo.remote_symlink_list = remote_symlink_list # In neb calculations there is no input read from standard input!! codeinfo.cmdline_params = (['-input_images', '2'] + list(cmdline_params)) codeinfo.stdout_name = self.inputs.metadata.options.output_filename codeinfo.code_uuid = self.inputs.code.uuid calcinfo.codes_info = [codeinfo] # Retrieve the output files and the xml files calcinfo.retrieve_list = [] calcinfo.retrieve_list.append(self.inputs.metadata.options.output_filename) calcinfo.retrieve_list.append(( os.path.join(self._OUTPUT_SUBFOLDER, self._PREFIX + '_*[0-9]', 'PW.out'), # source relative path (globbing) '.', # destination relative path 2 # depth to preserve )) for xml_filepath in self.xml_filepaths: # pylint: disable=not-an-iterable calcinfo.retrieve_list.append([xml_filepath, '.', 3]) calcinfo.retrieve_list += settings_dict.pop('ADDITIONAL_RETRIEVE_LIST', []) calcinfo.retrieve_list += self._internal_retrieve_list # We might still have parser options in the settings dictionary: pop them. _pop_parser_options(self, settings_dict) if settings_dict: unknown_keys = ', '.join(list(settings_dict.keys())) raise InputValidationError(f'`settings` contained unexpected keys: {unknown_keys}') return calcinfo
def prepare_for_submission(self, folder): """Prepare the calculation job for submission by transforming input nodes into input files. In addition to the input files being written to the sandbox folder, a `CalcInfo` instance will be returned that contains lists of files that need to be copied to the remote machine before job submission, as well as file lists that are to be retrieved after job completion. :param folder: a sandbox folder to temporarily write files on disk. :return: :py:`~aiida.common.datastructures.CalcInfo` instance. """ # pylint: disable=too-many-branches,too-many-statements if 'settings' in self.inputs: settings = _uppercase_dict(self.inputs.settings.get_dict(), dict_name='settings') else: settings = {} following_text = self._get_following_text() # Put the first-level keys as uppercase (i.e., namelist and card names) and the second-level keys as lowercase if 'parameters' in self.inputs: parameters = _uppercase_dict(self.inputs.parameters.get_dict(), dict_name='parameters') parameters = { k: _lowercase_dict(v, dict_name=k) for k, v in parameters.items() } else: parameters = {} # =================== NAMELISTS AND CARDS ======================== try: namelists_toprint = settings.pop('NAMELISTS') if not isinstance(namelists_toprint, list): raise exceptions.InputValidationError( "The 'NAMELISTS' value, if specified in the settings input node, must be a list of strings" ) except KeyError: # list of namelists not specified; do automatic detection namelists_toprint = self._default_namelists parameters = self.set_blocked_keywords(parameters) parameters = self.filter_namelists(parameters, namelists_toprint) file_content = self.generate_input_file(parameters) file_content += '\n' + following_text input_filename = self.inputs.metadata.options.input_filename with folder.open(input_filename, 'w') as infile: infile.write(file_content) symlink = settings.pop('PARENT_FOLDER_SYMLINK', False) remote_copy_list = [] local_copy_list = [] remote_symlink_list = [] ptr = remote_symlink_list if symlink else remote_copy_list # copy remote output dir, if specified parent_calc_folder = self.inputs.get('parent_folder', None) if parent_calc_folder is not None: if isinstance(parent_calc_folder, RemoteData): parent_calc_out_subfolder = settings.pop( 'PARENT_CALC_OUT_SUBFOLDER', self._INPUT_SUBFOLDER) ptr.append((parent_calc_folder.computer.uuid, os.path.join(parent_calc_folder.get_remote_path(), parent_calc_out_subfolder), self._OUTPUT_SUBFOLDER)) elif isinstance(parent_calc_folder, FolderData): for filename in parent_calc_folder.list_object_names(): local_copy_list.append( (parent_calc_folder.uuid, filename, os.path.join(self._OUTPUT_SUBFOLDER, filename))) elif isinstance(parent_calc_folder, SinglefileData): single_file = parent_calc_folder local_copy_list.append((single_file.uuid, single_file.filename, single_file.filename)) codeinfo = datastructures.CodeInfo() codeinfo.cmdline_params = settings.pop('CMDLINE', []) codeinfo.stdin_name = self.inputs.metadata.options.input_filename codeinfo.stdout_name = self.inputs.metadata.options.output_filename codeinfo.code_uuid = self.inputs.code.uuid calcinfo = datastructures.CalcInfo() calcinfo.uuid = str(self.uuid) calcinfo.codes_info = [codeinfo] calcinfo.local_copy_list = local_copy_list calcinfo.remote_copy_list = remote_copy_list calcinfo.remote_symlink_list = remote_symlink_list # Retrieve by default the output file and the xml file calcinfo.retrieve_list = [] calcinfo.retrieve_list.append( self.inputs.metadata.options.output_filename) settings_retrieve_list = settings.pop('ADDITIONAL_RETRIEVE_LIST', []) calcinfo.retrieve_list += settings_retrieve_list calcinfo.retrieve_list += self._internal_retrieve_list calcinfo.retrieve_singlefile_list = self._retrieve_singlefile_list # We might still have parser options in the settings dictionary: pop them. _pop_parser_options(self, settings) if settings: unknown_keys = ', '.join(list(settings.keys())) raise exceptions.InputValidationError( f'`settings` contained unexpected keys: {unknown_keys}') return calcinfo