def calc_aim_cubes(self, iseed, elements, ijob_dir, result_dir='cube_files', gzip=True, backup_existing=True, purge_existing=True, version=1, verbose=False): """ Function that calculations a cube file from a CASTEP run. Note that this routine does not prepare any input files, you should use "prepare_continuation_calculation()" for this purpose! Parameters ---------- ''iseed'' string Common seed for the cube calculation. ''elements'' list of strings Elements for which to calculate the Hirshfeld decomposition. ''ijob_dir'' string Working directory, i.e. the directory where the input has been prepared. Use the "prepare_continuation_calculation()" method for preparation! ''result_dir'' string (default = 'cube_files') Directory into which the output files are moved. It will always be a subdirectory of 'ijob_dir' in the sense of <ijob_dir>/<result_dir>. So do not pass absolute paths here. ''gzip'' boolean, optional (default = True) Determines whether the output files are zipped or not. ''backup_existing'' boolean, optional (default = True) Flag that is directly passed to the "mkdir()" routine of rtools. See documentation there. ''purge_existing'' boolean, optional (default = True) Flag that is directly passed to the "mkdir()" routine of rtools. See documentation there. ''version'' integer, optional (default = 1) Which version of the castep_Hirshfeld binary is used. The original JM binary is version 1, the extended binary by SPR is version 2. Version 2 does not require an additional castep2cube run and avoids output of some unnecessary files. ''verbose'' boolean, optional (default = False) Print some more information to stdout. Returns ------- None """ origin_dir = os.getcwd() ijob_dir = os.path.abspath(ijob_dir) result_dir = os.path.join(ijob_dir, result_dir) # check if all files are there... requirements = [ '{}.{}'.format(iseed, suffix) for suffix in ('cell', 'param') ] self._check_requirements(requirements, ijob_dir) # make sure there is a proper result directory mkdir(result_dir, backup_existing=backup_existing, purge_existing=purge_existing, verbose=verbose) if verbose: print('Running LDFA-AIM calculation for seed: {}'.format(iseed)) print('\tJob folder : {}'.format(ijob_dir)) print('\tResult folder : {}'.format(result_dir)) if version < 2: # Do the cube calculation first self.calc_cube( iseed=iseed, ijob_dir=ijob_dir, # result dir stuff will be done by this routine result_dir='', # we will do the gzipping afterwards gzip=False, # we already initialized init=False, verbose=verbose) # change to working directory os.chdir(ijob_dir) # link the binary only if it is not in path... castep_hirshfeld_bin = self._link_binary(self.castep_hirshfeld_bin, verbose=verbose) # run the Hirshfeld decomposition castep_hirshfeld_str = r'{0} {1}'.format(castep_hirshfeld_bin, iseed) if verbose: print('Running Hirshfeld decomposition') print('\t' + castep_hirshfeld_str) shell_stdouterr(castep_hirshfeld_str) # find unneeded elements automatically atoms = read_cell(iseed + '.cell') delete_elements = set( [a.symbol for a in atoms if a.symbol not in elements]) delete_pattern = '{}'.format('|'.join(list(delete_elements))) # deleting unnecessary output if verbose: print('Removing unnecessary output files:') for f in os.listdir('.'): pattern = r'Hirshfeld_rho_ba.*' + format(delete_pattern) if re.search(pattern, f) or re.search(r'Hirshfeld_w-', f) or re.search(r'\.err', f): if verbose: print('\t{}'.format(f)) os.remove(f) # renaming remaining output files if verbose: print('Renaming remaining output files (just for convenience):') for e in elements: for f in os.listdir('.'): # we want to get rid of the 'ns' flag pattern = r'(.*)(-Hirshfeld_rho_ba)-ns_[0-9]+(.*)' search_obj = re.search(pattern, f) if search_obj: old = search_obj.group() new = ''.join(search_obj.groups()) if verbose: print('\t{} --> {}'.format(old, new)) os.rename(old, new) # get the difference density cube file cube_subtract_bin = self._link_binary(self.cube_subtract_bin, verbose=verbose) if verbose: print('Calculating density differences') r = re.compile(r'.*-chargeden\.cube') interacting_density = filter(r.match, os.listdir('.'))[-1] r = re.compile(r'.*Hirshfeld_rho_ba.*') hirshfeld_densities = filter(r.match, os.listdir('.')) for hirsh in hirshfeld_densities: cube_subtract_str = r'{0} {1} {2}'.format(cube_subtract_bin, interacting_density, hirsh) if verbose: print('\t' + cube_subtract_str) shell_stdouterr(cube_subtract_str) # get the species identifyer pattern = r'.*-Hirshfeld_rho_ba-(.*)' ispec = re.match(pattern, hirsh).groups()[-1] # rename the resulting file os.rename(interacting_density + '-minus-' + hirsh, iseed + '-{}-'.format(self._prefix) + ispec) if gzip: if verbose: print('Gzipping results') for f in os.listdir('.'): if re.search(r'\.cube.*', f): gzip_file(f) if verbose: print('\t' + f) if not os.path.samefile(ijob_dir, result_dir): if verbose: print('Moving results to resultfolder') for f in os.listdir('.'): if re.search(r'\.cube.*', f): shutil.move(f, result_dir) # change back to originfolder os.chdir(origin_dir)
def _prepare( self, task, var, # just a dummy, no longe required with latest ase pspot_suffix='OTF', ): """ Function that wraps all necessary tasks to run several calculations. Does not actually run the calculation. Arguments --------- ''task'' string Actual task to carry out. Will be filtered via `_normalize_task()`. Currently available since implemented are * 'kpoints' * 'cutoff' * 'vacuum' / 'vacuumwithadsorbate' ''var'' str/generic Values that the varied parameter should correspond to. For `cutoff` and `vacuum`, these may be floats or integers, for `kpoints` we require strings like 'X Y Z', which are directly passed to the `KPOINTS_MP_GRID` variable in the `*.cell` file. ''pspot_suffix'' string, optional (default = 'OTF') Usually CASTEP USPP are named like <Elem>_<pspot_suffix>.usp. This suffix ensures to correctly create the cell file. Returns ------- ''atoms'' readily prepared atoms object. ''iseed'' The iseed of the prepared calculation ''idir'' The idir of the prepared calculation. """ # get the indivial variables iseed = self.get_iseed(var=var, task=task) idir = self.get_idir(var=var, task=task) # create folder if necessary # if results exist: skip! if mkdir(idir, backup_existing=False, purge_existing=False, verbose=False): pass else: return [None] * 3 # get the calculator and set label and dir calc = self.get_calc() calc._label = iseed calc._directory = idir # get the atoms atoms = self.get_atoms() if task == 'kpoints': # set the k point mesh calc.cell.kpoints_mp_grid = var elif task == 'kpointspacing': calc.cell.kpoints_mp_spacing = float(var) elif task == 'cutoff': # set the cutoff calc.cut_off_energy = int(var) elif task == 'vacuum': # resize the separating distance atoms.center(vacuum=var / 2., axis=2) elif task == 'vacuumwithadsorbate': atoms = self._get_slab() atoms.center(vacuum=var / 2., axis=2) self._add_adsorbate(atoms) # set the calculator atoms.set_calculator(calc) # we need it hard code it here... # calc.set_pspot(pspot = pspot_suffix) atoms.calc.prepare_input_files() return atoms, iseed, idir
def calc_cube(self, iseed, ijob_dir, result_dir = 'cube_files', gzip = True, backup_existing = True, purge_existing = True, init = True, verbose = False): """ Function that calculations a cube file from a CASTEP run. Note that this routine does not prepare any input files, you should use "prepare_continuation_calculation()" for this purpose! Parameters ---------- ''iseed'' string Common seed for the cube calculation. ''ijob_dir'' string Working directory, i.e. the directory where the input has been prepared. Use the "prepare_continuation_calculation()" method for preparation! ''result_dir'' string (default = 'cube_files') Directory into which the output files are moved. It will always be a subdirectory of 'ijob_dir' in the sense of <ijob_dir>/<result_dir>. So do not pass absolute paths here. ''gzip'' boolean, optional (default = True) Determines whether the output files are zipped or not. ''backup_existing'' boolean, optional (default = True) Flag that is directly passed to the "mkdir()" routine of rtools. See documentation there. ''purge_existing'' boolean, optional (default = True) Flag that is directly passed to the "mkdir()" routine of rtools. See documentation there. ''init'' boolean, optional (default = True) Check for requirements and create directories if necessary. Turn of, if you want to avoid double checking when using this routine in other routines. ''verbose'' boolean, optional (default = False) Print some more information to stdout. Returns ------- None """ origin_dir = os.getcwd() ijob_dir = os.path.abspath(ijob_dir) result_dir = os.path.join(ijob_dir, result_dir) if init: # check if all files are there... requirements = ['{}.{}'.format(iseed, suffix) for suffix in ('cell','param')] self._check_requirements(requirements, ijob_dir) # make sure there is a proper result directory mkdir(result_dir, backup_existing = backup_existing, purge_existing = purge_existing, verbose = verbose) if verbose: print('Running cube calculation for seed: {}'.format(iseed)) print('\tJob folder : {}'.format(ijob_dir)) print('\tResult folder : {}'.format(result_dir)) # change to working directory os.chdir(ijob_dir) castep2cube_bin = self._link_binary(self.castep2cube_bin, verbose = verbose) castep2cube_str = r'{0} {1}'.format(castep2cube_bin, iseed) if verbose: print('Running castep2cube:') print('\t' + castep2cube_str) shell_stdouterr(castep2cube_str) # rename output os.rename(iseed+'.chargeden_cube', iseed+'-chargeden.cube') # remove all unnecessary files if verbose: print('Removing unnecessary output files:') for f in os.listdir('.'): if re.search(r'_xsf_|_esp_|chdiff_cube|\.err', f): if verbose: print('\t{}'.format(f)) os.remove(f) outfile = iseed+'-chargeden.cube' if gzip: if verbose: print('Gzipping results') gzip_file(outfile) outfile += '.gz' if not os.path.samefile(ijob_dir, result_dir): if verbose: print('Moving results to resultfolder') shutil.move(outfile, result_dir) os.chdir(origin_dir)
def _calculate(self, points, pspot_suffix='OTF', submit_func=None, force_resubmit_empty=False, force_resubmit_all=False, verbose=False, try_reuse_previous=False, **kwargs): """ Function that wraps all necessary tasks to run several calculations. This function is generic in the sense, that the actual submit function may be changed. Arguments --------- ''submit_func'' : rtools submit agent function Function that does all the PBS related communication. See the submitagents provided with rtools for more details. ''points'' list List of values that the varied parameter should correspond to. ''pspot_suffix'' string, optional (default = 'OTF') Usually CASTEP USPP are named like <Elem>_<pspot_suffix>.usp. This suffix ensures to correctly create the cell file. ''force_resubmit_empty'' boolean, optional (default = False) Resubmit if the results folder is empty. Be careful, your job may still be running. ''force_resubmit_all'' boolean, optional (default = False) Resubmit regardless of whether there are any existing files or folders. ''verbose'' boolean, optional (default=False) Print "Job X / Y" before submitting/calculating. ''try_reuse_previous'' boolean, optional (default=False) Try to fetch the check file of the previous run and use it to speed up the next simulation thanks to density extrapolation. Note that this option should *never* be used togehter with a cluster and thus distributed computations. ''**kwargs'' are directly passed to the `submit_func()` function. Returns ------- None """ njobs = len(points) nsubmitted = 0 nskipped = 0 nprocessed = 0 print(self._lim) print('Requested {} jobs in total'.format(njobs)) print(self._lim) _prev_point = None for point in points: if verbose: info = '| Job {{:{0}d}} / {{:{0}d}} |'.format(len( str(njobs))).format(nprocessed + 1, njobs) lim = '+' + '-' * (len(info) - 2) + '+' print(lim) print(info) print(lim) # get the indivial variables iseed = self.get_iseed(point) idir = self.get_idir(point) idir_export = self.get_idir_export(point) # create folder if necessary if mkdir(idir, backup_existing=False, purge_existing=False, verbose=False): pass # "results" is hard coded, see below elif os.path.exists(os.path.join(idir, 'results')) and os.listdir( os.path.join(idir, 'results')) and force_resubmit_empty: print( 'Resubmitting job "{}" due to empty result folder. Existing files are not backed up.' .format(iseed)) pass elif not force_resubmit_all: # folder already exists... print('Skipping job "{}" due to existing files'.format(iseed)) nskipped += 1 nprocessed += 1 _prev_point = point continue # get the calculator and set label and dir calc = self.get_calc() calc._label = iseed calc._directory = idir # get the atoms atoms = self.get_atoms(point) # set the calculator atoms.set_calculator(calc) # we need it hard coded here... atoms.calc.set_pspot(pspot=pspot_suffix) if nprocessed > 0 and try_reuse_previous: _prev_iseed = self.get_iseed(_prev_point) _prev_idir = self.get_idir(_prev_point) _prev_idir_export = self.get_idir_export(_prev_point) # try to fetch a check file (either on /data or on the /export # partition; we do not know as this is determined by the submit # function) locations = [ os.path.join(_prev_idir, 'results', _prev_iseed + '.check'), os.path.join(_prev_idir_export, _prev_iseed + '.check'), ] _prev_icheck = None for f in locations: if os.path.exists(f): _prev_icheck = f break if _prev_icheck: if verbose: print( 'Info: Fetched previous checkfile -- will be reused' .format(_prev_icheck)) # Symlink the check file (makes life easier...) os.symlink(_prev_icheck, os.path.join(idir, _prev_iseed + '.check')) atoms.calc.param.reuse = _prev_iseed + '.check' else: if verbose: print( 'Info: Unable to fetch previous checkfile -- start from scratch' ) # prepare input files and submit atoms.calc.prepare_input_files() # make sure that user does not override the result default result_dir = kwargs.pop('result_dir', None) if result_dir: print( 'Argument "result_dir" was set to "{}"'.format(result_dir)) print( 'Will be changed to "<job_dir>/results" to maintain compatibility with reading routines' ) submit_func(seed=iseed, job_dir=idir, result_dir='results', export_dir=idir_export, **kwargs) nprocessed += 1 nsubmitted += 1 # this is for the reuse feature _prev_point = point print(self._lim) print('Submitted : {0:>4d} / {1:d} jobs'.format(nsubmitted, njobs)) print('Skipped : {0:>4d} / {1:d} jobs'.format(nskipped, njobs)) print(self._lim)
def write_ascii(self, node='PES', observable='energy_normalized', fname='PES.dat', comment='no unit info provided', include_points=True, **kwargs): """ Routine to actually dump things from the database to a clear-text ascii file. Parameters ---------- ''node'' string, optional (default = 'PES') The node that is to be considered. It will be '/analysis/<node>', as we do not want raw data dumping to clear text. This is what the HDF5 database is for. ''observable'' string/list of strings, optional (default = 'energy_normalized') The column in the dataframe that is printed next to the respective coordinates. Can also be a list of strings for several output quantities. ''fname'' string, optional (default = 'PES.dat') The filename to be written to. You can specify an entire path as well, folders will be created on demand. ''comment'' string, optional (default = 'no unit info provided') Some string that comments on the data in the file. This is up to the user. ''include_points'' boolean, optional (default=True) Include the point identifyer, even if not specified as observable (fallback mode) **kwargs Passed to the dataframe.to_string() method. Allows more finegrained control on the format. Returns ------- <None> """ # check if we need to create the directory dirname = os.path.dirname(fname) if dirname: mkdir(dirname, backup_existing = False, purge_existing = False, verbose = False) df = self.store['/analysis/{}'.format(node)] if not isinstance(observable, list): observable = [observable] if include_points: idx = self.point_names + observable else: idx = observable print('Writing data to ascii file : {}'.format(fname)) print('\tObservable(s):\n\t* {}'.format('\n\t* '.join(observable))) with open(fname, 'w') as f: f.write('# {}'.format(f.name)) f.write('\n# file written on: {}'.format(time.strftime('%c'))) if comment: f.write('\n#') comment = comment.split('\n') for c in comment: for line in textwrap.wrap(c, width=78): f.write('\n# {}'.format(line)) data='#' + df[idx].to_string(index=False, **kwargs)[1::] f.write('\n#\n#\n{}'.format(data))
def prepare_continuation_calculation(self, DFT_idir, ijob_dir, iseed=None, DFT_iseed=None, backup_existing=True, verbose=False): """ Function that prepares a CASTEP continuation calculation. The function creates a <iseed>.cell and <iseed>.param file in <ijob_dir> from a given SCF calculation in <DFT_idir> using the ase CASTEP calculator. The latter will be appended a "continuation : <checkfile>" and possibly existing "reuse" statements are remove due to conflicting options. Parameters ---------- ''DFTdir'' string Path to the output of the CASTEP SCF calculation (*including in particular the .check file*), on which the continuation calcultion shall be based on. ''ijob_dir'' string Path where to prepare the continuation calculation. ''iseed'' string, optional (default = None) Seed for the new files in 'ijob_dir'. If None, the iseed from the DFT calculations will be re-used. ''DFT_iseed'' string (default = None) CASTEP iseed. If not specified, the routine will glob using a wildcard. In this case, you should make sure that there is actually only one .cell, .param, and .check file in 'DFT_idir'. ''backup_existing'' boolean, optional (default = True) Flag that is directly passed to the "mkdir()" routine of rtools. Note that "purge_existing" is always <True>. *This means that your files will be irrevesibly gone if you do not set "backup_existing = True". ''verbose'' boolean, optional (default = False) Print some more information to stdout. Returns ------- None """ # make sure we have absolute path names ijob_dir = os.path.abspath(ijob_dir) DFT_idir = os.path.abspath(DFT_idir) # holds all DFT names DFT_info = self._get_DFT_infos(DFT_idir=DFT_idir, DFT_iseed=DFT_iseed) # use the same iseed as the DFT calculation if none is given if iseed == None: iseed = DFT_info['iseed'] if verbose: print('Preparing calculation for seed: {}'.format(iseed)) print('\tSource : {}'.format(DFT_idir)) print('\tJob folder : {}'.format(ijob_dir)) # create the folder if not already there mkdir(ijob_dir, backup_existing=backup_existing, purge_existing=True, verbose=verbose) self._prepare_castep_files(DFT_info, iseed=iseed, ijob_dir=ijob_dir, verbose=verbose) return None