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

dockonsurf / modules / screening.py @ b516a1d6

Historique | Voir | Annoter | Télécharger (23,76 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 e1c5f171 Carles Marti
def select_confs(orig_conf_list: list, calc_dirs: list, magns: list,
13 e1c5f171 Carles Marti
                 num_sel: int, code: str):
14 bfe93f0d Carles Marti
    """Takes a list ase.Atoms and selects the most different magnitude-wise.
15 bfe93f0d Carles Marti

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

72 b4aef3d7 Carles Marti
    @param v1: The first vector.
73 b4aef3d7 Carles Marti
    @param v2: The second vector.
74 b516a1d6 Carles Marti
    @param ref: Orthogonal vector to both v1 and v2,
75 b516a1d6 Carles Marti
        along which the sign of the rotation is defined (i.e. positive if
76 b516a1d6 Carles Marti
        counterclockwise angle when facing ref)
77 b4aef3d7 Carles Marti
    @param degrees: Whether the result should be in radians (True) or in
78 b4aef3d7 Carles Marti
        degrees (False).
79 b4aef3d7 Carles Marti
    @return: The angle in radians if degrees = False, or in degrees if
80 b4aef3d7 Carles Marti
        degrees =True
81 b4aef3d7 Carles Marti
    """
82 b4aef3d7 Carles Marti
    v1_u = v1 / np.linalg.norm(v1)
83 b4aef3d7 Carles Marti
    v2_u = v2 / np.linalg.norm(v2)
84 b4aef3d7 Carles Marti
    angle = np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))
85 b516a1d6 Carles Marti
    if ref is not None:
86 b516a1d6 Carles Marti
        # Give sign according to ref direction
87 b516a1d6 Carles Marti
        angle *= (1 if np.dot(np.cross(v1, v2), ref) >= 0 else -1)
88 b516a1d6 Carles Marti
89 b4aef3d7 Carles Marti
    return angle if not degrees else angle * 180 / np.pi
90 b4aef3d7 Carles Marti
91 b4aef3d7 Carles Marti
92 12a12bdd Carles Marti
def vect_avg(vects):
93 a5cc42ff Carles Marti
    """Computes the element-wise mean of a set of vectors.
94 12a12bdd Carles Marti

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

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

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

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

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

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

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

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

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

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