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

dockonsurf / modules / screening.py @ 1e9e784d

Historique | Voir | Annoter | Télécharger (23,51 ko)

1 e07c09eb Carles
import logging
2 f3d1e601 Carles Marti
import numpy as np
3 f3d1e601 Carles Marti
import ase
4 e07c09eb Carles
5 e07c09eb Carles
logger = logging.getLogger('DockOnSurf')
6 e07c09eb Carles
7 e07c09eb Carles
8 bfe93f0d Carles Marti
def assign_prop(atoms: ase.Atoms, prop_name: str, prop_val):  # TODO Needed?
9 bfe93f0d Carles Marti
    atoms.info[prop_name] = prop_val
10 bfe93f0d Carles Marti
11 bfe93f0d Carles Marti
12 1e9e784d Carles Marti
def select_confs(orig_conf_list: list, calc_dirs: list, magns: list, num_sel: int, code: str):
13 bfe93f0d Carles Marti
    """Takes a list ase.Atoms and selects the most different magnitude-wise.
14 bfe93f0d Carles Marti

15 bfe93f0d Carles Marti
    Given a list of ase.Atoms objects and a list of magnitudes, it selects a
16 bfe93f0d Carles Marti
    number of the most different conformers according to every magnitude
17 bfe93f0d Carles Marti
    specified.
18 1e9e784d Carles Marti
     
19 bfe93f0d Carles Marti
    @param orig_conf_list: list of ase.Atoms objects to select among.
20 1e9e784d Carles Marti
    @param calc_dirs: List of directories where to read the energies from.
21 bfe93f0d Carles Marti
    @param magns: list of str with the names of the magnitudes to use for the
22 1e9e784d Carles Marti
        conformer selection. Supported magnitudes: 'energy', 'moi'.
23 bfe93f0d Carles Marti
    @param num_sel: number of conformers to select for every of the magnitudes.
24 bfe93f0d Carles Marti
    @param code: The code that generated the magnitude information.
25 bfe93f0d Carles Marti
         Supported codes: See formats.py
26 bfe93f0d Carles Marti
    @return: list of the selected ase.Atoms objects.
27 bfe93f0d Carles Marti
    """
28 bfe93f0d Carles Marti
    from copy import deepcopy
29 fd2384fc Carles Marti
    from modules.formats import collect_energies
30 bfe93f0d Carles Marti
31 bfe93f0d Carles Marti
    conf_list = deepcopy(orig_conf_list)
32 bfe93f0d Carles Marti
    selected_ids = []
33 bfe93f0d Carles Marti
    if num_sel >= len(conf_list):
34 bfe93f0d Carles Marti
        logger.warning('Number of conformers per magnitude is equal or larger '
35 bfe93f0d Carles Marti
                       'than the total number of conformers. Using all '
36 695dcff8 Carles Marti
                       f'available conformers: {len(conf_list)}.')
37 bfe93f0d Carles Marti
        return conf_list
38 bfe93f0d Carles Marti
39 bfe93f0d Carles Marti
    # Read properties
40 bfe93f0d Carles Marti
    if 'energy' in magns:
41 1e9e784d Carles Marti
        conf_enrgs = collect_energies(calc_dirs, code, 'isolated')
42 bfe93f0d Carles Marti
    if 'moi' in magns:
43 bfe93f0d Carles Marti
        mois = np.array([conf.get_moments_of_inertia() for conf in conf_list])
44 bfe93f0d Carles Marti
45 bfe93f0d Carles Marti
    # Assign values
46 bfe93f0d Carles Marti
    for i, conf in enumerate(conf_list):
47 bfe93f0d Carles Marti
        assign_prop(conf, 'idx', i)
48 bfe93f0d Carles Marti
        if 'energy' in magns:
49 bfe93f0d Carles Marti
            assign_prop(conf, 'energy', conf_enrgs[i])
50 bfe93f0d Carles Marti
        if 'moi' in magns:
51 bfe93f0d Carles Marti
            assign_prop(conf, 'moi', mois[i, 2])
52 bfe93f0d Carles Marti
53 bfe93f0d Carles Marti
    # pick ids
54 bfe93f0d Carles Marti
    for magn in magns:
55 bfe93f0d Carles Marti
        sorted_list = sorted(conf_list, key=lambda conf: abs(conf.info[magn]))
56 bfe93f0d Carles Marti
        if sorted_list[-1].info['idx'] not in selected_ids:
57 bfe93f0d Carles Marti
            selected_ids.append(sorted_list[-1].info['idx'])
58 bfe93f0d Carles Marti
        if num_sel > 1:
