Statistiques
| Branche: | Tag: | Révision :

dockonsurf / modules / screening.py @ dadc6016

Historique | Voir | Annoter | Télécharger (8,54 ko)

1
import logging
2
import numpy as np
3
import ase
4

    
5
logger = logging.getLogger('DockOnSurf')
6

    
7

    
8
def get_vect_angle(v1, v2, degrees=False):
9
    """Computes the angle between two vectors.
10

11
    @param v1: The first vector.
12
    @param v2: The second vector.
13
    @param degrees: Whether the result should be in radians (True) or in
14
        degrees (False).
15
    @return: The angle in radians if degrees = False, or in degrees if
16
        degrees =True
17
    """
18
    v1_u = v1 / np.linalg.norm(v1)
19
    v2_u = v2 / np.linalg.norm(v2)
20
    angle = np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))
21
    return angle if not degrees else angle * 180 / np.pi
22

    
23

    
24
def vect_avg(vects):
25
    """Computes the element-wise mean of a set of vectors.
26

27
    @param vects: list of lists-like: containing the vectors (num_vectors,
28
        length_vector).
29
    @return: vector average computed doing the element-wise mean.
30
    """
31
    from utilities import try_command
32
    err = "vect_avg parameter vects must be a list-like, able to be converted" \
33
          " np.array"
34
    array = try_command(np.array, [(ValueError, err)], vects)
35
    if len(array.shape) == 1:
36
        return array
37
    else:
38
        num_vects = array.shape[1]
39
        return np.array([np.average(array[:, i]) for i in range(num_vects)])
40

    
41

    
42
def get_atom_coords(atoms: ase.Atoms, ctrs_list):
43
    """Gets the coordinates of the specified atoms from a ase.Atoms object.
44

45
    Given an ase.Atoms object and a list of atom indices specified in ctrs_list
46
    it gets the coordinates of the specified atoms. If the element in the
47
    ctrs_list is not an index but yet a list of indices, it computes the
48
    element-wise mean of the coordinates of the atoms specified in the inner
49
    list.
50
    @param atoms: ase.Atoms object for which to obtain the coordinates of.
51
    @param ctrs_list: list of (indices/list of indices) of the atoms for which
52
                      the coordinates should be extracted.
53
    @return: np.ndarray of atomic coordinates.
54
    """
55
    coords = []
56
    for i, elem in enumerate(ctrs_list):
57
        if isinstance(elem, list):
58
            coords.append(vect_avg(np.array([atoms[c].position for c in elem])))
59
        elif isinstance(elem, int):
60
            coords.append(atoms[elem].position)
61
        else:
62
            err = f"'ctrs_list must be a list of ints or lists, {type(elem)} " \
63
                  "found."
64
            logger.error(err)
65
            raise ValueError
66
    return np.array(coords)
67

    
68

    
69
def add_adsorbate(slab, adsorbate, site_coord, ctr_coord, height, offset=None,
70
                  norm_vect=(0, 0, 1)):
71
    """Add an adsorbate to a surface.
72

73
    This function extends the functionality of ase.build.add_adsorbate
74
    (https://wiki.fysik.dtu.dk/ase/ase/build/surface.html#ase.build.add_adsorbate)
75
    by enabling to change the z coordinate and the axis perpendicular to the
76
    surface.
77
    @param slab: ase.Atoms object containing the coordinates of the surface
78
    @param adsorbate: ase.Atoms object containing the coordinates of the
79
        adsorbate.
80
    @param site_coord: The coordinates of the adsorption site on the surface.
81
    @param ctr_coord: The coordinates of the adsorption center in the molecule.
82
    @param height: The height above the surface where to adsorb.
83
    @param offset: Offsets the adsorbate by a number of unit cells. Mostly
84
        useful when adding more than one adsorbate.
85
    @param norm_vect: The vector perpendicular to the surface.
86
    """
87
    info = slab.info.get('adsorbate_info', {})
88
    pos = np.array([0.0, 0.0, 0.0])  # part of absolute coordinates
89
    spos = np.array([0.0, 0.0, 0.0])  # part relative to unit cell
90
    norm_vect_u = np.array(norm_vect) / np.linalg.norm(norm_vect)
91
    if offset is not None:
92
        spos += np.asarray(offset, float)
93
    if isinstance(site_coord, str):
94
        # A site-name:
95
        if 'sites' not in info:
96
            raise TypeError('If the atoms are not made by an ase.build '
97
                            'function, position cannot be a name.')
98
        if site_coord not in info['sites']:
99
            raise TypeError('Adsorption site %s not supported.' % site_coord)
100
        spos += info['sites'][site_coord]
101
    else:
102
        pos += site_coord
103
    if 'cell' in info:
104
        cell = info['cell']
105
    else:
106
        cell = slab.get_cell()
107
    pos += np.dot(spos, cell)
108
    # Convert the adsorbate to an Atoms object
109
    if isinstance(adsorbate, ase.Atoms):
