Example #1
0
    def __init__(self) -> None:
        ''' A class to analyze the results of calculations '''

        input_file = 'input.json'
        try:
            with open(input_file, 'r') as f:
                input_json = json.load(f)

            surface_types_and_repeats = input_json['surface_types_and_repeats']
            metal_atom = input_json['metal_atom']
            self.yamlfile = input_json['yamlfile']
            self.facetpaths = IO().get_facetpaths(
                metal_atom, surface_types_and_repeats.keys())
            self.reactions = IO().open_yaml_file(self.yamlfile)
            self.ev_to_kjmol = 23.06035 * 4.184
            self.slab_paths = [
                '{}_big_slab_opt.xyz'.format(facetpath)
                for facetpath in self.facetpaths
            ]
            self.current_dir = os.getcwd()

        except FileNotFoundError:
            print('!    input.json not found. \n'
                  '     Make sure {} matches exactly "input.json"\n'
                  '\n'
                  '     You can use this module only from your main working \n'
                  '     directory - the one with all your input files and \n'
                  '     "facetpath" dir and job_files dir.\n'
                  '\n'
                  '     Your current directory is:\n'
                  '     {}'.format(input_file, os.getcwd()))
            sys.exit()
Example #2
0
    def get_barrier_all(self) -> Dict[str, Dict[str, str]]:
        ''' Get barrier heights for all rxn_names and facetpaths

        Returns
        -------
        ts_ener : Dict[str,Dict[str,str]]
            a dictionary with all barrier heights (kj/mol)
            in a format like below

            >>> ts_ener = {'Cu_111_OH_O+H':
                    {'TS_00': '155.27', 'TS_01': '157.97'}}

        '''
        ts_ener = {}
        for facetpath, slab_path in zip(self.facetpaths, self.slab_paths):
            minima_path = os.path.join(facetpath, 'minima')
            for rxn in self.reactions:
                r_name_list, p_name_list = IO.get_reactants_and_products(rxn)
                rxn_name = IO.get_rxn_name(rxn)
                ts_path = os.path.join(self.current_dir, facetpath, rxn_name,
                                       'TS_estimate_unique')
                ts_ener[facetpath + '_' + rxn_name] = self.get_barrier(
                    minima_path, ts_path, facetpath, r_name_list, p_name_list,
                    slab_path)
        return ts_ener
Example #3
0
File: ts.py Project: zadorlab/pynta
    def prepare_ts_estimate(self, rxn: Dict[str, str], scfactor: float,
                            scfactor_surface: float, pytemplate_xtb: str,
                            species_list: List[str],
                            reacting_atoms: Dict[str, int], metal_atom: str,
                            scaled1: bool, scaled2: bool) -> None:
        ''' Prepare TS estimates for subsequent xTB calculations

        Parameters
        ___________

        rxn : dict(yaml[str:str])
            a dictionary with info about the paricular reaction. This can be
            view as a splitted many reaction .yaml file to a single reaction
            .yaml file
        scfator : float
            a scaling factor to scale a bond distance between
            atoms taking part in the reaction
            e.g. 1.4
        scfactor_surface : float
            a scaling factor to scale the target bond distance, i.e.
            the average distance between adsorbed atom and the nearest
            surface atom. Helpful e.g. when H is far away form the surface
            in TS, whereas for minima it is close to the surface
            e.g. 1.0
        pytemplate_xtb : python script
            a template file for penalty function minimization job
        species_list : List[str]
            a list of species which atoms take part in the reaction,
            i.e. for ['CO2'] ['C'] is taking part in reaction
            e.g. ['O', 'H'] or ['CO2', 'H']
        reacting_atoms : Dict[str, int]
            keys are sybols of atoms that takes part in reaction whereas,
            values are their indicies
        metal_atom : str
            a checmical symbol for the surface atoms (only metallic surfaces
            are allowed)
        scaled1 : bool
            specify whether use the optional scfactor_surface
            for the species 1 (sp1)
        scaled2 : bool
            specify whether use the optional scfactor_surface
            for the species 2 (sp2)

        '''
        r_name_list, p_name_list = IO.get_reactants_and_products(rxn)
        rxn_name = IO.get_rxn_name(rxn)

        ts_estimate_path = os.path.join(self.creation_dir, self.facetpath,
                                        rxn_name, self.ts_estimate_dir)

        self.TS_placer(ts_estimate_path, scfactor, rxn, rxn_name, r_name_list,
                       p_name_list, reacting_atoms)

        self.filtered_out_equiv_ts_estimates(ts_estimate_path, rxn_name)

        self.set_up_penalty_xtb(ts_estimate_path, pytemplate_xtb, species_list,
                                reacting_atoms, metal_atom, scaled1,
                                scfactor_surface)