59 bfe93f0d Carles Marti
            for i in range(0, len(sorted_list) - 1,
60 bfe93f0d Carles Marti
                           len(conf_list) // (num_sel - 1)):
61 bfe93f0d Carles Marti
                if sorted_list[i].info['idx'] not in selected_ids:
62 bfe93f0d Carles Marti
                    selected_ids.append(sorted_list[i].info['idx'])
63 bfe93f0d Carles Marti
64 695dcff8 Carles Marti
    logger.info(f'Selected {len(selected_ids)} conformers for adsorption.')
65 bfe93f0d Carles Marti
    return [conf_list[idx] for idx in selected_ids]
66 bfe93f0d Carles Marti
67 bfe93f0d Carles Marti
68 b4aef3d7 Carles Marti
def get_vect_angle(v1, v2, degrees=False):
69 b4aef3d7 Carles Marti
    """Computes the angle between two vectors.
70 b4aef3d7 Carles Marti

71 b4aef3d7 Carles Marti
    @param v1: The first vector.
72 b4aef3d7 Carles Marti
    @param v2: The second vector.
73 b4aef3d7 Carles Marti
    @param degrees: Whether the result should be in radians (True) or in
74 b4aef3d7 Carles Marti
        degrees (False).
75 b4aef3d7 Carles Marti
    @return: The angle in radians if degrees = False, or in degrees if
76 b4aef3d7 Carles Marti
        degrees =True
77 b4aef3d7 Carles Marti
    """
78 b4aef3d7 Carles Marti
    v1_u = v1 / np.linalg.norm(v1)
79 b4aef3d7 Carles Marti
    v2_u = v2 / np.linalg.norm(v2)
80 b4aef3d7 Carles Marti
    angle = np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))
81 b4aef3d7 Carles Marti
    return angle if not degrees else angle * 180 / np.pi
82 b4aef3d7 Carles Marti
83 b4aef3d7 Carles Marti
84 12a12bdd Carles Marti
def vect_avg(vects):
85 a5cc42ff Carles Marti
    """Computes the element-wise mean of a set of vectors.
86 12a12bdd Carles Marti

87 dadc6016 Carles Marti
    @param vects: list of lists-like: containing the vectors (num_vectors,
88 dadc6016 Carles Marti
        length_vector).
89 a5cc42ff Carles Marti
    @return: vector average computed doing the element-wise mean.
90 12a12bdd Carles Marti
    """
91 12a12bdd Carles Marti
    from utilities import try_command
92 12a12bdd Carles Marti
    err = "vect_avg parameter vects must be a list-like, able to be converted" \
93 12a12bdd Carles Marti
          " np.array"
94 dadc6016 Carles Marti
    array = try_command(np.array, [(ValueError, err)], vects)
95 dadc6016 Carles Marti
    if len(array.shape) == 1:
96 dadc6016 Carles Marti
        return array
97 12a12bdd Carles Marti
    else:
98 dadc6016 Carles Marti
        num_vects = array.shape[1]
99 dadc6016 Carles Marti
        return np.array([np.average(array[:, i]) for i in range(num_vects)])
100 12a12bdd Carles Marti
101 12a12bdd Carles Marti
102 8fdff302 Carles Marti
def get_atom_coords(atoms: ase.Atoms, ctrs_list=None):
103 8fdff302 Carles Marti
    """Gets the coordinates of the specified indices from a ase.Atoms object.
104 a5cc42ff Carles Marti

105 a5cc42ff Carles Marti
    Given an ase.Atoms object and a list of atom indices specified in ctrs_list
106 a5cc42ff Carles Marti
    it gets the coordinates of the specified atoms. If the element in the
107 a5cc42ff Carles Marti
    ctrs_list is not an index but yet a list of indices, it computes the
108 a5cc42ff Carles Marti
    element-wise mean of the coordinates of the atoms specified in the inner
109 a5cc42ff Carles Marti
    list.
110 a5cc42ff Carles Marti
    @param atoms: ase.Atoms object for which to obtain the coordinates of.
111 a5cc42ff Carles Marti
    @param ctrs_list: list of (indices/list of indices) of the atoms for which
112 a5cc42ff Carles Marti
                      the coordinates should be extracted.
113 a5cc42ff Carles Marti
    @return: np.ndarray of atomic coordinates.
114 a5cc42ff Carles Marti
    """
115 12a12bdd Carles Marti
    coords = []
116 8fdff302 Carles Marti
    err = "'ctrs_list' argument must be an integer, a list of integers or a " \
117 8fdff302 Carles Marti
          "list of lists of integers. Every integer must be in the range " \
118 8fdff302 Carles Marti
          "[0, num_atoms)"
119 8fdff302 Carles Marti
    if ctrs_list is None:
120 8fdff302 Carles Marti
        ctrs_list = range(len(atoms))
121 8fdff302 Carles Marti
    elif isinstance(ctrs_list, int):
122 8fdff302 Carles Marti
        if ctrs_list not in range(len(atoms)):
123 8fdff302 Carles Marti
            logger.error(err)
124 8fdff302 Carles Marti
            raise ValueError(err)
125 8fdff302 Carles Marti
        return atoms[ctrs_list].position
126 8fdff302 Carles Marti
    for elem in ctrs_list:
127 12a12bdd Carles Marti
        if isinstance(elem, list):
128 8fdff302 Carles Marti
            coords.append(vect_avg([atoms[c].position for c in elem]))
129 12a12bdd Carles Marti
        elif isinstance(elem, int):
130 12a12bdd Carles Marti
            coords.append(atoms[elem].position)
131 12a12bdd Carles Marti
        else:
132 8fdff302 Carles Marti
133 12a12bdd Carles Marti
            logger.error(err)
134 12a12bdd Carles Marti
            raise ValueError
135 12a12bdd Carles Marti
    return np.array(coords)
136 f3d1e601 Carles Marti
137 f3d1e601 Carles Marti
138 dadc6016 Carles Marti
def add_adsorbate(slab, adsorbate, site_coord, ctr_coord, height, offset=None,
139 1d22a086 Carles Marti
                  norm_vect=(0, 0, 1)):