110
        ads = adsorbate
111
    elif isinstance(adsorbate, ase.Atom):
112
        ads = ase.Atoms([adsorbate])
113
    else:
114
        # Assume it is a string representing a single Atom
115
        ads = ase.Atoms([ase.Atom(adsorbate)])
116
    pos += height * norm_vect_u
117
    # Move adsorbate into position
118
    ads.translate(pos - ctr_coord)
119
    # Attach the adsorbate
120
    slab.extend(ads)
121

    
122

    
123
def ads_euler(molec, surf, site, ctr, pts_angle, neigh_ctr, norm_vect):
124
    from random import random
125
    plane_vect = np.cross(norm_vect, [random() for i in range(3)])
126
    add_adsorbate()
127

    
128
    return
129

    
130

    
131
def ads_chemcat(site, ctr, pts_angle):
132
    return "TO IMPLEMENT"
133

    
134

    
135
def adsorb_confs(conf_list, surf, ads_ctrs, sites, algo, num_pts, neigh_ctrs,
136
                 norm_vect, coll_bttm):
137
    """Generates a number of adsorbate-surface structure coordinates.
138

139
    Given a list of conformers, a surface, a list of atom indices (or list of
140
    list of indices) of both the surface and the adsorbate, it generates a
141
    number of adsorbate-surface structures for every possible combination of
142
    them at different orientations.
143
    @param conf_list: list of ase.Atoms of the different conformers
144
    @param surf: the ase.Atoms object of the surface
145
    @param ads_ctrs: the list atom indices of the adsorbate.
146
    @param sites: the list of atom indices of the surface.
147
    @param algo: the algorithm to use for the generation of adsorbates.
148
    @param num_pts: the number of points per angle orientation to sample
149
    @param neigh_ctrs: the indices of the neighboring atoms to the adsorption
150
    atoms.
151
    @param norm_vect: The vector perpendicular to the surface.
152
    @param coll_bttm: The lowermost height for which to detect a collision
153
    @return: list of ase.Atoms for the adsorbate-surface structures
154
    """
155
    surf_ads_list = []
156
    sites_coords = get_atom_coords(surf, sites)
157
    for conf in conf_list:
158
        molec_ctr_coords = get_atom_coords(conf, ads_ctrs)
159
        molec_neigh_coords = get_atom_coords(conf, neigh_ctrs)
160
        for site in sites_coords:
161
            for i, molec_ctr in enumerate(molec_ctr_coords):
162
                if algo == 'euler':
163
                    surf_ads_list.extend(ads_euler(conf, surf, molec_ctr, site,
164
                                                   num_pts, norm_vect,
165
                                                   coll_bttm,
166
                                                   molec_neigh_coords[i]))
167
                elif algo == 'chemcat':
168
                    surf_ads_list.extend(ads_chemcat(site, molec_ctr, num_pts))
169
    return surf_ads_list
170

    
171

    
172
def run_screening(inp_vars):
173
    """Carry out the screening of adsorbate coordinates on a surface
174

175
    @param inp_vars: Calculation parameters from input file.
176
    """
177
    import os
178
    from modules.formats import read_coords, read_energies, \
179
        rdkit_mol_to_ase_atoms, adapt_format
180
    from modules.clustering import get_rmsd, clustering
181
    from modules.isolated import get_moments_of_inertia
182
    from modules.calculation import run_calc
183

    
184
    if not os.path.isdir("isolated"):
185
        err = "'isolated' directory not found. It is needed in order to carry "
186
        "out the screening of structures to be adsorbed"
187
        logger.error(err)
188
        raise ValueError(err)
189

    
190
    conf_list = read_coords(inp_vars['code'], 'isolated', 'rdkit')
191
    # TODO Implement neighbors algorithm / align molecule to surface
192
    # neigh_list = get_neighbors(conf_list[0], inp_vars['molec_ads_ctrs'])
193
    conf_enrgs = read_energies(inp_vars['code'], 'isolated')
194
    mois = np.array([get_moments_of_inertia(conf)[0] for conf in conf_list])
195
    rmsd_mtx = get_rmsd(conf_list)
196
    exemplars = clustering(rmsd_mtx)  # TODO Kind of Clustering
197
    clust_conf_list = [conf_list[idx] for idx in exemplars]
198
    conf_list = [rdkit_mol_to_ase_atoms(conf) for conf in clust_conf_list]
199
    surf = adapt_format('ase', inp_vars['surf_file'])
200
    surf_ads_list = adsorb_confs(conf_list, surf, inp_vars['molec_ads_ctrs'],
201
                                 inp_vars['sites'], inp_vars['ads_algo'],
202
                                 inp_vars['sample_points_per_angle'],
203
                                 inp_vars['molec_neigh_ctrs'],
204
                                 inp_vars['surf_norm_vect'],
205
                                 inp_vars['collision_bottom'])
206
    run_calc('screening', inp_vars, surf_ads_list)