Example #4
0
    def adjacency_to_3d(self) -> None:
        ''' Place adsorbates on the surface

        .. todo:: Add support for a bidentate adsorption

        '''
        all_species_symbols = IO.get_all_unique_species_symbols(self.yamlfile)
        all_images_with_bonds = IO.get_all_unique_images_with_bonds(
            self.yamlfile)

        # prepare surface for placing adsorbates
        grslab = self.get_grslab()
        ads_builder = Builder(grslab)

        # build adsorbates
        structures = dict()
        for sp_symbol, unique_images_with_bonds in \
                zip(all_species_symbols, all_images_with_bonds.values()):
            for bond, sp_gratoms in unique_images_with_bonds.items():

                if len(sp_gratoms) == 0:
                    continue

                # which atom connects to the surface
                bonded = [bond]

                try:
                    # put adsorbates on the surface
                    structs = ads_builder.add_adsorbate(sp_gratoms,
                                                        index=-1,
                                                        bonds=bonded)
                    structures[str(sp_symbol)] = structs
                except IndexError:
                    print('sp_gratoms, sp_gratoms.edges, sp.gratoms.tags')
                    print(sp_gratoms, sp_gratoms.edges, sp_gratoms.get_tags())

        for sp_symbol, adsorbate in structures.items():
            # create directory where all adsorbates are stored
            savedir = os.path.join(self.creation_dir, self.facetpath, 'minima',
                                   sp_symbol)
            os.makedirs(savedir, exist_ok=True)

            for prefix, structure in enumerate(adsorbate):
                big_slab_ads = self.big_slab + structure[self.nslab:]
                # save adsorbates as .xyz and .png
                write(
                    os.path.join(savedir,
                                 '{}.xyz'.format(str(prefix).zfill(2))),
                    big_slab_ads)
                write(
                    os.path.join(savedir,
                                 '{}.png'.format(str(prefix).zfill(2))),
                    big_slab_ads)
Example #5
0
    def get_ts_out_files(ts_path: str) -> List[str]:
        ''' Get TS :literal:`*.out` files

        Parameters
        ts_path : str
            a path to main TS directory, e.g.

            >>> ts_path = 'Cu_111/TS_estimate_unique'

        Returns
        -------
        ts_out_file_list : List[str]
            a sorted list with paths to Sella's .out files for each TSs

        '''
        ts_out_file_list = []
        unique_ts_prefixes = IO.get_unique_final_ts_prefixes(ts_path)

        try:
            for outfile in os.listdir(ts_path):
                for uq_ts_prefix in unique_ts_prefixes:
                    if outfile.startswith(uq_ts_prefix) and outfile.endswith(
                            'out'):
                        uq_ts_outfile_path = os.path.join(ts_path, outfile)
                        ts_out_file_list.append(str(uq_ts_outfile_path))

        except FileNotFoundError:
            pass

        return sorted(ts_out_file_list)
Example #6
0
    def get_all_distances(self) -> None:
        ''' Get distances between reacting species for ts, forward and
        reverse structure.

        '''
        all_rxn_names = IO().get_list_all_rxns_names(self.yamlfile)
        for rxn_name in all_rxn_names:
            ts_dist_dict = self.get_ts_dist(rxn_name)
            f_dist_dict, r_dist_dict = self.get_forward_and_reverse_dist(
                rxn_name)
            AfterTS.print_table(ts_dist_dict, f_dist_dict, r_dist_dict)
Example #7
0
    def plot(self,
             plot_filename: str = None,
             max_barrier: float = None,
             x_size_in: float = 8,
             y_size_in: float = 6) -> None:
        ''' Plot all results and automatically detect how many reactions and
        facet types exist.

        Parameters
        ----------
        plot_filename: str, optional
            a name of the file of generated plot,
            by default ``None``
        max_barrier: float, optional
            all barriers lower then max_barrier will be ploted,
            by default ``None``
        x_size_in: float, optional
            x size of the plot in inches, by default ``8``
        y_size_in: float, optional
            y size of the plot in inches, by default ``6``

        '''
        reaction_energies = self.get_reaction_energies_all()
        activation_barriers = self.get_barrier_all()
        all_rxn_names = IO().get_list_all_rxns_names(self.yamlfile)
        n_facets = len(self.facetpaths)
        n_rxns = len(all_rxn_names)
        _, axes = plt.subplots(n_facets, n_rxns, squeeze=False)

        # print(axes)
        for num, rxn in enumerate(self.reactions):
            for ax, facetpath, in zip(axes, self.facetpaths):
                rxn_name = IO().get_rxn_name(rxn)
                key = facetpath + '_' + rxn_name
                Results.plot_rxn(key, reaction_energies, activation_barriers,
                                 rxn_name, ax, num, plot_filename, max_barrier,
                                 x_size_in, y_size_in)