140 1d22a086 Carles Marti
    """Add an adsorbate to a surface.
141 1d22a086 Carles Marti

142 1d22a086 Carles Marti
    This function extends the functionality of ase.build.add_adsorbate
143 1d22a086 Carles Marti
    (https://wiki.fysik.dtu.dk/ase/ase/build/surface.html#ase.build.add_adsorbate)
144 1d22a086 Carles Marti
    by enabling to change the z coordinate and the axis perpendicular to the
145 1d22a086 Carles Marti
    surface.
146 1d22a086 Carles Marti
    @param slab: ase.Atoms object containing the coordinates of the surface
147 1d22a086 Carles Marti
    @param adsorbate: ase.Atoms object containing the coordinates of the
148 1d22a086 Carles Marti
        adsorbate.
149 dadc6016 Carles Marti
    @param site_coord: The coordinates of the adsorption site on the surface.
150 dadc6016 Carles Marti
    @param ctr_coord: The coordinates of the adsorption center in the molecule.
151 dadc6016 Carles Marti
    @param height: The height above the surface where to adsorb.
152 1d22a086 Carles Marti
    @param offset: Offsets the adsorbate by a number of unit cells. Mostly
153 1d22a086 Carles Marti
        useful when adding more than one adsorbate.
154 1d22a086 Carles Marti
    @param norm_vect: The vector perpendicular to the surface.
155 1d22a086 Carles Marti
    """
156 36d92f4f Carles Marti
    from copy import deepcopy
157 1d22a086 Carles Marti
    info = slab.info.get('adsorbate_info', {})
158 dadc6016 Carles Marti
    pos = np.array([0.0, 0.0, 0.0])  # part of absolute coordinates
159 1d22a086 Carles Marti
    spos = np.array([0.0, 0.0, 0.0])  # part relative to unit cell
160 dadc6016 Carles Marti
    norm_vect_u = np.array(norm_vect) / np.linalg.norm(norm_vect)
161 1d22a086 Carles Marti
    if offset is not None:
162 1d22a086 Carles Marti
        spos += np.asarray(offset, float)
163 dadc6016 Carles Marti
    if isinstance(site_coord, str):
164 1d22a086 Carles Marti
        # A site-name:
165 1d22a086 Carles Marti
        if 'sites' not in info:
166 1d22a086 Carles Marti
            raise TypeError('If the atoms are not made by an ase.build '
167 1d22a086 Carles Marti
                            'function, position cannot be a name.')
168 dadc6016 Carles Marti
        if site_coord not in info['sites']:
169 dadc6016 Carles Marti
            raise TypeError('Adsorption site %s not supported.' % site_coord)
170 dadc6016 Carles Marti
        spos += info['sites'][site_coord]
171 1d22a086 Carles Marti
    else:
172 dadc6016 Carles Marti
        pos += site_coord
173 1d22a086 Carles Marti
    if 'cell' in info:
174 1d22a086 Carles Marti
        cell = info['cell']
175 1d22a086 Carles Marti
    else:
176 1d22a086 Carles Marti
        cell = slab.get_cell()
177 1d22a086 Carles Marti
    pos += np.dot(spos, cell)
178 1d22a086 Carles Marti
    # Convert the adsorbate to an Atoms object
179 1d22a086 Carles Marti
    if isinstance(adsorbate, ase.Atoms):
180 36d92f4f Carles Marti
        ads = deepcopy(adsorbate)
181 1d22a086 Carles Marti
    elif isinstance(adsorbate, ase.Atom):
182 1d22a086 Carles Marti
        ads = ase.Atoms([adsorbate])
183 1d22a086 Carles Marti
    else:
184 1d22a086 Carles Marti
        # Assume it is a string representing a single Atom
185 1d22a086 Carles Marti
        ads = ase.Atoms([ase.Atom(adsorbate)])
186 dadc6016 Carles Marti
    pos += height * norm_vect_u
187 1d22a086 Carles Marti
    # Move adsorbate into position
188 dadc6016 Carles Marti
    ads.translate(pos - ctr_coord)
189 1d22a086 Carles Marti
    # Attach the adsorbate
190 1d22a086 Carles Marti
    slab.extend(ads)
191 1d22a086 Carles Marti
192 1d22a086 Carles Marti
193 a4b57124 Carles Marti
def check_collision(slab_molec, slab_num_atoms, min_height, vect, nn_slab=0,
194 a4b57124 Carles Marti
                    nn_molec=0, coll_coeff=0.9):
195 5f3f4b69 Carles Marti
    """Checks whether a slab and a molecule collide or not.
196 5f3f4b69 Carles Marti

197 5f3f4b69 Carles Marti
    @param slab_molec: The system of adsorbate-slab for which to detect if there
198 5f3f4b69 Carles Marti
        are collisions.
199 5f3f4b69 Carles Marti
    @param nn_slab: Number of neigbors in the surface.
200 5f3f4b69 Carles Marti
    @param nn_molec: Number of neighbors in the molecule.
201 5f3f4b69 Carles Marti
    @param coll_coeff: The coefficient that multiplies the covalent radius of
202 5f3f4b69 Carles Marti
        atoms resulting in a distance that two atoms being closer to that is
203 5f3f4b69 Carles Marti
        considered as atomic collision.
204 5f3f4b69 Carles Marti
    @param slab_num_atoms: Number of atoms of the bare slab.
205 5f3f4b69 Carles Marti
    @param min_height: The minimum height atoms can have to not be considered as
206 5f3f4b69 Carles Marti
        colliding.
207 5f3f4b69 Carles Marti
    @param vect: The vector perpendicular to the slab.
208 5f3f4b69 Carles Marti
    @return: bool, whether the surface and the molecule collide.
209 5f3f4b69 Carles Marti
    """