Example #8
0
    def get_reaction_energies_all(self) -> None:
        ''' Get reactiom energy (kj/mol) for all facetpaths and reactions

        Returns
        -------
        reaction_energies : Dict[str:float]
            a dictionary with reaction energies for a given facetpath and
            rxn_name, e.g.

            >>> reaction_energies = {'Cu_111_OH_O+H': 70.81}

        '''
        reaction_energies = {}
        for facetpath, slab_path in zip(self.facetpaths, self.slab_paths):
            minima_path = os.path.join(facetpath, 'minima')
            for rxn in self.reactions:
                r_name_list, p_name_list = IO.get_reactants_and_products(rxn)
                rxn_name = IO.get_rxn_name(rxn)
                key = facetpath + '_' + rxn_name
                reaction_energies[key] = float(
                    self.get_reaction_energy(minima_path, facetpath,
                                             r_name_list, p_name_list,
                                             slab_path))
        return reaction_energies
Example #9
0
    def __init__(
            self,
            facetpath: str,
            yamlfile: str,
            slab: str,
            repeats: Tuple[int, int, int],
            creation_dir: PosixPath) -> None:

        self.facetpath = facetpath
        self.yamlfile = yamlfile
        self.repeats = repeats
        self.creation_dir = creation_dir
        self.slab_atom_path = os.path.join(self.creation_dir, slab)
        self.nslab = len(read(self.slab_atom_path) * self.repeats)
        self.n_kpts = IO().get_kpoints(self.repeats)
Example #10
0
File: ts.py Project: zadorlab/pynta
    def __init__(self, facetpath: str, slab: str, ts_estimate_dir: str,
                 yamlfile: str, repeats: Tuple[int, int, int],
                 creation_dir: PosixPath) -> None:
        ''' Initializing

        Parameters
        ___________
        facetpath : str
            a path to the workflow's main dir
            e.g. 'Cu_111'
        slab : str
            a '.xyz' file name with the optimized slab
            e.g.
            'Cu_111_slab_opt.xyz'
        ts_estimate_dir : str
            a path to directory with TSs
            e.g. 'TS_estimate'
        yamlfile : str
            a name of the .yaml file with a reactions list
        repeats : tuple(int, int, int)
            how to replicate unit cell in (x, y, z) direction,
            e.g. (3, 3, 1)
        creation_dir : posix
            a posix path to the main working directory

        '''
        self.facetpath = facetpath
        self.slab = slab
        self.ts_estimate_dir = ts_estimate_dir
        self.yamlfile = yamlfile
        self.repeats = repeats
        self.creation_dir = creation_dir
        self.n_kpts = IO().get_kpoints(self.repeats)
        self.path_to_slab = os.path.join(self.creation_dir, self.slab)
        big_slab = read(self.path_to_slab) * self.repeats
        self.nslab = len(big_slab)