210 5f3f4b69 Carles Marti
    from ase.neighborlist import natural_cutoffs, neighbor_list
211 5fb01677 Carles Marti
    if min_height is not False:
212 a4b57124 Carles Marti
        cart_axes = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0],
213 a4b57124 Carles Marti
                     [-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]]
214 a4b57124 Carles Marti
        if vect.tolist() in cart_axes:
215 5fb01677 Carles Marti
            for atom in slab_molec[slab_num_atoms:]:
216 a4b57124 Carles Marti
                for i, coord in enumerate(vect):
217 a4b57124 Carles Marti
                    if coord == 0:
218 a4b57124 Carles Marti
                        continue
219 a4b57124 Carles Marti
                    if atom.position[i] * coord < min_height:
220 a4b57124 Carles Marti
                        return True
221 a4b57124 Carles Marti
            return False
222 5f3f4b69 Carles Marti
    else:
223 5fb01677 Carles Marti
        slab_molec_cutoffs = natural_cutoffs(slab_molec, mult=coll_coeff)
224 b4b2f307 Carles Marti
        slab_molec_nghbs = len(
225 b4b2f307 Carles Marti
            neighbor_list("i", slab_molec, slab_molec_cutoffs))
226 5fb01677 Carles Marti
        if slab_molec_nghbs > nn_slab + nn_molec:
227 5fb01677 Carles Marti
            return True
228 5fb01677 Carles Marti
        else:
229 5fb01677 Carles Marti
            return False
230 5f3f4b69 Carles Marti
231 5f3f4b69 Carles Marti
232 b4b2f307 Carles Marti
def dissociate_h(slab_molec_orig, h_idx, num_atoms_slab, neigh_cutoff=1):
233 b4b2f307 Carles Marti
    # TODO rethink
234 91ae8d86 Carles Marti
    """Tries to dissociate a H from the molecule and adsorbs it on the slab.
235 b4b2f307 Carles Marti

236 91ae8d86 Carles Marti
    Tries to dissociate a H atom from the molecule and adsorb in on top of the
237 91ae8d86 Carles Marti
    surface if the distance is shorter than two times the neigh_cutoff value.
238 b4b2f307 Carles Marti
    @param slab_molec_orig: The ase.Atoms object of the system adsorbate-slab.
239 b4b2f307 Carles Marti
    @param h_idx: The index of the hydrogen atom to carry out adsorption of.
240 b4b2f307 Carles Marti
    @param num_atoms_slab: The number of atoms of the slab without adsorbate.
241 b4b2f307 Carles Marti
    @param neigh_cutoff: half the maximum distance between the surface and the
242 b4b2f307 Carles Marti
        H for it to carry out dissociation.
243 b4b2f307 Carles Marti
    @return: An ase.Atoms object of the system adsorbate-surface with H
244 b4b2f307 Carles Marti
    """
245 b4b2f307 Carles Marti
    from copy import deepcopy
246 b4b2f307 Carles Marti
    from ase.neighborlist import NeighborList
247 b4b2f307 Carles Marti
    slab_molec = deepcopy(slab_molec_orig)
248 b4b2f307 Carles Marti
    cutoffs = len(slab_molec) * [neigh_cutoff]
249 b4b2f307 Carles Marti
    nl = NeighborList(cutoffs, self_interaction=False, bothways=True)
250 b4b2f307 Carles Marti
    nl.update(slab_molec)
251 b4b2f307 Carles Marti
    surf_h_vect = np.array([np.infty] * 3)
252 b4b2f307 Carles Marti
    for neigh_idx in nl.get_neighbors(h_idx)[0]:
253 b4b2f307 Carles Marti
        if neigh_idx < num_atoms_slab:
254 b4b2f307 Carles Marti
            dist = np.linalg.norm(slab_molec[neigh_idx].position -
255 b4b2f307 Carles Marti
                                  slab_molec[h_idx].position)
256 b4b2f307 Carles Marti
            if dist < np.linalg.norm(surf_h_vect):
257 b4b2f307 Carles Marti
                surf_h_vect = slab_molec[neigh_idx].position \
258 b4b2f307 Carles Marti
                              - slab_molec[h_idx].position
259 b4b2f307 Carles Marti
    if np.linalg.norm(surf_h_vect) != np.infty:
260 b4b2f307 Carles Marti
        trans_vect = surf_h_vect - surf_h_vect / np.linalg.norm(surf_h_vect)
261 b4b2f307 Carles Marti
        slab_molec[h_idx].position = slab_molec[h_idx].position + trans_vect
262 b4b2f307 Carles Marti
        return slab_molec
263 b4b2f307 Carles Marti
264 b4b2f307 Carles Marti
265 b4b2f307 Carles Marti
def dissociation(slab_molec, disso_atoms, num_atoms_slab):
266 b4b2f307 Carles Marti
    # TODO rethink
267 b4b2f307 Carles Marti
    # TODO multiple dissociation
268 b4b2f307 Carles Marti
    """Decides which H atoms to dissociate according to a list of atoms.
269 b4b2f307 Carles Marti

270 b4b2f307 Carles Marti
    Given a list of chemical symbols or atom indices it checks for every atom
271 b4b2f307 Carles Marti
    or any of its neighbor if it's a H and calls dissociate_h to try to carry
272 b4b2f307 Carles Marti
    out dissociation of that H. For atom indices, it checks both whether
273 b4b2f307 Carles Marti
    the atom index or its neighbors are H, for chemical symbols, it only checks
274 b4b2f307 Carles Marti
    if there is a neighbor H.
275 b4b2f307 Carles Marti
    @param slab_molec: The ase.Atoms object of the system adsorbate-slab.
276 b4b2f307 Carles Marti
    @param disso_atoms: The indices or chemical symbols of the atoms
277 b4b2f307 Carles Marti
    @param num_atoms_slab:
278 b4b2f307 Carles Marti
    @return:
279 b4b2f307 Carles Marti
    """
280 b4b2f307 Carles Marti
    from ase.neighborlist import natural_cutoffs, NeighborList
281 b4b2f307 Carles Marti
    molec = slab_molec[num_atoms_slab:]
282 b4b2f307 Carles Marti
    cutoffs = natural_cutoffs(molec)
283 b4b2f307 Carles Marti
    nl = NeighborList(cutoffs, self_interaction=False, bothways=True)
284 b4b2f307 Carles Marti
    nl.update(molec)
285 b4b2f307 Carles Marti
    disso_structs = []
286 b4b2f307 Carles Marti
    for el in disso_atoms:
287 b4b2f307 Carles Marti
        if isinstance(el, int):
288 b4b2f307 Carles Marti
            if molec[el].symbol == 'H':
289 b4b2f307 Carles Marti
                disso_struct = dissociate_h(slab_molec, el + num_atoms_slab,
290 b4b2f307 Carles Marti
                                            num_atoms_slab)
291 b4b2f307 Carles Marti
                if disso_struct is not None:
292 b4b2f307 Carles Marti
                    disso_structs.append(disso_struct)
293 b4b2f307 Carles Marti
            else:
294 b4b2f307 Carles Marti
                for neigh_idx in nl.get_neighbors(el)[0]:
295 b4b2f307 Carles Marti
                    if molec[neigh_idx].symbol == 'H':
296 b4b2f307 Carles Marti
                        disso_struct = dissociate_h(slab_molec, neigh_idx +
297 b4b2f307 Carles Marti
                                                    num_atoms_slab,
298 b4b2f307 Carles Marti
                                                    num_atoms_slab)
299 b4b2f307 Carles Marti
                        if disso_struct is not None:
300 b4b2f307 Carles Marti
                            disso_structs.append(disso_struct)
301 b4b2f307 Carles Marti
        else:
302 b4b2f307 Carles Marti
            for atom in molec:
303 b4b2f307 Carles Marti
                if atom.symbol.lower() == el.lower():
304 b4b2f307 Carles Marti
                    for neigh_idx in nl.get_neighbors(atom.index)[0]:
305 b4b2f307 Carles Marti
                        if molec[neigh_idx].symbol == 'H':
306 b4b2f307 Carles Marti
                            disso_struct = dissociate_h(slab_molec, neigh_idx \
307 b4b2f307 Carles Marti
                                                        + num_atoms_slab,
308 b4b2f307 Carles Marti
                                                        num_atoms_slab)
309 b4b2f307 Carles Marti
                            if disso_struct is not None:
310 b4b2f307 Carles Marti
                                disso_structs.append(disso_struct)
311 b4b2f307 Carles Marti
    return disso_structs
312 b4b2f307 Carles Marti
313 b4b2f307 Carles Marti
314 a260d04d Carles Marti
def correct_coll(molec, slab, ctr_coord, site_coord, num_pts,
315 5fb01677 Carles Marti
                 min_coll_height, norm_vect, slab_nghbs, molec_nghbs,
316 a581d3c1 Carles Marti
                 coll_coeff, height=2.5):
317 a260d04d Carles Marti
    # TODO Rethink this function
318 a260d04d Carles Marti
    """Tries to adsorb a molecule on a slab trying to avoid collisions by doing
319 a260d04d Carles Marti
    small rotations.
320 a260d04d Carles Marti

321 a260d04d Carles Marti
    @param molec: ase.Atoms object of the molecule to adsorb
322 a260d04d Carles Marti
    @param slab: ase.Atoms object of the surface on which to adsorb the
323 a260d04d Carles Marti
        molecule
324 a260d04d Carles Marti
    @param ctr_coord: The coordinates of the molecule to use as adsorption
325 a260d04d Carles Marti
        center.
326 a260d04d Carles Marti
    @param site_coord: The coordinates of the surface on which to adsorb the
327 a260d04d Carles Marti
        molecule
328 a260d04d Carles Marti
    @param num_pts: Number on which to sample Euler angles.
329 5fb01677 Carles Marti
    @param min_coll_height: The lowermost height for which to detect a collision
330 a260d04d Carles Marti
    @param norm_vect: The vector perpendicular to the surface.
331 a260d04d Carles Marti
    @param slab_nghbs: Number of neigbors in the surface.
332 a260d04d Carles Marti
    @param molec_nghbs: Number of neighbors in the molecule.
333 a260d04d Carles Marti
    @param coll_coeff: The coefficient that multiplies the covalent radius of
334 a260d04d Carles Marti
        atoms resulting in a distance that two atoms being closer to that is
335 a260d04d Carles Marti
        considered as atomic collision.
336 a581d3c1 Carles Marti
    @param height: Height on which to try adsorption
337 a260d04d Carles Marti
    @return collision: bool, whether the structure generated has collisions
338 a260d04d Carles Marti
        between slab and adsorbate.
339 a260d04d Carles Marti
    """