Example #11
0
    def get_ts_guess_and_bonded_idx(
            self, reacting_idxs: List[int]) -> Tuple[Gratoms, int]:
        ''' Get ts_guess (Gratom) and index of atom that connects ts_guess
        to the surface. Currently, only monodentate adsobrtion is supported

        Parameters
        ----------
        reacting_idxs : List[int]
            list with indicies of reacting atoms, in order as they appear in
            .yaml file but not necessary with the same indicies

        Returns
        -------
        ts_guess_image, s_bonded_idx : Tuple[Gratoms, int]
            ts_geuss_el is Gratoms representation of the TS guess, whereas
            s_bonded_idx is the index of adsorbate atom that bonds to surface

        '''
        # get TS guess image
        ts_guess_image = IO.get_TS_guess_image(self.rxn, self.easier_to_build)

        # get index of surface bonded atom
        s_bonded_idx = self.get_s_bonded_idx()

        # get reacting atoms indices, as they appear in the .yaml file
        # surface atoms and multiplicity line are ignored
        tag_react_atom_idx_1, tag_react_atom_idx_2 = reacting_idxs

        # convert tag indices to indices as they appear in the Gratoms object
        react_atom_idx_1 = [
            atom.index for atom in ts_guess_image
            if atom.tag == tag_react_atom_idx_1
        ][0]

        react_atom_idx_2 = [
            atom.index for atom in ts_guess_image
            if atom.tag == tag_react_atom_idx_2
        ][0]

        # get atomic indices of all atoms connected to atom2
        connected_atoms = self.convert_tag_to_correct_idx(
            ts_guess_image, tag_react_atom_idx_1, tag_react_atom_idx_2)

        # add atom2 idx to the list of connected_atoms
        if react_atom_idx_2 not in connected_atoms:
            connected_atoms.append(react_atom_idx_2)

        # get lenght of the bond between reacting atoms
        bondlen = ts_guess_image.get_distance(react_atom_idx_1,
                                              react_atom_idx_2)

        n_total_ads_atoms = len(ts_guess_image)

        # create a better TS_guess for a couple of common edge cases
        if n_total_ads_atoms == 2:
            # proper diatomic
            ts_guess_image.rotate(90, 'y')

        elif n_total_ads_atoms == 3:
            # proper triatomic
            remaining_atom_idx = n_total_ads_atoms - \
                (react_atom_idx_1 + react_atom_idx_2)

            if react_atom_idx_2 != 2:
                # Symetrically not important, but structure looks
                # better visually
                ts_guess_image.rotate(-90, 'z')
            else:
                ts_guess_image.rotate(90, 'z')

            # set angle that puts atom_2 closer to the surface
            ts_guess_image.set_angle(remaining_atom_idx,
                                     react_atom_idx_1,
                                     react_atom_idx_2,
                                     30,
                                     indices=[
                                         remaining_atom_idx, react_atom_idx_1,
                                         react_atom_idx_2
                                     ],
                                     add=True)

        else:
            # continue with defaults
            # TODO for now it should work bu should be improved later
            # pass
            ts_guess_image.rotate(90, 'z')

            # scale the bond distance between reacting atoms
        ts_guess_image.set_distance(react_atom_idx_1,
                                    react_atom_idx_2,
                                    bondlen * self.scfactor,
                                    fix=0,
                                    indices=connected_atoms)

        return ts_guess_image, s_bonded_idx
scaled2 = {scaled2}
creation_dir = '{creation_dir}'
rxn = {rxn}
rxn_name = '{rxn_name}'
balsam_exe_settings = {balsam_exe_settings}
minima_dir = os.path.join(creation_dir, facetpath, 'minima')
ts_dir = 'TS_estimate'
path_to_ts_estimate = os.path.join(creation_dir, facetpath, rxn_name, ts_dir)

ts = TS(facetpath, slab, ts_dir, yamlfile, repeats, creation_dir)

ts.prepare_ts_estimate(rxn, scfactor, scfactor_surface, pytemplate_xtb,
                       species_list, reacting_atoms, metal_atom, scaled1,
                       scaled2)

dependancy_dict = IO().depends_on(facetpath, yamlfile, creation_dir)
jobs_to_be_finished = dependancy_dict[rxn_name]

dependency_workflow_name = facetpath + '_01_' + rxn_name
workflow_name = facetpath + '_02_' + rxn_name
dependent_workflow_name = facetpath + '_03_' + rxn_name

pending_simulations_dep = BalsamJob.objects.filter(
    workflow__contains=dependent_workflow_name).exclude(state="JOB_FINISHED")

# for a given rxn_name, get all BalsamJob objects that it depends on
pending_simulations = []
for dep_job in jobs_to_be_finished:
    pending_simulations.append(
        BalsamJob.objects.filter(name=dep_job).exclude(state="JOB_FINISHED"))