340 a260d04d Carles Marti
    from copy import deepcopy
341 a260d04d Carles Marti
    slab_num_atoms = len(slab)
342 a260d04d Carles Marti
    collision = True
343 a260d04d Carles Marti
    max_corr = 6  # Should be an even number
344 a260d04d Carles Marti
    d_angle = 180 / ((max_corr / 2.0) * num_pts)
345 a260d04d Carles Marti
    num_corr = 0
346 a260d04d Carles Marti
    while collision and num_corr <= max_corr:
347 a260d04d Carles Marti
        k = num_corr * (-1) ** num_corr
348 a260d04d Carles Marti
        slab_molec = deepcopy(slab)
349 a260d04d Carles Marti
        molec.euler_rotate(k * d_angle, k * d_angle / 2, k * d_angle,
350 a260d04d Carles Marti
                           center=ctr_coord)
351 a581d3c1 Carles Marti
        add_adsorbate(slab_molec, molec, site_coord, ctr_coord, height,
352 a260d04d Carles Marti
                      norm_vect=norm_vect)
353 5fb01677 Carles Marti
        collision = check_collision(slab_molec, slab_num_atoms, min_coll_height,
354 a260d04d Carles Marti
                                    norm_vect, slab_nghbs, molec_nghbs,
355 a260d04d Carles Marti
                                    coll_coeff)
356 a260d04d Carles Marti
        num_corr += 1
357 a260d04d Carles Marti
    return slab_molec, collision
358 a260d04d Carles Marti
359 1d22a086 Carles Marti
360 3ab0865c Carles Marti
def ads_euler(orig_molec, slab, ctr_coord, site_coord, num_pts,
361 b4b2f307 Carles Marti
              min_coll_height, coll_coeff, norm_vect, slab_nghbs, molec_nghbs,
362 b4b2f307 Carles Marti
              disso_atoms):
363 3ab0865c Carles Marti
    """Generates adsorbate-surface structures by sampling over Euler angles.
364 3ab0865c Carles Marti

365 3ab0865c Carles Marti
    This function generates a number of adsorbate-surface structures at
366 3ab0865c Carles Marti
    different orientations of the adsorbate sampled at multiple Euler (zxz)
367 3ab0865c Carles Marti
    angles.
368 3ab0865c Carles Marti
    @param orig_molec: ase.Atoms object of the molecule to adsorb
369 3ab0865c Carles Marti
    @param slab: ase.Atoms object of the surface on which to adsorb the molecule
370 3ab0865c Carles Marti
    @param ctr_coord: The coordinates of the molecule to use as adsorption
371 3ab0865c Carles Marti
        center.
372 3ab0865c Carles Marti
    @param site_coord: The coordinates of the surface on which to adsorb the
373 3ab0865c Carles Marti
        molecule
374 3ab0865c Carles Marti
    @param num_pts: Number on which to sample Euler angles.
375 5fb01677 Carles Marti
    @param min_coll_height: The lowermost height for which to detect a collision
376 3ab0865c Carles Marti
    @param coll_coeff: The coefficient that multiplies the covalent radius of
377 3ab0865c Carles Marti
        atoms resulting in a distance that two atoms being closer to that is
378 3ab0865c Carles Marti
        considered as atomic collision.
379 3ab0865c Carles Marti
    @param norm_vect: The vector perpendicular to the surface.
380 3ab0865c Carles Marti
    @param slab_nghbs: Number of neigbors in the surface.
381 3ab0865c Carles Marti
    @param molec_nghbs: Number of neighbors in the molecule.
382 b4b2f307 Carles Marti
    @param disso_atoms: List of atom types or atom numbers to try to dissociate.
383 3ab0865c Carles Marti
    @return: list of ase.Atoms object conatining all the orientations of a given
384 3ab0865c Carles Marti
        conformer
385 3ab0865c Carles Marti
    """
386 3ab0865c Carles Marti
    from copy import deepcopy
387 3ab0865c Carles Marti
    slab_ads_list = []
388 3ab0865c Carles Marti
    # rotation around z
389 3ab0865c Carles Marti
    for alpha in np.arange(0, 360, 360 / num_pts):
390 3ab0865c Carles Marti
        # rotation around x'
391 3ab0865c Carles Marti
        for beta in np.arange(0, 180, 180 / num_pts):
392 3ab0865c Carles Marti
            # rotation around z'
393 3ab0865c Carles Marti
            for gamma in np.arange(0, 360, 360 / num_pts):
394 3ab0865c Carles Marti
                molec = deepcopy(orig_molec)
395 3ab0865c Carles Marti
                molec.euler_rotate(alpha, beta, gamma, center=ctr_coord)