Example #13
0
    def create_minima_vib_all(
            self,
            socket_calculator: str,
            facetpath: str,
            yamlfile_path: PosixPath,
            pytemplate: str,
            balsam_exe_settings: Dict[str, int],
            pseudo_dir: str,
            pseudopotentials: Dict[str, str],
            calc_keywords: Dict[str, str],
            creation_dir: PosixPath) -> None:
        ''' Create all files for frequency calculations for the most stable
        conformer for a given species.

        Parameters
        ----------
        facetpath : str
            a path to the workflow's main dir
            e.g. ``'Cu_111'``
        yamlfile_path : posix
            a path to .yaml file with all reactions e.g. 'reactions.yaml'
        pytemplate : str
            a pytemplate for frequency calculations of minima
        balsam_exe_settings : Dict[str, int]
            a dictionary with balsam execute parameters (cores, nodes, etc.),
            e.g.

            >>> balsam_exe_settings = {'num_nodes': 1,
                                    'ranks_per_node': 48,
                                    'threads_per_rank': 1}

        calc_keywords : Dict[str, str]
            a dictionary with parameters to run DFT package. Quantum Espresso
            is used as default, e.g.

            >>> calc_keywords = {'kpts': (3, 3, 1), 'occupations': 'smearing',
                                'smearing':  'marzari-vanderbilt',
                                'degauss': 0.01, 'ecutwfc': 40, 'nosym': True,
                                'conv_thr': 1e-11, 'mixing_mode': 'local-TF'}

        pseudo_dir : str
            a path to the QE's pseudopotentials main directory
            e.g.
            ``'/home/mgierad/espresso/pseudo'``

        pseudopotentials : Dict[str, str]
            a dictionary with QE pseudopotentials for all species.
            e.g.

            >>> dict(Cu='Cu.pbe-spn-kjpaw_psl.1.0.0.UPF',
                    H='H.pbe-kjpaw_psl.1.0.0.UPF',
                    O='O.pbe-n-kjpaw_psl.1.0.0.UPF',
                    C='C.pbe-n-kjpaw_psl.1.0.0.UPF')

        calc_keywords : Dict[str, str]
            a dictionary with parameters to run DFT package. Quantum Espresso
            is used as default, e.g.

            >>> calc_keywords = {'kpts': (3, 3, 1),
                    'occupations': 'smearing',
                    'smearing': 'marzari-vanderbilt',
                    'degauss': 0.01,
                    'ecutwfc': 40,
                    'nosym': True,
                    'conv_thr': 1e-11,
                    'mixing_mode':'local-TF'}

        creation_dir : PosixPath
            a posix path to the working directory

        '''
        minima_vib_path = os.path.join(
            self.creation_dir, self.facetpath, 'minima_vib')
        os.makedirs(minima_vib_path, exist_ok=True)

        unique_adsorbates_prefixes = IO().get_unique_adsorbates_prefixes(
            facetpath, yamlfile_path, creation_dir)
        for adsorbate, unique_prefixes in unique_adsorbates_prefixes.items():
            for prefix in unique_prefixes:
                path_to_minimum_traj = os.path.join(
                    self.minima_path, adsorbate, prefix + '.traj')

                path_to_vib_species = os.path.join(
                    minima_vib_path, adsorbate, prefix)
                os.makedirs(path_to_vib_species, exist_ok=True)

                traj_to_start_vib = os.path.join(
                    path_to_vib_species,
                    '{}_{}.traj'.format(prefix, adsorbate))
                shutil.copy2(path_to_minimum_traj, traj_to_start_vib)

                self.create_minima_vib_py_files(
                    socket_calculator, adsorbate, prefix, traj_to_start_vib,
                    minima_vib_path, pytemplate, balsam_exe_settings,
                    pseudo_dir, pseudopotentials, calc_keywords, creation_dir)
Example #14
0
repeats = {repeats}
yamlfile = '{yamlfile}'
pytemplate = '{pytemplate}'
pseudopotentials = {pseudopotentials}
pseudo_dir = '{pseudo_dir}'
balsam_exe_settings = {balsam_exe_settings}
calc_keywords = {calc_keywords}
creation_dir = '{creation_dir}'

put_adsorbates = Adsorbates(facetpath, slab, repeats, yamlfile, creation_dir)
put_adsorbates.adjacency_to_3d()
put_adsorbates.create_relax_jobs(socket_calculator, pytemplate,
                                 pseudopotentials, pseudo_dir,
                                 balsam_exe_settings, calc_keywords)

dependancy_dict = IO().depends_on(facetpath, yamlfile, creation_dir)

# keep track of all submitted jobs (all unique)
all_submitted_jobs = []

# specify dependant 02 for given reactions
for rxn_name in dependancy_dict.keys():
    workflow_name = facetpath + '_01_' + rxn_name
    # ts estimate jobs
    dependent_workflow_name_1 = facetpath + '_02_' + rxn_name
    # get all dependant workflow BalsamJobs objects (should be one)
    pending_simulations_dep_1 = BalsamJob.objects.filter(
        workflow__contains=dependent_workflow_name_1).exclude(
            state="JOB_FINISHED")

    # for each reaction keep track of its dependencies
Example #15
0
    def set_up_ts_vib(
            self,
            socket_calculator: str,
            rxn: Dict[str, str],
            pytemplate: str,
            balsam_exe_settings: Dict[str, int],
            calc_keywords: Dict[str, str],
            pseudopotentials: Dict[str, str],
            pseudo_dir: str) -> None:
        ''' Set up files for TSs vibration calculations

        Parameters
        ----------
        rxn : Dict[str, str]
            a dictionary with info about the paricular reaction. This can be
            view as a splitted many reaction .yaml file to a single reaction
            :literal:`*.yaml` file
        pytemplate : python script
            a template for TS_vib calculations
        balsam_exe_settings : Dict[str, int],
            a dictionary with balsam execute parameters (cores, nodes, etc.),
            e.g.

            >>> balsam_exe_settings = {'num_nodes': 1,
                                    'ranks_per_node': 48,
                                    'threads_per_rank': 1}

        calc_keywords : Dict[str, str]
            a dictionary with parameters to run DFT package. Quantum Espresso
            is used as default, e.g.

            >>> calc_keywords = {'kpts': (3, 3, 1), 'occupations': 'smearing',
                                'smearing':  'marzari-vanderbilt',
                                'degauss': 0.01, 'ecutwfc': 40, 'nosym': True,
                                'conv_thr': 1e-11, 'mixing_mode': 'local-TF'}

        pseudopotentials : Dict[str, str]
            a dictionary with QE pseudopotentials for all species.
            e.g.

            >>> dict(Cu='Cu.pbe-spn-kjpaw_psl.1.0.0.UPF',
                    H='H.pbe-kjpaw_psl.1.0.0.UPF',
                    O='O.pbe-n-kjpaw_psl.1.0.0.UPF',
                    C='C.pbe-n-kjpaw_psl.1.0.0.UPF')

        pseudo_dir : str
            a path to the QE's pseudopotentials main directory
            e.g.
            ``'/home/mgierad/espresso/pseudo'``

        '''
        rxn_name = IO().get_rxn_name(rxn)

        ts_estimate_unique_dir = os.path.join(
            self.creation_dir,
            self.facetpath,
            rxn_name,
            'TS_estimate_unique')
        ts_vib_dir = os.path.join(
            self.creation_dir,
            self.facetpath,
            rxn_name,
            'TS_estimate_unique_vib')

        ts_final_geoms = Path(ts_estimate_unique_dir).glob('**/*final.xyz')

        for ts_final_geom in ts_final_geoms:
            ts_final_geom = str(ts_final_geom)
            prefix = ts_final_geom.split('/')[-2]
            ts_vib_dir_prefix = os.path.join(ts_vib_dir, prefix)
            os.makedirs(ts_vib_dir_prefix, exist_ok=True)

            # copy *ts_final.xyz files to ts_vib_dir_prefix - for debug
            shutil.copy2(ts_final_geom, ts_vib_dir_prefix)
            _, geom = os.path.split(ts_final_geom)

            py_fname = ts_vib_dir_prefix + '_' + \
                self.facetpath + '_' + rxn_name + '_ts_vib.py'

            self.create_ts_vib_py_files(
                socket_calculator,
                pytemplate,
                geom,
                py_fname,
                balsam_exe_settings,
                calc_keywords,
                pseudopotentials,
                pseudo_dir)