396 3ab0865c Carles Marti
                slab_molec, collision = correct_coll(molec, slab,
397 5fb01677 Carles Marti
                                                     ctr_coord, site_coord,
398 5fb01677 Carles Marti
                                                     num_pts, min_coll_height,
399 5fb01677 Carles Marti
                                                     norm_vect,
400 5fb01677 Carles Marti
                                                     slab_nghbs, molec_nghbs,
401 5fb01677 Carles Marti
                                                     coll_coeff)
402 b2ffd8c5 Carles Marti
                if not collision and \
403 b2ffd8c5 Carles Marti
                        not any([(slab_molec.positions == conf.positions).all()
404 b2ffd8c5 Carles Marti
                                 for conf in slab_ads_list]):
405 3ab0865c Carles Marti
                    slab_ads_list.append(slab_molec)
406 b4b2f307 Carles Marti
                    slab_ads_list.extend(dissociation(slab_molec, disso_atoms,
407 b4b2f307 Carles Marti
                                                      len(slab)))
408 3ab0865c Carles Marti
409 3ab0865c Carles Marti
    return slab_ads_list
410 f3d1e601 Carles Marti
411 f3d1e601 Carles Marti
412 f3d1e601 Carles Marti
def ads_chemcat(site, ctr, pts_angle):
413 a5cc42ff Carles Marti
    return "TO IMPLEMENT"
414 f3d1e601 Carles Marti
415 f3d1e601 Carles Marti
416 bb55f47c Carles Marti
def adsorb_confs(conf_list, surf, molec_ctrs, sites, algo, num_pts, neigh_ctrs,
417 b4b2f307 Carles Marti
                 norm_vect, min_coll_height, coll_coeff, disso_atoms):
418 a5cc42ff Carles Marti
    """Generates a number of adsorbate-surface structure coordinates.
419 a5cc42ff Carles Marti

420 a5cc42ff Carles Marti
    Given a list of conformers, a surface, a list of atom indices (or list of
421 a5cc42ff Carles Marti
    list of indices) of both the surface and the adsorbate, it generates a
422 a5cc42ff Carles Marti
    number of adsorbate-surface structures for every possible combination of
423 a5cc42ff Carles Marti
    them at different orientations.
424 a5cc42ff Carles Marti
    @param conf_list: list of ase.Atoms of the different conformers
425 a5cc42ff Carles Marti
    @param surf: the ase.Atoms object of the surface
426 bb55f47c Carles Marti
    @param molec_ctrs: the list atom indices of the adsorbate.
427 a5cc42ff Carles Marti
    @param sites: the list of atom indices of the surface.
428 a5cc42ff Carles Marti
    @param algo: the algorithm to use for the generation of adsorbates.
429 a5cc42ff Carles Marti
    @param num_pts: the number of points per angle orientation to sample
430 a5cc42ff Carles Marti
    @param neigh_ctrs: the indices of the neighboring atoms to the adsorption
431 bb55f47c Carles Marti
        atoms.
432 1d22a086 Carles Marti
    @param norm_vect: The vector perpendicular to the surface.
433 5fb01677 Carles Marti
    @param min_coll_height: The lowermost height for which to detect a collision
434 bb55f47c Carles Marti
    @param coll_coeff: The coefficient that multiplies the covalent radius of
435 bb55f47c Carles Marti
        atoms resulting in a distance that two atoms being closer to that is
436 bb55f47c Carles Marti
        considered as atomic collision.
437 b4b2f307 Carles Marti
    @param disso_atoms: List of atom types or atom numbers to try to dissociate.
438 a5cc42ff Carles Marti
    @return: list of ase.Atoms for the adsorbate-surface structures
439 a5cc42ff Carles Marti
    """
440 bb55f47c Carles Marti
    from ase.neighborlist import natural_cutoffs, neighbor_list
441 f3d1e601 Carles Marti
    surf_ads_list = []
442 f3d1e601 Carles Marti
    sites_coords = get_atom_coords(surf, sites)
443 3ccf0131 Carles Marti
    if min_coll_height is False:
444 5fb01677 Carles Marti
        surf_cutoffs = natural_cutoffs(surf, mult=coll_coeff)
445 5fb01677 Carles Marti
        surf_nghbs = len(neighbor_list("i", surf, surf_cutoffs))
446 5fb01677 Carles Marti
    else:
447 5fb01677 Carles Marti
        surf_nghbs = 0
448 f3d1e601 Carles Marti
    for conf in conf_list:
449 bb55f47c Carles Marti
        molec_ctr_coords = get_atom_coords(conf, molec_ctrs)
450 f3d1e601 Carles Marti
        molec_neigh_coords = get_atom_coords(conf, neigh_ctrs)
451 3ccf0131 Carles Marti
        if min_coll_height is False:
452 5fb01677 Carles Marti
            conf_cutoffs = natural_cutoffs(conf, mult=coll_coeff)
453 5fb01677 Carles Marti
            molec_nghbs = len(neighbor_list("i", conf, conf_cutoffs))
454 5fb01677 Carles Marti
        else:
455 5fb01677 Carles Marti
            molec_nghbs = 0
456 f3d1e601 Carles Marti
        for site in sites_coords:
457 bb55f47c Carles Marti
            for ctr in molec_ctr_coords:
458 f3d1e601 Carles Marti
                if algo == 'euler':
459 bb55f47c Carles Marti
                    surf_ads_list.extend(ads_euler(conf, surf, ctr, site,
460 5fb01677 Carles Marti
                                                   num_pts, min_coll_height,
461 bb55f47c Carles Marti
                                                   coll_coeff, norm_vect,
462 b4b2f307 Carles Marti
                                                   surf_nghbs, molec_nghbs,
463 b4b2f307 Carles Marti
                                                   disso_atoms))
464 f3d1e601 Carles Marti
                elif algo == 'chemcat':
465 bb55f47c Carles Marti
                    surf_ads_list.extend(ads_chemcat(site, ctr, num_pts))
466 f3d1e601 Carles Marti
    return surf_ads_list
467 f3d1e601 Carles Marti
468 f3d1e601 Carles Marti
469 4614bb6a Carles
def run_screening(inp_vars):
470 91ae8d86 Carles Marti
    """Carries out the screening of adsorbate structures on a surface.
471 e07c09eb Carles

472 e07c09eb Carles
    @param inp_vars: Calculation parameters from input file.
473 e07c09eb Carles
    """
474 e07c09eb Carles
    import os
475 57e3a8c7 Carles Marti
    import random
476 fd2384fc Carles Marti
    from modules.formats import collect_coords, adapt_format
477 cf8fe0e3 Carles Marti
    from modules.calculation import run_calc, check_finished_calcs
478 e07c09eb Carles
479 e07c09eb Carles
    if not os.path.isdir("isolated"):
480 e07c09eb Carles
        err = "'isolated' directory not found. It is needed in order to carry "
481 e07c09eb Carles
        "out the screening of structures to be adsorbed"
482 e07c09eb Carles
        logger.error(err)
483 cf8fe0e3 Carles Marti
        raise FileNotFoundError(err)
484 e07c09eb Carles
485 14f39d2a Carles Marti
    logger.info('Carrying out procedures for the screening of adsorbate-surface'
486 14f39d2a Carles Marti
                ' structures.')
487 cf8fe0e3 Carles Marti
488 cf8fe0e3 Carles Marti
    finished_calcs, unfinished_calcs = check_finished_calcs('isolated',
489 cf8fe0e3 Carles Marti
                                                            inp_vars['code'])
490 cf8fe0e3 Carles Marti
    if len(unfinished_calcs) == 0:
491 cf8fe0e3 Carles Marti
        logger.info(f"Found {len(finished_calcs)} structures of isolated "
492 cf8fe0e3 Carles Marti
                    f"conformers.")
493 cf8fe0e3 Carles Marti
    else:
494 cf8fe0e3 Carles Marti
        logger.info(f"Found {len(finished_calcs)} structures of isolated "
495 cf8fe0e3 Carles Marti
                    f"conformers whose calculation finished normally.")
496 cf8fe0e3 Carles Marti
        logger.warning(f"Found {len(unfinished_calcs)} calculations more that "
497 cf8fe0e3 Carles Marti
                       f"did not finish normally: {unfinished_calcs}. Using "
498 cf8fe0e3 Carles Marti
                       f"only the {len(finished_calcs)} ones who finished "
499 cf8fe0e3 Carles Marti
                       f"normally")
500 cf8fe0e3 Carles Marti
501 fd2384fc Carles Marti
    conformer_atoms_list = collect_coords(finished_calcs, inp_vars['code'],
502 fd2384fc Carles Marti
                                          'isolated', inp_vars['special_atoms'])
503 1e9e784d Carles Marti
    selected_confs = select_confs(conformer_atoms_list, finished_calcs,
504 fd2384fc Carles Marti
                                  inp_vars['select_magns'],
505 bfe93f0d Carles Marti
                                  inp_vars['confs_per_magn'],
506 bfe93f0d Carles Marti
                                  inp_vars['code'])
507 90819cc3 Carles Marti
    surf = adapt_format('ase', inp_vars['surf_file'], inp_vars['special_atoms'])
508 bfe93f0d Carles Marti
    surf_ads_list = adsorb_confs(selected_confs, surf,
509 bfe93f0d Carles Marti
                                 inp_vars['molec_ads_ctrs'], inp_vars['sites'],
510 bfe93f0d Carles Marti
                                 inp_vars['ads_algo'],
511 f3d1e601 Carles Marti
                                 inp_vars['sample_points_per_angle'],
512 1d22a086 Carles Marti
                                 inp_vars['molec_neigh_ctrs'],
513 dadc6016 Carles Marti
                                 inp_vars['surf_norm_vect'],
514 5fb01677 Carles Marti
                                 inp_vars['min_coll_height'],
515 b4b2f307 Carles Marti
                                 inp_vars['collision_threshold'],
516 b4b2f307 Carles Marti
                                 inp_vars['disso_atoms'])
517 7d97341d Carles Marti
    if len(surf_ads_list) > inp_vars['max_structures']:
518 57e3a8c7 Carles Marti
        surf_ads_list = random.sample(surf_ads_list, inp_vars['max_structures'])
519 bfe93f0d Carles Marti
    logger.info(f'Generated {len(surf_ads_list)} adsorbate-surface atomic '
520 d9167fea Carles Marti
                f'configurations to carry out a calculation of.')
521 d9167fea Carles Marti
522 f3d1e601 Carles Marti
    run_calc('screening', inp_vars, surf_ads_list)
523 14f39d2a Carles Marti
    logger.info('Finished the procedures for the screening of adsorbate-surface'
524 14f39d2a Carles Marti
                ' structures section.')