Example #16
0
    def prepare_opt_after_ts(
            self,
            socket_calculator: str,
            rxn: Dict[str, str],
            pytemplate: str,
            balsam_exe_settings: Dict[str, int],
            calc_keywords: Dict[str, str],
            pseudopotentials: Dict[str, str],
            pseudo_dir: str) -> None:
        ''' Create files for after_TS calculations - to verify TS structures
        and get corresponding reactant and product minima

        Parameters
        ----------
        rxn : Dict[str, str]
            a dictionary with info about the paricular reaction. This can be
            view as a splitted many reaction .yaml file to a single reaction
            :literal:`*.yaml` file
        pytemplate : python script
            a template for after_TS calculations
        balsam_exe_settings : Dict[str, int]
            a dictionary with balsam execute parameters (cores, nodes, etc.),
            e.g.

            >>> balsam_exe_settings = {'num_nodes': 1,
                                    'ranks_per_node': 48,
                                    'threads_per_rank': 1}

        calc_keywords : Dict[str, str]
            a dictionary with parameters to run DFT package. Quantum Espresso
            is used as default, e.g.

            >>> calc_keywords = {'kpts': (3, 3, 1), 'occupations': 'smearing',
                                'smearing':  'marzari-vanderbilt',
                                'degauss': 0.01, 'ecutwfc': 40, 'nosym': True,
                                'conv_thr': 1e-11, 'mixing_mode': 'local-TF'}

        pseudopotentials : Dict[str, str]
            a dictionary with QE pseudopotentials for all species.
            e.g.

            >>> dict(Cu='Cu.pbe-spn-kjpaw_psl.1.0.0.UPF',
                    H='H.pbe-kjpaw_psl.1.0.0.UPF',
                    O='O.pbe-n-kjpaw_psl.1.0.0.UPF',
                    C='C.pbe-n-kjpaw_psl.1.0.0.UPF')

        pseudo_dir : str
            a path to the QE's pseudopotentials main directory
            e.g.
            ``'/home/mgierad/espresso/pseudo'``

        '''
        rxn_name = IO().get_rxn_name(rxn)
        ts_vib_dir = os.path.join(self.creation_dir,
                                  self.facetpath,
                                  rxn_name,
                                  'TS_estimate_unique_vib')

        vib_traj_files = Path(ts_vib_dir).glob('**/*traj')
        for vib_traj in vib_traj_files:
            vib_traj = str(vib_traj)
            prefix = vib_traj.split('/')[-2]

            after_ts_dir = os.path.join(self.creation_dir,
                                        self.facetpath,
                                        rxn_name,
                                        'after_TS',
                                        prefix)
            os.makedirs(after_ts_dir, exist_ok=True)

            fname = os.path.join(
                prefix + '_' + self.facetpath + '_' + rxn_name + '_after_ts')

            fname_forward = os.path.join(after_ts_dir, fname + '_f')
            fname_reverse = os.path.join(after_ts_dir, fname + '_r')

            AfterTS.get_forward_and_reverse(
                vib_traj,
                fname_forward,
                fname_reverse)

            self.create_after_ts_py_files(
                socket_calculator,
                pytemplate,
                fname_forward,
                fname_reverse,
                balsam_exe_settings,
                calc_keywords,
                pseudopotentials,
                pseudo_dir)
Example #17
0
    def create_relax_jobs(self,
                          socket_calculator: str,
                          pytemplate: str,
                          pseudopotentials: Dict[str, str],
                          pseudo_dir: str,
                          balsam_exe_settings: Dict[str, int],
                          calc_keywords: Dict[str, str],
                          shtemplate: str = None) -> None:
        ''' Create a submit scripts

        Parameters
        __________
        pytemplate: str
            a template to prepare submission scripts
            for adsorbate+surface minimization
        pseudopotentials: Dict[str, str]
            a dictionary with QE pseudopotentials for all species.
            e.g.

            >>> dict(Cu='Cu.pbe-spn-kjpaw_psl.1.0.0.UPF',
                    H='H.pbe-kjpaw_psl.1.0.0.UPF',
                    O='O.pbe-n-kjpaw_psl.1.0.0.UPF',
                    C='C.pbe-n-kjpaw_psl.1.0.0.UPF')

        pseudo_dir: str
            a path to the QE's pseudopotentials main directory
            e.g.
            ``'/home/mgierad/espresso/pseudo'``
        balsam_exe_settings: Dict[str, int]
            a dictionary with balsam execute parameters(cores, nodes, etc.),
            e.g.

            >>> balsam_exe_settings={'num_nodes': 1,
                'ranks_per_node': 48,
                'threads_per_rank': 1}

        calc_keywords: Dict[str, str]
            a dictionary with parameters to run DFT package. Quantum Espresso
            is used as default, e.g.

            >>> calc_keywords={'kpts': (3, 3, 1),
                    'occupations': 'smearing',
                    'smearing': 'marzari-vanderbilt',
                    'degauss': 0.01,
                    'ecutwfc': 40,
                    'nosym': True,
                    'conv_thr': 1e-11,
                    'mixing_mode': 'local-TF'}

        shtemplate: str
            optional, a path to :literal:`*.sh` template(not required by the
            workflow but possible to specified for special cases)

        '''
        n_kpts = IO().get_kpoints(self.repeats)
        minimapath = os.path.join(self.creation_dir, self.facetpath, 'minima')
        with open(pytemplate, 'r') as f:
            pytemplate = f.read()

        if shtemplate is not None:
            with open(shtemplate, 'r') as f:
                shtemplate = f.read()

        for species in os.listdir(minimapath):
            speciespath = os.path.join(minimapath, species)
            if not os.path.isdir(speciespath):
                continue
            for structure in os.listdir(speciespath):
                if structure.endswith('xyz'):
                    prefix = os.path.splitext(structure)[0]
                    fname = os.path.join(
                        minimapath, self.facetpath + '_' + species + '_' +
                        prefix + '_relax.py')
                    with open(fname, 'w') as f:
                        f.write(
                            pytemplate.format(
                                socket_calculator=socket_calculator,
                                adsorbate=species,
                                prefix=prefix,
                                pseudopotentials=pseudopotentials,
                                pseudo_dir=pseudo_dir,
                                balsam_exe_settings=balsam_exe_settings,
                                calc_keywords=calc_keywords,
                                creation_dir=self.creation_dir,
                                repeats=self.repeats,
                                n_kpts=n_kpts))
                    if shtemplate is None:
                        continue
                    # optional
                    fname = os.path.join(minimapath,
                                         f'{species}_{prefix}_relax.sh')
                    with open(fname, 'w') as f:
                        f.write(
                            shtemplate.format(adsorbate=species,
                                              prefix=prefix))
Example #18
0
File: ts.py Project: zadorlab/pynta
    def get_av_dist(path_to_minima: str,
                    species: str,
                    metal_atom: str,
                    scfactor_surface: float,
                    scaled: bool = False) -> float:
        ''' Get the average bond distance between a given adsorbate atom and
        the nearest surface atom for all symmetrically distinct minima

        Parameters
        ___________
        path_to_minima : str
            a path to minima
            e.g. ``'Cu_111/minima'``
        species : str
            a species symbol
            e.g. ``'H'`` or ``'CO'``
        metal_atom : str
            a checmical symbol for the surface atoms (only metallic surfaces
            are allowed)
        scfactor_surface : float
            a scaling factor to scale the target bond distance, i.e.
            the average distance between adsorbed atom and the nearest
            surface atom. Helpful e.g. when H is far away form the surface
            in TS, whereas for minima it is close to the surface
            e.g. 1.0
        scaled : bool
            specify whether to use the optional scfactor_surface
            for the given species
            default = False

        Returns
        ________
        av_dist : float
            an average bond distance between the given species and the nearest
            surface atom for all symmetrically dictinct minima

        '''
        path_to_species = os.path.join(path_to_minima, species)

        # get unique minima prefixes
        unique_minima_prefixes = IO.get_unique_prefixes(path_to_species)

        # choose a representative temp .traj file that will be used to create
        # surface_atom_idxs and adsorbate_atom_idxs will be created
        path_to_tmp_traj = os.path.join(path_to_species, '00.traj')
        tmp_traj = read(path_to_tmp_traj)

        # create a list with all surface atom indicies
        surface_atom_idxs = [
            atom.index for atom in tmp_traj if atom.symbol == metal_atom
        ]

        # create a list with all adosorbate atom indicies
        adsorbate_atom_idxs = {
            atom.symbol + '_' + str(atom.index): atom.index
            for atom in tmp_traj if atom.symbol != metal_atom
        }

        # loop through all unique traj files, e.g. 00.traj, 01.traj ...
        all_dists_bonded = []
        for index in unique_minima_prefixes:
            path_to_unique_minima_traj = os.path.join(path_to_species,
                                                      '{}.traj'.format(index))
            uq_species_atom = read(path_to_unique_minima_traj)
            ads_atom_surf_dist = {}
            for key, ads_atom_idx in adsorbate_atom_idxs.items():
                # create a dict storing the shortest distance between given
                # adsorbate index atom and surface atoms
                ads_atom_surf_dist[key] = min(
                    uq_species_atom.get_distances(ads_atom_idx,
                                                  surface_atom_idxs))
            # the shortest distance in bonded_ads_atom_surf_dist.values()
            # is considered as a distance between atom bonded to the surface
            # and the surface
            bonded_ads_atom_surf_dist = min(ads_atom_surf_dist.values())
            all_dists_bonded.append(bonded_ads_atom_surf_dist)
        # apply the scalling factor
        if scaled:
            av_dist = mean(all_dists_bonded) * scfactor_surface
        else:
            av_dist = mean(all_dists_bonded)
        return av_dist