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

dockonsurf / modules / screening.py @ e5faf87a

Historique | Voir | Annoter | Télécharger (47,44 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 7dd94df7 Carles Marti
def get_vect_angle(v1: list, v2: list, ref=None, degrees=True):
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 12a12bdd Carles Marti
            logger.error(err)
141 12a12bdd Carles Marti
            raise ValueError
142 12a12bdd Carles Marti
    return np.array(coords)
143 f3d1e601 Carles Marti
144 f3d1e601 Carles Marti
145 d6da8693 Carles Marti
def compute_norm_vect(atoms, idxs, cell):
146 d6da8693 Carles Marti
    """Computes the local normal vector of a surface at a given site.
147 d6da8693 Carles Marti

148 d6da8693 Carles Marti
    Given an ase.Atoms object and a site defined as a linear combination of
149 d6da8693 Carles Marti
    atoms it computes the vector perpendicular to the surface, considering the
150 d6da8693 Carles Marti
    local environment of the site.
151 d6da8693 Carles Marti
    @param atoms: ase.Atoms object of the surface.
152 d6da8693 Carles Marti
    @param idxs: list or int: Index or list of indices of the atom/s that define
153 d6da8693 Carles Marti
        the site
154 d6da8693 Carles Marti
    @param cell: Unit cell. A 3x3 matrix (the three unit cell vectors)
155 d6da8693 Carles Marti
    @return: numpy.ndarray of the coordinates of the vector locally
156 d6da8693 Carles Marti
    perpendicular to the surface.
157 d6da8693 Carles Marti
    """
158 d6da8693 Carles Marti
    from ASANN import coordination_numbers as coord_nums
159 d6da8693 Carles Marti
    if isinstance(idxs, list):
160 d6da8693 Carles Marti
        atm_vect = [-np.round(coord_nums(atoms.get_scaled_positions(),
161 d6da8693 Carles Marti
                                         pbc=np.any(cell),
162 d6da8693 Carles Marti
                                         cell_vectors=cell)[3][i], 2)
163 d6da8693 Carles Marti
                    for i in idxs]
164 d6da8693 Carles Marti
        norm_vec = vect_avg([vect / np.linalg.norm(vect) for vect in atm_vect])
165 d6da8693 Carles Marti
    elif isinstance(idxs, int):
166 d6da8693 Carles Marti
        norm_vec = -coord_nums(atoms.get_scaled_positions(),
167 d6da8693 Carles Marti
                               pbc=np.any(cell),
168 d6da8693 Carles Marti
                               cell_vectors=cell)[3][idxs]
169 d6da8693 Carles Marti
    else:
170 d6da8693 Carles Marti
        err = "'idxs' must be either an int or a list"
171 d6da8693 Carles Marti
        logger.error(err)
172 d6da8693 Carles Marti
        raise ValueError(err)
173 587dca22 Carles Marti
    norm_vec = np.round(norm_vec, 2) / np.linalg.norm(np.round(norm_vec, 2))
174 587dca22 Carles Marti
    logger.info(f"The perpendicular vector to the surface at site '{idxs}' is "
175 587dca22 Carles Marti
                f"{norm_vec}")
176 587dca22 Carles Marti
    return norm_vec
177 d6da8693 Carles Marti
178 d6da8693 Carles Marti
179 4918e2ad Carles Marti
def align_molec(molec, ctr_coord, ref_vect):
180 4918e2ad Carles Marti
    """Align a molecule to a vector by a center.
181 4918e2ad Carles Marti

182 4918e2ad Carles Marti
    Given a reference vector to be aligned to and some coordinates acting as
183 4918e2ad Carles Marti
    alignment center, it first averages the vectors pointing to neighboring
184 4918e2ad Carles Marti
    atoms and then tries to align this average vector to the target. If the
185 4918e2ad Carles Marti
    average vector is 0 it takes the vector to the nearest neighbor.
186 4918e2ad Carles Marti
    @param molec: The molecule to alugn
187 4918e2ad Carles Marti
    @param ctr_coord: The coordinates to use ase alignment center.
188 4918e2ad Carles Marti
    @param ref_vect: The vector to be aligned with.
189 4918e2ad Carles Marti
    @return: ase.Atoms of the aligned molecule
190 4918e2ad Carles Marti
    """
191 4918e2ad Carles Marti
    from ase import Atom
192 4918e2ad Carles Marti
    from ase.neighborlist import natural_cutoffs, neighbor_list
193 4918e2ad Carles Marti
194 4918e2ad Carles Marti
    if len(molec) == 1:
195 4918e2ad Carles Marti
        err_msg = "Cannot align a single atom"
196 4918e2ad Carles Marti
        logger.error(err_msg)
197 4918e2ad Carles Marti
        ValueError(err_msg)
198 4918e2ad Carles Marti
    cutoffs = natural_cutoffs(molec, mult=1.2)
199 4918e2ad Carles Marti
    # Check if ctr_coord are the coordinates of an atom and if not creates a
200 4918e2ad Carles Marti
    # dummy one to extract the neighboring atoms.
201 4918e2ad Carles Marti
    ctr_idx = None
202 f16ebc80 Carles Marti
    dummy_atom = False
203 4918e2ad Carles Marti
    for atom in molec:
204 4918e2ad Carles Marti
        if np.allclose(ctr_coord, atom.position, rtol=1e-2):
205 4918e2ad Carles Marti
            ctr_idx = atom.index
206 4918e2ad Carles Marti
            break
207 4918e2ad Carles Marti
    if ctr_idx is None:
208 4918e2ad Carles Marti
        molec.append(Atom('X', position=ctr_coord))
209 4918e2ad Carles Marti
        cutoffs.append(0.2)
210 4918e2ad Carles Marti
        ctr_idx = len(molec) - 1
211 f16ebc80 Carles Marti
        dummy_atom = True
212 4918e2ad Carles Marti
    # Builds the neighbors and computes the average vector
213 4918e2ad Carles Marti
    refs, vects = neighbor_list("iD", molec, cutoffs, self_interaction=False)
214 4918e2ad Carles Marti
    neigh_vects = [vects[i] for i, atm in enumerate(refs) if atm == ctr_idx]
215 4918e2ad Carles Marti
    # If no neighbors are present, the cutoff of the alignment center is
216 4918e2ad Carles Marti
    # set to a value where at least one atom is a neighbor and neighbors are
217 4918e2ad Carles Marti
    # recalculated.
218 4918e2ad Carles Marti
    if len(neigh_vects) == 0:
219 4918e2ad Carles Marti
        min_dist, min_idx = (np.inf, np.inf)
220 4918e2ad Carles Marti
        for atom in molec:
221 4918e2ad Carles Marti
            if atom.index == ctr_idx:
222 4918e2ad Carles Marti
                continue
223 e5faf87a Carles Marti
            if molec.get_distance(ctr_idx, atom.index) < min_dist:
224 4918e2ad Carles Marti
                min_dist = molec.get_distance(ctr_idx, atom.index)
225 4918e2ad Carles Marti
                min_idx = atom.index
226 4918e2ad Carles Marti
        cutoffs[ctr_idx] = min_dist - cutoffs[min_idx] + 0.05
227 4918e2ad Carles Marti
        refs, vects = neighbor_list("iD", molec, cutoffs,
228 4918e2ad Carles Marti
                                    self_interaction=False)
229 4918e2ad Carles Marti
        neigh_vects = [vects[i] for i, atm in enumerate(refs) if atm == ctr_idx]
230 4918e2ad Carles Marti
    target_vect = vect_avg(neigh_vects)
231 e5faf87a Carles Marti
    # If the target vector is 0 (the center is at the baricenter of its
232 e5faf87a Carles Marti
    # neighbors). Assuming the adsorption center is coplanar or colinear to its
233 e5faf87a Carles Marti
    # neighbors (it would not make a lot of sense to chose a center which is
234 e5faf87a Carles Marti
    # the baricenter of neighbors distributed in 3D), the target_vector is
235 e5faf87a Carles Marti
    # chosen perpendicular to the nearest neighbor.
236 e5faf87a Carles Marti
    if np.allclose(target_vect, 0, rtol=1e-3):
237 e5faf87a Carles Marti
        nn_vect = np.array([np.inf] * 3)
238 4918e2ad Carles Marti
        for vect in neigh_vects:
239 e5faf87a Carles Marti
            if np.linalg.norm(vect) < np.linalg.norm(nn_vect):
240 e5faf87a Carles Marti
                nn_vect = vect
241 e5faf87a Carles Marti
        cart_axes = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
242 e5faf87a Carles Marti
        axis = cart_axes[int(np.argmax([np.linalg.norm(np.cross(ax, nn_vect))
243 e5faf87a Carles Marti
                                        for ax in cart_axes]))]
244 e5faf87a Carles Marti
        target_vect = np.cross(axis, nn_vect)
245 4918e2ad Carles Marti
246 4918e2ad Carles Marti
    rot_vect = np.cross(target_vect, ref_vect)
247 4918e2ad Carles Marti
    if np.allclose(rot_vect, 0):
248 4918e2ad Carles Marti
        cart_axes = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
249 4918e2ad Carles Marti
        axis = cart_axes[int(np.argmax([np.linalg.norm(np.cross(ax, ref_vect))
250 4918e2ad Carles Marti
                                        for ax in cart_axes]))]
251 4918e2ad Carles Marti
        rot_vect = np.cross(ref_vect, axis)
252 4918e2ad Carles Marti
    rot_angle = -get_vect_angle(ref_vect, target_vect, rot_vect)
253 4918e2ad Carles Marti
    molec.rotate(rot_angle, rot_vect, ctr_coord)
254 f16ebc80 Carles Marti
    if dummy_atom:
255 f16ebc80 Carles Marti
        del molec[-1]
256 4918e2ad Carles Marti
    return molec
257 4918e2ad Carles Marti
258 4918e2ad Carles Marti
259 dadc6016 Carles Marti
def add_adsorbate(slab, adsorbate, site_coord, ctr_coord, height, offset=None,
260 1d22a086 Carles Marti
                  norm_vect=(0, 0, 1)):
261 1d22a086 Carles Marti
    """Add an adsorbate to a surface.
262 1d22a086 Carles Marti

263 1d22a086 Carles Marti
    This function extends the functionality of ase.build.add_adsorbate
264 1d22a086 Carles Marti
    (https://wiki.fysik.dtu.dk/ase/ase/build/surface.html#ase.build.add_adsorbate)
265 1d22a086 Carles Marti
    by enabling to change the z coordinate and the axis perpendicular to the
266 1d22a086 Carles Marti
    surface.
267 1d22a086 Carles Marti
    @param slab: ase.Atoms object containing the coordinates of the surface
268 1d22a086 Carles Marti
    @param adsorbate: ase.Atoms object containing the coordinates of the
269 1d22a086 Carles Marti
        adsorbate.
270 dadc6016 Carles Marti
    @param site_coord: The coordinates of the adsorption site on the surface.
271 dadc6016 Carles Marti
    @param ctr_coord: The coordinates of the adsorption center in the molecule.
272 dadc6016 Carles Marti
    @param height: The height above the surface where to adsorb.
273 1d22a086 Carles Marti
    @param offset: Offsets the adsorbate by a number of unit cells. Mostly
274 1d22a086 Carles Marti
        useful when adding more than one adsorbate.
275 1d22a086 Carles Marti
    @param norm_vect: The vector perpendicular to the surface.
276 1d22a086 Carles Marti
    """
277 36d92f4f Carles Marti
    from copy import deepcopy
278 1d22a086 Carles Marti
    info = slab.info.get('adsorbate_info', {})
279 dadc6016 Carles Marti
    pos = np.array([0.0, 0.0, 0.0])  # part of absolute coordinates
280 1d22a086 Carles Marti
    spos = np.array([0.0, 0.0, 0.0])  # part relative to unit cell
281 dadc6016 Carles Marti
    norm_vect_u = np.array(norm_vect) / np.linalg.norm(norm_vect)
282 1d22a086 Carles Marti
    if offset is not None:
283 1d22a086 Carles Marti
        spos += np.asarray(offset, float)
284 dadc6016 Carles Marti
    if isinstance(site_coord, str):
285 1d22a086 Carles Marti
        # A site-name:
286 1d22a086 Carles Marti
        if 'sites' not in info:
287 1d22a086 Carles Marti
            raise TypeError('If the atoms are not made by an ase.build '
288 1d22a086 Carles Marti
                            'function, position cannot be a name.')
289 dadc6016 Carles Marti
        if site_coord not in info['sites']:
290 dadc6016 Carles Marti
            raise TypeError('Adsorption site %s not supported.' % site_coord)
291 dadc6016 Carles Marti
        spos += info['sites'][site_coord]
292 1d22a086 Carles Marti
    else:
293 dadc6016 Carles Marti
        pos += site_coord
294 1d22a086 Carles Marti
    if 'cell' in info:
295 1d22a086 Carles Marti
        cell = info['cell']
296 1d22a086 Carles Marti
    else:
297 1d22a086 Carles Marti
        cell = slab.get_cell()
298 1d22a086 Carles Marti
    pos += np.dot(spos, cell)
299 1d22a086 Carles Marti
    # Convert the adsorbate to an Atoms object
300 1d22a086 Carles Marti
    if isinstance(adsorbate, ase.Atoms):
301 36d92f4f Carles Marti
        ads = deepcopy(adsorbate)
302 1d22a086 Carles Marti
    elif isinstance(adsorbate, ase.Atom):
303 1d22a086 Carles Marti
        ads = ase.Atoms([adsorbate])
304 1d22a086 Carles Marti
    else:
305 1d22a086 Carles Marti
        # Assume it is a string representing a single Atom
306 1d22a086 Carles Marti
        ads = ase.Atoms([ase.Atom(adsorbate)])
307 dadc6016 Carles Marti
    pos += height * norm_vect_u
308 1d22a086 Carles Marti
    # Move adsorbate into position
309 dadc6016 Carles Marti
    ads.translate(pos - ctr_coord)
310 1d22a086 Carles Marti
    # Attach the adsorbate
311 1d22a086 Carles Marti
    slab.extend(ads)
312 1d22a086 Carles Marti
313 1d22a086 Carles Marti
314 a4b57124 Carles Marti
def check_collision(slab_molec, slab_num_atoms, min_height, vect, nn_slab=0,
315 e8bebcca Carles Marti
                    nn_molec=0, coll_coeff=1.2):
316 5f3f4b69 Carles Marti
    """Checks whether a slab and a molecule collide or not.
317 5f3f4b69 Carles Marti

318 5f3f4b69 Carles Marti
    @param slab_molec: The system of adsorbate-slab for which to detect if there
319 5f3f4b69 Carles Marti
        are collisions.
320 5f3f4b69 Carles Marti
    @param nn_slab: Number of neigbors in the surface.
321 5f3f4b69 Carles Marti
    @param nn_molec: Number of neighbors in the molecule.
322 5f3f4b69 Carles Marti
    @param coll_coeff: The coefficient that multiplies the covalent radius of
323 5f3f4b69 Carles Marti
        atoms resulting in a distance that two atoms being closer to that is
324 5f3f4b69 Carles Marti
        considered as atomic collision.
325 5f3f4b69 Carles Marti
    @param slab_num_atoms: Number of atoms of the bare slab.
326 5f3f4b69 Carles Marti
    @param min_height: The minimum height atoms can have to not be considered as
327 5f3f4b69 Carles Marti
        colliding.
328 5f3f4b69 Carles Marti
    @param vect: The vector perpendicular to the slab.
329 5f3f4b69 Carles Marti
    @return: bool, whether the surface and the molecule collide.
330 e8bebcca Carles Marti
    """
331 5f3f4b69 Carles Marti
    from ase.neighborlist import natural_cutoffs, neighbor_list
332 e8bebcca Carles Marti
333 e8bebcca Carles Marti
    # Check structure overlap by height
334 5fb01677 Carles Marti
    if min_height is not False:
335 a4b57124 Carles Marti
        cart_axes = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0],
336 a4b57124 Carles Marti
                     [-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]]
337 e8bebcca Carles Marti
        if vect.tolist() not in cart_axes:
338 e8bebcca Carles Marti
            err_msg = "'min_coll_height' option is only implemented for " \
339 e8bebcca Carles Marti
                      "'surf_norm_vect' to be one of the x, y or z axes. "
340 e8bebcca Carles Marti
            logger.error(err_msg)
341 e8bebcca Carles Marti
            raise ValueError(err_msg)
342 e8bebcca Carles Marti
        for atom in slab_molec[slab_num_atoms:]:
343 e8bebcca Carles Marti
            for i, coord in enumerate(vect):
344 e8bebcca Carles Marti
                if coord == 0:
345 e8bebcca Carles Marti
                    continue
346 e8bebcca Carles Marti
                if atom.position[i] * coord < min_height * coord:
347 e8bebcca Carles Marti
                    return True
348 e8bebcca Carles Marti
349 e8bebcca Carles Marti
    # Check structure overlap by sphere collision
350 e8bebcca Carles Marti
    if coll_coeff is not False:
351 5fb01677 Carles Marti
        slab_molec_cutoffs = natural_cutoffs(slab_molec, mult=coll_coeff)
352 b4b2f307 Carles Marti
        slab_molec_nghbs = len(
353 b4b2f307 Carles Marti
            neighbor_list("i", slab_molec, slab_molec_cutoffs))
354 5fb01677 Carles Marti
        if slab_molec_nghbs > nn_slab + nn_molec:
355 5fb01677 Carles Marti
            return True
356 e8bebcca Carles Marti
357 e8bebcca Carles Marti
    return False
358 e8bebcca Carles Marti
359 e8bebcca Carles Marti
360 e8bebcca Carles Marti
def correct_coll(molec, slab, ctr_coord, site_coord, num_pts,
361 e8bebcca Carles Marti
                 min_coll_height, norm_vect, slab_nghbs, molec_nghbs,
362 e8bebcca Carles Marti
                 coll_coeff, height=2.5):
363 e8bebcca Carles Marti
    # TODO Rethink this function
364 e8bebcca Carles Marti
    """Tries to adsorb a molecule on a slab trying to avoid collisions by doing
365 e8bebcca Carles Marti
    small rotations.
366 e8bebcca Carles Marti

367 e8bebcca Carles Marti
    @param molec: ase.Atoms object of the molecule to adsorb
368 e8bebcca Carles Marti
    @param slab: ase.Atoms object of the surface on which to adsorb the
369 e8bebcca Carles Marti
        molecule
370 e8bebcca Carles Marti
    @param ctr_coord: The coordinates of the molecule to use as adsorption
371 e8bebcca Carles Marti
        center.
372 e8bebcca Carles Marti
    @param site_coord: The coordinates of the surface on which to adsorb the
373 e8bebcca Carles Marti
        molecule
374 e8bebcca Carles Marti
    @param num_pts: Number on which to sample Euler angles.
375 e8bebcca Carles Marti
    @param min_coll_height: The lowermost height for which to detect a collision
376 e8bebcca Carles Marti
    @param norm_vect: The vector perpendicular to the surface.
377 e8bebcca Carles Marti
    @param slab_nghbs: Number of neigbors in the surface.
378 e8bebcca Carles Marti
    @param molec_nghbs: Number of neighbors in the molecule.
379 e8bebcca Carles Marti
    @param coll_coeff: The coefficient that multiplies the covalent radius of
380 e8bebcca Carles Marti
        atoms resulting in a distance that two atoms being closer to that is
381 e8bebcca Carles Marti
        considered as atomic collision.
382 e8bebcca Carles Marti
    @param height: Height on which to try adsorption
383 e8bebcca Carles Marti
    @return collision: bool, whether the structure generated has collisions
384 e8bebcca Carles Marti
        between slab and adsorbate.
385 e8bebcca Carles Marti
    """
386 e8bebcca Carles Marti
    from copy import deepcopy
387 e8bebcca Carles Marti
    slab_num_atoms = len(slab)
388 e8bebcca Carles Marti
    slab_molec = []
389 e8bebcca Carles Marti
    collision = True
390 e8bebcca Carles Marti
    max_corr = 6  # Should be an even number
391 e8bebcca Carles Marti
    d_angle = 180 / ((max_corr / 2.0) * num_pts)
392 e8bebcca Carles Marti
    num_corr = 0
393 e8bebcca Carles Marti
    while collision and num_corr <= max_corr:
394 e8bebcca Carles Marti
        k = num_corr * (-1) ** num_corr
395 e8bebcca Carles Marti
        slab_molec = deepcopy(slab)
396 e8bebcca Carles Marti
        molec.euler_rotate(k * d_angle, k * d_angle / 2, k * d_angle,
397 e8bebcca Carles Marti
                           center=ctr_coord)
398 e8bebcca Carles Marti
        add_adsorbate(slab_molec, molec, site_coord, ctr_coord, height,
399 e8bebcca Carles Marti
                      norm_vect=norm_vect)
400 e8bebcca Carles Marti
        collision = check_collision(slab_molec, slab_num_atoms, min_coll_height,
401 e8bebcca Carles Marti
                                    norm_vect, slab_nghbs, molec_nghbs,
402 e8bebcca Carles Marti
                                    coll_coeff)
403 e8bebcca Carles Marti
        num_corr += 1
404 e8bebcca Carles Marti
    return slab_molec, collision
405 5f3f4b69 Carles Marti
406 5f3f4b69 Carles Marti
407 c25aa299 Carles Marti
def dissociate_h(slab_molec_orig, h_idx, num_atoms_slab, h_acceptor,
408 c25aa299 Carles Marti
                 neigh_cutoff=1):
409 b4b2f307 Carles Marti
    # TODO rethink
410 91ae8d86 Carles Marti
    """Tries to dissociate a H from the molecule and adsorbs it on the slab.
411 b4b2f307 Carles Marti

412 91ae8d86 Carles Marti
    Tries to dissociate a H atom from the molecule and adsorb in on top of the
413 91ae8d86 Carles Marti
    surface if the distance is shorter than two times the neigh_cutoff value.
414 b4b2f307 Carles Marti
    @param slab_molec_orig: The ase.Atoms object of the system adsorbate-slab.
415 b4b2f307 Carles Marti
    @param h_idx: The index of the hydrogen atom to carry out adsorption of.
416 b4b2f307 Carles Marti
    @param num_atoms_slab: The number of atoms of the slab without adsorbate.
417 c25aa299 Carles Marti
    @param h_acceptor: List of atom types or atom numbers that are H-acceptors.
418 b4b2f307 Carles Marti
    @param neigh_cutoff: half the maximum distance between the surface and the
419 b4b2f307 Carles Marti
        H for it to carry out dissociation.
420 b4b2f307 Carles Marti
    @return: An ase.Atoms object of the system adsorbate-surface with H
421 b4b2f307 Carles Marti
    """
422 b4b2f307 Carles Marti
    from copy import deepcopy
423 b4b2f307 Carles Marti
    from ase.neighborlist import NeighborList
424 b4b2f307 Carles Marti
    slab_molec = deepcopy(slab_molec_orig)
425 b4b2f307 Carles Marti
    cutoffs = len(slab_molec) * [neigh_cutoff]
426 c25aa299 Carles Marti
    nl = NeighborList(cutoffs, self_interaction=False, bothways=True, skin=0.0)
427 b4b2f307 Carles Marti
    nl.update(slab_molec)
428 b4b2f307 Carles Marti
    surf_h_vect = np.array([np.infty] * 3)
429 c25aa299 Carles Marti
    if h_acceptor == 'all':
430 c25aa299 Carles Marti
        h_acceptor = list(range(num_atoms_slab))
431 b4b2f307 Carles Marti
    for neigh_idx in nl.get_neighbors(h_idx)[0]:
432 c25aa299 Carles Marti
        for elm in h_acceptor:
433 c25aa299 Carles Marti
            if isinstance(elm, int):
434 c25aa299 Carles Marti
                if neigh_idx == elm and neigh_idx < num_atoms_slab:
435 c25aa299 Carles Marti
                    dist = np.linalg.norm(slab_molec[neigh_idx].position -
436 c25aa299 Carles Marti
                                          slab_molec[h_idx].position)
437 c25aa299 Carles Marti
                    if dist < np.linalg.norm(surf_h_vect):
438 c25aa299 Carles Marti
                        surf_h_vect = slab_molec[neigh_idx].position \
439 c25aa299 Carles Marti
                                      - slab_molec[h_idx].position
440 c25aa299 Carles Marti
            else:
441 c25aa299 Carles Marti
                if slab_molec[neigh_idx].symbol == elm \
442 c25aa299 Carles Marti
                        and neigh_idx < num_atoms_slab:
443 c25aa299 Carles Marti
                    dist = np.linalg.norm(slab_molec[neigh_idx].position -
444 c25aa299 Carles Marti
                                          slab_molec[h_idx].position)
445 c25aa299 Carles Marti
                    if dist < np.linalg.norm(surf_h_vect):
446 c25aa299 Carles Marti
                        surf_h_vect = slab_molec[neigh_idx].position \
447 c25aa299 Carles Marti
                                      - slab_molec[h_idx].position
448 c25aa299 Carles Marti
449 b4b2f307 Carles Marti
    if np.linalg.norm(surf_h_vect) != np.infty:
450 b4b2f307 Carles Marti
        trans_vect = surf_h_vect - surf_h_vect / np.linalg.norm(surf_h_vect)
451 b4b2f307 Carles Marti
        slab_molec[h_idx].position = slab_molec[h_idx].position + trans_vect
452 b4b2f307 Carles Marti
        return slab_molec
453 b4b2f307 Carles Marti
454 b4b2f307 Carles Marti
455 c25aa299 Carles Marti
def dissociation(slab_molec, h_donor, h_acceptor, num_atoms_slab):
456 b4b2f307 Carles Marti
    # TODO multiple dissociation
457 b4b2f307 Carles Marti
    """Decides which H atoms to dissociate according to a list of atoms.
458 b4b2f307 Carles Marti

459 b4b2f307 Carles Marti
    Given a list of chemical symbols or atom indices it checks for every atom
460 b4b2f307 Carles Marti
    or any of its neighbor if it's a H and calls dissociate_h to try to carry
461 b4b2f307 Carles Marti
    out dissociation of that H. For atom indices, it checks both whether
462 b4b2f307 Carles Marti
    the atom index or its neighbors are H, for chemical symbols, it only checks
463 b4b2f307 Carles Marti
    if there is a neighbor H.
464 b4b2f307 Carles Marti
    @param slab_molec: The ase.Atoms object of the system adsorbate-slab.
465 c25aa299 Carles Marti
    @param h_donor: List of atom types or atom numbers that are H-donors.
466 c25aa299 Carles Marti
    @param h_acceptor: List of atom types or atom numbers that are H-acceptors.
467 c25aa299 Carles Marti
    @param num_atoms_slab: Number of atoms of the bare slab.
468 b4b2f307 Carles Marti
    @return:
469 b4b2f307 Carles Marti
    """
470 b4b2f307 Carles Marti
    from ase.neighborlist import natural_cutoffs, NeighborList
471 b4b2f307 Carles Marti
    molec = slab_molec[num_atoms_slab:]
472 b4b2f307 Carles Marti
    cutoffs = natural_cutoffs(molec)
473 b4b2f307 Carles Marti
    nl = NeighborList(cutoffs, self_interaction=False, bothways=True)
474 b4b2f307 Carles Marti
    nl.update(molec)
475 b4b2f307 Carles Marti
    disso_structs = []
476 c25aa299 Carles Marti
    for el in h_donor:
477 b4b2f307 Carles Marti
        if isinstance(el, int):
478 b4b2f307 Carles Marti
            if molec[el].symbol == 'H':
479 b4b2f307 Carles Marti
                disso_struct = dissociate_h(slab_molec, el + num_atoms_slab,
480 c25aa299 Carles Marti
                                            num_atoms_slab, h_acceptor)
481 b4b2f307 Carles Marti
                if disso_struct is not None:
482 b4b2f307 Carles Marti
                    disso_structs.append(disso_struct)
483 b4b2f307 Carles Marti
            else:
484 b4b2f307 Carles Marti
                for neigh_idx in nl.get_neighbors(el)[0]:
485 b4b2f307 Carles Marti
                    if molec[neigh_idx].symbol == 'H':
486 b4b2f307 Carles Marti
                        disso_struct = dissociate_h(slab_molec, neigh_idx +
487 b4b2f307 Carles Marti
                                                    num_atoms_slab,
488 c25aa299 Carles Marti
                                                    num_atoms_slab, h_acceptor)
489 b4b2f307 Carles Marti
                        if disso_struct is not None:
490 b4b2f307 Carles Marti
                            disso_structs.append(disso_struct)
491 b4b2f307 Carles Marti
        else:
492 b4b2f307 Carles Marti
            for atom in molec:
493 b4b2f307 Carles Marti
                if atom.symbol.lower() == el.lower():
494 b4b2f307 Carles Marti
                    for neigh_idx in nl.get_neighbors(atom.index)[0]:
495 b4b2f307 Carles Marti
                        if molec[neigh_idx].symbol == 'H':
496 5261a07f Carles Marti
                            disso_struct = dissociate_h(slab_molec, neigh_idx
497 b4b2f307 Carles Marti
                                                        + num_atoms_slab,
498 c25aa299 Carles Marti
                                                        num_atoms_slab,
499 c25aa299 Carles Marti
                                                        h_acceptor)
500 b4b2f307 Carles Marti
                            if disso_struct is not None:
501 b4b2f307 Carles Marti
                                disso_structs.append(disso_struct)
502 b4b2f307 Carles Marti
    return disso_structs
503 b4b2f307 Carles Marti
504 b4b2f307 Carles Marti
505 3ab0865c Carles Marti
def ads_euler(orig_molec, slab, ctr_coord, site_coord, num_pts,
506 b4b2f307 Carles Marti
              min_coll_height, coll_coeff, norm_vect, slab_nghbs, molec_nghbs,
507 c25aa299 Carles Marti
              h_donor, h_acceptor):
508 3ab0865c Carles Marti
    """Generates adsorbate-surface structures by sampling over Euler angles.
509 3ab0865c Carles Marti

510 3ab0865c Carles Marti
    This function generates a number of adsorbate-surface structures at
511 3ab0865c Carles Marti
    different orientations of the adsorbate sampled at multiple Euler (zxz)
512 3ab0865c Carles Marti
    angles.
513 5261a07f Carles Marti
    @param orig_molec: ase.Atoms object of the molecule to adsorb.
514 5261a07f Carles Marti
    @param slab: ase.Atoms object of the surface on which to adsorb the
515 5261a07f Carles Marti
        molecule.
516 3ab0865c Carles Marti
    @param ctr_coord: The coordinates of the molecule to use as adsorption
517 3ab0865c Carles Marti
        center.
518 3ab0865c Carles Marti
    @param site_coord: The coordinates of the surface on which to adsorb the
519 5261a07f Carles Marti
        molecule.
520 3ab0865c Carles Marti
    @param num_pts: Number on which to sample Euler angles.
521 5261a07f Carles Marti
    @param min_coll_height: The lowest height for which to detect a collision.
522 3ab0865c Carles Marti
    @param coll_coeff: The coefficient that multiplies the covalent radius of
523 3ab0865c Carles Marti
        atoms resulting in a distance that two atoms being closer to that is
524 3ab0865c Carles Marti
        considered as atomic collision.
525 3ab0865c Carles Marti
    @param norm_vect: The vector perpendicular to the surface.
526 3ab0865c Carles Marti
    @param slab_nghbs: Number of neigbors in the surface.
527 3ab0865c Carles Marti
    @param molec_nghbs: Number of neighbors in the molecule.
528 c25aa299 Carles Marti
    @param h_donor: List of atom types or atom numbers that are H-donors.
529 c25aa299 Carles Marti
    @param h_acceptor: List of atom types or atom numbers that are H-acceptors.
530 3ab0865c Carles Marti
    @return: list of ase.Atoms object conatining all the orientations of a given
531 5261a07f Carles Marti
        conformer.
532 3ab0865c Carles Marti
    """
533 3ab0865c Carles Marti
    from copy import deepcopy
534 3ab0865c Carles Marti
    slab_ads_list = []
535 e5faf87a Carles Marti
    orig_molec = align_molec(orig_molec, ctr_coord, norm_vect)
536 3ab0865c Carles Marti
    # rotation around z
537 3ab0865c Carles Marti
    for alpha in np.arange(0, 360, 360 / num_pts):
538 3ab0865c Carles Marti
        # rotation around x'
539 3ab0865c Carles Marti
        for beta in np.arange(0, 180, 180 / num_pts):
540 3ab0865c Carles Marti
            # rotation around z'
541 3ab0865c Carles Marti
            for gamma in np.arange(0, 360, 360 / num_pts):
542 3ab0865c Carles Marti
                molec = deepcopy(orig_molec)
543 3ab0865c Carles Marti
                molec.euler_rotate(alpha, beta, gamma, center=ctr_coord)
544 3ab0865c Carles Marti
                slab_molec, collision = correct_coll(molec, slab,
545 5fb01677 Carles Marti
                                                     ctr_coord, site_coord,
546 5fb01677 Carles Marti
                                                     num_pts, min_coll_height,
547 5fb01677 Carles Marti
                                                     norm_vect,
548 5fb01677 Carles Marti
                                                     slab_nghbs, molec_nghbs,
549 5fb01677 Carles Marti
                                                     coll_coeff)
550 5261a07f Carles Marti
                if not collision and not any([np.allclose(slab_molec.positions,
551 5261a07f Carles Marti
                                                          conf.positions)
552 5261a07f Carles Marti
                                              for conf in slab_ads_list]):
553 3ab0865c Carles Marti
                    slab_ads_list.append(slab_molec)
554 c25aa299 Carles Marti
                    if h_donor is not False:
555 c25aa299 Carles Marti
                        slab_ads_list.extend(dissociation(slab_molec, h_donor,
556 c25aa299 Carles Marti
                                                          h_acceptor,
557 c25aa299 Carles Marti
                                                          len(slab)))
558 3ab0865c Carles Marti
559 3ab0865c Carles Marti
    return slab_ads_list
560 f3d1e601 Carles Marti
561 f3d1e601 Carles Marti
562 7dd94df7 Carles Marti
def chemcat_rotate(molecule, surf, ctr1_mol, ctr2_mol, ctr3_mol, ctr1_surf,
563 7dd94df7 Carles Marti
                   ctr2_surf, bond_vector, bond_angle_target,
564 7dd94df7 Carles Marti
                   dihedral_angle_target=None, mol_dihedral_angle_target=None):
565 7dd94df7 Carles Marti
    """Performs translation and rotation of an adsorbate defined by an
566 7dd94df7 Carles Marti
    adsorption bond length, direction, angle and dihedral angle
567 7dd94df7 Carles Marti

568 7dd94df7 Carles Marti
    Carles modification of chemcat's transform_adsorbate to work with
569 7dd94df7 Carles Marti
    coordinates instead of ase.Atom
570 7dd94df7 Carles Marti
    Parameters:
571 7dd94df7 Carles Marti
        molecule (ase.Atoms): The molecule to adsorb.
572 7dd94df7 Carles Marti

573 7dd94df7 Carles Marti
        surf (ase.Atoms): The surface ontop of which to adsorb.
574 7dd94df7 Carles Marti

575 7dd94df7 Carles Marti
        ctr1_mol (int/list): The position of the adsorption center in the
576 7dd94df7 Carles Marti
        molecule that will be bound to the surface.
577 7dd94df7 Carles Marti

578 7dd94df7 Carles Marti
        ctr2_mol (int/list): The position of a second center of the
579 7dd94df7 Carles Marti
        adsorbate used to define the adsorption bond angle, and the dihedral
580 7dd94df7 Carles Marti
        adsorption angle.
581 7dd94df7 Carles Marti

582 7dd94df7 Carles Marti
        ctr3_mol (int/list): The position of a third center in the
583 7dd94df7 Carles Marti
        adsorbate used to define the adsorbate dihedral angle.
584 7dd94df7 Carles Marti

585 7dd94df7 Carles Marti
        ctr1_surf (int/list): The position of the site on the surface that
586 7dd94df7 Carles Marti
        will be bound to the molecule.
587 7dd94df7 Carles Marti

588 7dd94df7 Carles Marti
        ctr2_surf (int/list): The position of a second center of the
589 7dd94df7 Carles Marti
        surface used to define the dihedral adsorption angle.
590 7dd94df7 Carles Marti

591 7dd94df7 Carles Marti
        bond_vector (numpy.ndarray): The adsorption bond desired.
592 7dd94df7 Carles Marti
            Details: offset = vect(atom1_surf, atom1_mol)
593 7dd94df7 Carles Marti

594 7dd94df7 Carles Marti
        bond_angle_target (float or int): The adsorption bond angle desired (in
595 7dd94df7 Carles Marti
            degrees).
596 7dd94df7 Carles Marti
            Details: bond_angle_target = angle(atom1_surf, atom1_mol, atom2_mol)
597 7dd94df7 Carles Marti

598 7dd94df7 Carles Marti
        dihedral_angle_target (float or int): The dihedral adsorption angle
599 7dd94df7 Carles Marti
            desired (in degrees).
600 7dd94df7 Carles Marti
            Details: dihedral_angle_target = dihedral(atom2_surf, atom1_surf,
601 7dd94df7 Carles Marti
            atom1_mol, atom2_mol)
602 7dd94df7 Carles Marti
                The rotation vector is facing the adsorbate from the surface
603 7dd94df7 Carles Marti
                (i.e. counterclockwise rotation when facing the surface (i.e.
604 7dd94df7 Carles Marti
                view from top))
605 7dd94df7 Carles Marti

606 7dd94df7 Carles Marti
        mol_dihedral_angle_target (float or int): The adsorbate dihedral angle
607 7dd94df7 Carles Marti
            desired (in degrees).
608 7dd94df7 Carles Marti
            Details: mol_dihedral_angle_target = dihedral(atom1_surf, atom1_mol,
609 7dd94df7 Carles Marti
            atom2_mol, atom3_mol)
610 7dd94df7 Carles Marti
                The rotation vector is facing atom2_mol from atom1_mol
611 7dd94df7 Carles Marti

612 7dd94df7 Carles Marti
    Returns:
613 7dd94df7 Carles Marti
        None (the `molecule` object is modified in-place)
614 7dd94df7 Carles Marti
    """
615 7dd94df7 Carles Marti
    vect_surf = get_atom_coords(surf, ctr2_surf) - get_atom_coords(surf,
616 7dd94df7 Carles Marti
                                                                   ctr1_surf)
617 d6da8693 Carles Marti
    vect_inter = get_atom_coords(molecule, ctr1_mol) \
618 d6da8693 Carles Marti
        - get_atom_coords(surf, ctr1_surf)
619 7dd94df7 Carles Marti
    vect_mol = get_atom_coords(molecule, ctr2_mol) - get_atom_coords(molecule,
620 7dd94df7 Carles Marti
                                                                     ctr1_mol)
621 7dd94df7 Carles Marti
    vect2_mol = get_atom_coords(molecule, ctr3_mol) - get_atom_coords(molecule,
622 7dd94df7 Carles Marti
                                                                      ctr2_mol)
623 7dd94df7 Carles Marti
624 7dd94df7 Carles Marti
    # Check if dihedral angles can be defined
625 7dd94df7 Carles Marti
    do_dihedral = dihedral_angle_target is not None
626 7dd94df7 Carles Marti
    do_mol_dihedral = mol_dihedral_angle_target is not None
627 7dd94df7 Carles Marti
    dihedral_use_mol2 = False
628 7dd94df7 Carles Marti
    # Check if vect_surf and vect_inter are not aligned
629 7dd94df7 Carles Marti
    if np.allclose(np.cross(vect_surf, vect_inter), 0):
630 7dd94df7 Carles Marti
        logger.warning(
631 7dd94df7 Carles Marti
            "Surface atoms are incompatible with adsorption "
632 7dd94df7 Carles Marti
            "direction/bond. An adsorption dihedral angle cannot be defined.")
633 7dd94df7 Carles Marti
        do_dihedral = False
634 7dd94df7 Carles Marti
    # Check if requested bond angle is not flat
635 7dd94df7 Carles Marti
    if np.isclose((bond_angle_target + 90) % 180 - 90, 0):
636 7dd94df7 Carles Marti
        logger.warning("Requested bond angle is flat. Only a single dihedral "
637 7dd94df7 Carles Marti
                       "angle can be defined (ctr2_surf, ctr1_surf, ctr2_mol, "
638 7dd94df7 Carles Marti
                       "ctr3_mol).")
639 7dd94df7 Carles Marti
        do_mol_dihedral = False
640 7dd94df7 Carles Marti
        dihedral_use_mol2 = True
641 7dd94df7 Carles Marti
        logger.warning("Dihedral adsorption angle rotation will be perfomed "
642 7dd94df7 Carles Marti
                       "with (ctr2_surf, ctr1_surf, ctr2_mol, ctr3_mol).")
643 7dd94df7 Carles Marti
    # Check if vect_mol and vect2_mol are not aligned
644 7dd94df7 Carles Marti
    if np.allclose(np.cross(vect_mol, vect2_mol), 0):
645 7dd94df7 Carles Marti
        logger.warning("Adsorbates atoms are aligned. An adsorbate dihedral "
646 7dd94df7 Carles Marti
                       "angle cannot be defined.")
647 7dd94df7 Carles Marti
        do_mol_dihedral = False
648 7dd94df7 Carles Marti
    if not do_dihedral:
649 7dd94df7 Carles Marti
        logger.warning("Dihedral adsorption angle rotation will not be "
650 7dd94df7 Carles Marti
                       "performed.")
651 7dd94df7 Carles Marti
    if not do_mol_dihedral:
652 7dd94df7 Carles Marti
        logger.warning("Adsorbate dihedral angle rotation will not be "
653 7dd94df7 Carles Marti
                       "performed.")
654 7dd94df7 Carles Marti
655 7dd94df7 Carles Marti
    ###########################
656 7dd94df7 Carles Marti
    #       Translation       #
657 7dd94df7 Carles Marti
    ###########################
658 7dd94df7 Carles Marti
659 7dd94df7 Carles Marti
    # Compute and apply translation of adsorbate
660 7dd94df7 Carles Marti
    translation = bond_vector - vect_inter
661 7dd94df7 Carles Marti
    molecule.translate(translation)
662 7dd94df7 Carles Marti
663 7dd94df7 Carles Marti
    # Update adsorption bond
664 d6da8693 Carles Marti
    vect_inter = get_atom_coords(molecule, ctr1_mol) - \
665 d6da8693 Carles Marti
        get_atom_coords(surf, ctr1_surf)
666 7dd94df7 Carles Marti
667 7dd94df7 Carles Marti
    # Check if translation was successful
668 7dd94df7 Carles Marti
    if np.allclose(vect_inter, bond_vector):
669 7dd94df7 Carles Marti
        pass  # print("Translation successfully applied (error: ~ {:.5g} unit "
670 7dd94df7 Carles Marti
        # "length)".format(np.linalg.norm(vect_inter - bond_vector)))
671 7dd94df7 Carles Marti
    else:
672 7dd94df7 Carles Marti
        err = 'An unknown error occured during the translation'
673 7dd94df7 Carles Marti
        logger.error(err)
674 7dd94df7 Carles Marti
        raise AssertionError(err)
675 7dd94df7 Carles Marti
676 7dd94df7 Carles Marti
    ###########################
677 7dd94df7 Carles Marti
    #   Bond angle rotation   #
678 7dd94df7 Carles Marti
    ###########################
679 7dd94df7 Carles Marti
680 7dd94df7 Carles Marti
    # Compute rotation vector
681 7dd94df7 Carles Marti
    rotation_vector = np.cross(-vect_inter, vect_mol)
682 7dd94df7 Carles Marti
    if np.allclose(rotation_vector, 0, atol=1e-5):
683 7dd94df7 Carles Marti
        # If molecular bonds are aligned, any vector orthogonal to vect_inter
684 7dd94df7 Carles Marti
        # can be used Such vector can be found as the orthogonal rejection of
685 7dd94df7 Carles Marti
        # either X-axis, Y-axis or Z-axis onto vect_inter (since they cannot
686 7dd94df7 Carles Marti
        # be all aligned)
687 7dd94df7 Carles Marti
        non_aligned_vector = np.zeros(3)
688 7dd94df7 Carles Marti
        # Select the most orthogonal axis (lowest dot product):
689 7dd94df7 Carles Marti
        non_aligned_vector[np.argmin(np.abs(vect_inter))] = 1
690 7dd94df7 Carles Marti
        rotation_vector = non_aligned_vector - np.dot(non_aligned_vector,
691 7dd94df7 Carles Marti
                                                      vect_inter) / np.dot(
692 7dd94df7 Carles Marti
            vect_inter, vect_inter) * vect_inter
693 7dd94df7 Carles Marti
694 7dd94df7 Carles Marti
    # Retrieve current bond angle
695 7dd94df7 Carles Marti
    bond_angle_ini = get_vect_angle(-vect_inter, vect_mol, rotation_vector)
696 7dd94df7 Carles Marti
697 7dd94df7 Carles Marti
    # Apply rotation to reach desired bond_angle
698 7dd94df7 Carles Marti
    molecule.rotate(bond_angle_target - bond_angle_ini, v=rotation_vector,
699 7dd94df7 Carles Marti
                    center=get_atom_coords(molecule, ctr1_mol))
700 7dd94df7 Carles Marti
701 7dd94df7 Carles Marti
    # Update molecular bonds
702 7dd94df7 Carles Marti
    vect_mol = get_atom_coords(molecule, ctr2_mol) - get_atom_coords(molecule,
703 7dd94df7 Carles Marti
                                                                     ctr1_mol)
704 7dd94df7 Carles Marti
    vect2_mol = get_atom_coords(molecule, ctr3_mol) - get_atom_coords(molecule,
705 7dd94df7 Carles Marti
                                                                      ctr2_mol)
706 7dd94df7 Carles Marti
707 7dd94df7 Carles Marti
    # Check if rotation was successful
708 7dd94df7 Carles Marti
    bond_angle = get_vect_angle(-vect_inter, vect_mol)
709 7dd94df7 Carles Marti
    if np.isclose((bond_angle - bond_angle_target + 90) % 180 - 90, 0,
710 7dd94df7 Carles Marti
                  atol=1e-3) and np.allclose(get_atom_coords(molecule, ctr1_mol)
711 7dd94df7 Carles Marti
                                             - get_atom_coords(surf,
712 7dd94df7 Carles Marti
                                                               ctr1_surf),
713 7dd94df7 Carles Marti
                                             vect_inter):
714 7dd94df7 Carles Marti
        pass  # print("Rotation successfully applied (error: {:.5f}°)".format(
715 7dd94df7 Carles Marti
        # (bond_angle - bond_angle_target + 90) % 180 - 90))
716 7dd94df7 Carles Marti
    else:
717 7dd94df7 Carles Marti
        err = 'An unknown error occured during the rotation'
718 7dd94df7 Carles Marti
        logger.error(err)
719 7dd94df7 Carles Marti
        raise AssertionError(err)
720 7dd94df7 Carles Marti
721 7dd94df7 Carles Marti
    ###########################
722 7dd94df7 Carles Marti
    # Dihedral angle rotation #
723 7dd94df7 Carles Marti
    ###########################
724 7dd94df7 Carles Marti
725 7dd94df7 Carles Marti
    # Perform dihedral rotation if possible
726 7dd94df7 Carles Marti
    if do_dihedral:
727 7dd94df7 Carles Marti
        # Retrieve current dihedral angle (by computing the angle between the
728 7dd94df7 Carles Marti
        # vector rejection of vect_surf and vect_mol onto vect_inter)
729 7dd94df7 Carles Marti
        vect_inter_inner = np.dot(vect_inter, vect_inter)
730 7dd94df7 Carles Marti
        vect_surf_reject = vect_surf - np.dot(vect_surf, vect_inter) / \
731 d6da8693 Carles Marti
            vect_inter_inner * vect_inter
732 7dd94df7 Carles Marti
        if dihedral_use_mol2:
733 7dd94df7 Carles Marti
            vect_mol_reject = vect2_mol - np.dot(vect2_mol, vect_inter) / \
734 7dd94df7 Carles Marti
                              vect_inter_inner * vect_inter
735 7dd94df7 Carles Marti
        else:
736 7dd94df7 Carles Marti
            vect_mol_reject = vect_mol - np.dot(vect_mol, vect_inter) / \
737 7dd94df7 Carles Marti
                              vect_inter_inner * vect_inter
738 7dd94df7 Carles Marti
        dihedral_angle_ini = get_vect_angle(vect_surf_reject, vect_mol_reject,
739 7dd94df7 Carles Marti
                                            vect_inter)
740 7dd94df7 Carles Marti
741 7dd94df7 Carles Marti
        # Apply dihedral rotation along vect_inter
742 7dd94df7 Carles Marti
        molecule.rotate(dihedral_angle_target - dihedral_angle_ini,
743 7dd94df7 Carles Marti
                        v=vect_inter, center=get_atom_coords(molecule,
744 7dd94df7 Carles Marti
                                                             ctr1_mol))
745 7dd94df7 Carles Marti
746 7dd94df7 Carles Marti
        # Update molecular bonds
747 7dd94df7 Carles Marti
        vect_mol = get_atom_coords(molecule, ctr2_mol) - \
748 d6da8693 Carles Marti
            get_atom_coords(molecule, ctr1_mol)
749 7dd94df7 Carles Marti
        vect2_mol = get_atom_coords(molecule, ctr3_mol) - \
750 d6da8693 Carles Marti
            get_atom_coords(molecule, ctr2_mol)
751 7dd94df7 Carles Marti
752 7dd94df7 Carles Marti
        # Check if rotation was successful
753 7dd94df7 Carles Marti
        # Check dihedral rotation
754 7dd94df7 Carles Marti
        if dihedral_use_mol2:
755 7dd94df7 Carles Marti
            vect_mol_reject = vect2_mol - np.dot(vect2_mol, vect_inter) / \
756 7dd94df7 Carles Marti
                              vect_inter_inner * vect_inter
757 7dd94df7 Carles Marti
        else:
758 7dd94df7 Carles Marti
            vect_mol_reject = vect_mol - np.dot(vect_mol, vect_inter) / \
759 7dd94df7 Carles Marti
                              vect_inter_inner * vect_inter
760 7dd94df7 Carles Marti
        dihedral_angle = get_vect_angle(vect_surf_reject, vect_mol_reject,
761 7dd94df7 Carles Marti
                                        vect_inter)
762 7dd94df7 Carles Marti
        # Check bond rotation is unmodified
763 7dd94df7 Carles Marti
        bond_angle = get_vect_angle(-vect_inter, vect_mol)
764 7dd94df7 Carles Marti
        if np.isclose((dihedral_angle - dihedral_angle_target + 90) % 180 - 90,
765 5261a07f Carles Marti
                      0, atol=1e-3) \
766 5261a07f Carles Marti
                and np.isclose((bond_angle - bond_angle_target + 90) %
767 5261a07f Carles Marti
                               180 - 90, 0, atol=1e-5) \
768 c25aa299 Carles Marti
                and np.allclose(get_atom_coords(molecule, ctr1_mol)
769 c25aa299 Carles Marti
                                - get_atom_coords(surf, ctr1_surf),
770 c25aa299 Carles Marti
                                vect_inter):
771 7dd94df7 Carles Marti
            pass  # print( "Dihedral rotation successfully applied (error: {
772 7dd94df7 Carles Marti
            # :.5f}°)".format((dihedral_angle - dihedral_angle_target + 90) %
773 7dd94df7 Carles Marti
            # 180 - 90))
774 7dd94df7 Carles Marti
        else:
775 7dd94df7 Carles Marti
            err = 'An unknown error occured during the dihedral rotation'
776 7dd94df7 Carles Marti
            logger.error(err)
777 7dd94df7 Carles Marti
            raise AssertionError(err)
778 7dd94df7 Carles Marti
779 7dd94df7 Carles Marti
    #####################################
780 7dd94df7 Carles Marti
    # Adsorbate dihedral angle rotation #
781 7dd94df7 Carles Marti
    #####################################
782 7dd94df7 Carles Marti
783 7dd94df7 Carles Marti
    # Perform adsorbate dihedral rotation if possible
784 7dd94df7 Carles Marti
    if do_mol_dihedral:
785 7dd94df7 Carles Marti
        # Retrieve current adsorbate dihedral angle (by computing the angle
786 7dd94df7 Carles Marti
        # between the orthogonal rejection of vect_inter and vect2_mol onto
787 7dd94df7 Carles Marti
        # vect_mol)
788 7dd94df7 Carles Marti
        vect_mol_inner = np.dot(vect_mol, vect_mol)
789 7dd94df7 Carles Marti
        bond_inter_reject = -vect_inter - np.dot(-vect_inter, vect_mol) / \
790 5261a07f Carles Marti
            vect_mol_inner * vect_mol
791 7dd94df7 Carles Marti
        bond2_mol_reject = vect2_mol - np.dot(vect2_mol, vect_mol) / \
792 5261a07f Carles Marti
            vect_mol_inner * vect_mol
793 7dd94df7 Carles Marti
        dihedral_angle_ini = get_vect_angle(bond_inter_reject,
794 7dd94df7 Carles Marti
                                            bond2_mol_reject, vect_mol)
795 7dd94df7 Carles Marti
796 7dd94df7 Carles Marti
        # Apply dihedral rotation along vect_mol
797 7dd94df7 Carles Marti
        molecule.rotate(mol_dihedral_angle_target - dihedral_angle_ini,
798 7dd94df7 Carles Marti
                        v=vect_mol, center=get_atom_coords(molecule, ctr1_mol))
799 7dd94df7 Carles Marti
800 7dd94df7 Carles Marti
        # Update molecular bonds
801 7dd94df7 Carles Marti
        vect_mol = get_atom_coords(molecule, ctr2_mol) \
802 5261a07f Carles Marti
            - get_atom_coords(molecule, ctr1_mol)
803 7dd94df7 Carles Marti
        vect2_mol = get_atom_coords(molecule, ctr3_mol) \
804 5261a07f Carles Marti
            - get_atom_coords(molecule, ctr2_mol)
805 7dd94df7 Carles Marti
806 7dd94df7 Carles Marti
        # Check if rotation was successful
807 7dd94df7 Carles Marti
        # Check adsorbate dihedral rotation
808 7dd94df7 Carles Marti
        vect_mol_inner = np.dot(vect_mol, vect_mol)
809 7dd94df7 Carles Marti
        bond2_mol_reject = vect2_mol - np.dot(vect2_mol, vect_mol) / \
810 5261a07f Carles Marti
            vect_mol_inner * vect_mol
811 7dd94df7 Carles Marti
        mol_dihedral_angle = get_vect_angle(bond_inter_reject,
812 7dd94df7 Carles Marti
                                            bond2_mol_reject, vect_mol)
813 7dd94df7 Carles Marti
        # Check dihedral rotation
814 7dd94df7 Carles Marti
        vect_inter_inner = np.dot(vect_inter, vect_inter)
815 7dd94df7 Carles Marti
        vect_surf_reject = vect_surf - np.dot(vect_surf, vect_inter) / \
816 5261a07f Carles Marti
            vect_inter_inner * vect_inter
817 7dd94df7 Carles Marti
        vect_mol_reject = vect_mol - np.dot(vect_mol, vect_inter) / \
818 5261a07f Carles Marti
            vect_inter_inner * vect_inter
819 7dd94df7 Carles Marti
        dihedral_angle = get_vect_angle(vect_surf_reject, vect_mol_reject,
820 7dd94df7 Carles Marti
                                        vect_inter)
821 7dd94df7 Carles Marti
        # Check bond rotation is unmodified
822 7dd94df7 Carles Marti
        bond_angle = get_vect_angle(-vect_inter, vect_mol)
823 7dd94df7 Carles Marti
        if np.isclose((mol_dihedral_angle - mol_dihedral_angle_target + 90) %
824 7dd94df7 Carles Marti
                      180 - 90, 0, atol=1e-3) \
825 7dd94df7 Carles Marti
                and np.isclose((dihedral_angle -
826 7dd94df7 Carles Marti
                                dihedral_angle_target + 90) % 180 - 90, 0,
827 7dd94df7 Carles Marti
                               atol=1e-5) \
828 7dd94df7 Carles Marti
                and np.isclose((bond_angle - bond_angle_target + 90) % 180 - 90,
829 7dd94df7 Carles Marti
                               0, atol=1e-5) \
830 7dd94df7 Carles Marti
                and np.allclose(get_atom_coords(molecule, ctr1_mol) -
831 7dd94df7 Carles Marti
                                get_atom_coords(surf, ctr1_surf),
832 7dd94df7 Carles Marti
                                vect_inter):
833 7dd94df7 Carles Marti
            pass  # print(
834 7dd94df7 Carles Marti
            # "Adsorbate dihedral rotation successfully applied (error:
835 7dd94df7 Carles Marti
            # {:.5f}°)".format((mol_dihedral_angle - mol_dihedral_angle_target
836 7dd94df7 Carles Marti
            # + 90) % 180 - 90))
837 7dd94df7 Carles Marti
        else:
838 7dd94df7 Carles Marti
            err = 'An unknown error occured during the adsorbate dihedral ' \
839 7dd94df7 Carles Marti
                  'rotation'
840 7dd94df7 Carles Marti
            logger.error(err)
841 7dd94df7 Carles Marti
            raise AssertionError(err)
842 7dd94df7 Carles Marti
843 7dd94df7 Carles Marti
844 7dd94df7 Carles Marti
def ads_chemcat(orig_molec, slab, ctr1_mol, ctr2_mol, ctr3_mol, ctr1_surf,
845 7dd94df7 Carles Marti
                ctr2_surf, num_pts, min_coll_height, coll_coeff, norm_vect,
846 c25aa299 Carles Marti
                slab_nghbs, molec_nghbs, h_donor, h_acceptor, max_hel):
847 5261a07f Carles Marti
    """Generates adsorbate-surface structures by sampling over chemcat angles.
848 5261a07f Carles Marti

849 5261a07f Carles Marti
    @param orig_molec: ase.Atoms object of the molecule to adsorb (adsorbate).
850 5261a07f Carles Marti
    @param slab: ase.Atoms object of the surface on which to adsorb the molecule
851 5261a07f Carles Marti
    @param ctr1_mol: The index/es of the center in the adsorbate to use as
852 5261a07f Carles Marti
        adsorption center.
853 5261a07f Carles Marti
    @param ctr2_mol: The index/es of the center in the adsorbate to use for the
854 5261a07f Carles Marti
        definition of the surf-adsorbate angle, surf-adsorbate dihedral angle
855 5261a07f Carles Marti
        and adsorbate dihedral angle.
856 5261a07f Carles Marti
    @param ctr3_mol: The index/es of the center in the adsorbate to use for the
857 5261a07f Carles Marti
        definition of the adsorbate dihedral angle.
858 5261a07f Carles Marti
    @param ctr1_surf: The index/es of the center in the surface to use as
859 5261a07f Carles Marti
        adsorption center.
860 5261a07f Carles Marti
    @param ctr2_surf: The index/es of the center in the surface to use for the
861 5261a07f Carles Marti
        definition of the surf-adsorbate dihedral angle.
862 5261a07f Carles Marti
    @param num_pts: Number on which to sample Euler angles.
863 5261a07f Carles Marti
    @param min_coll_height: The lowest height for which to detect a collision
864 5261a07f Carles Marti
    @param coll_coeff: The coefficient that multiplies the covalent radius of
865 5261a07f Carles Marti
        atoms resulting in a distance that two atoms being closer to that is
866 5261a07f Carles Marti
        considered as atomic collision.
867 5261a07f Carles Marti
    @param norm_vect: The vector perpendicular to the surface.
868 5261a07f Carles Marti
    @param slab_nghbs: Number of neigbors in the surface.
869 5261a07f Carles Marti
    @param molec_nghbs: Number of neighbors in the molecule.
870 c25aa299 Carles Marti
    @param h_donor: List of atom types or atom numbers that are H-donors.
871 c25aa299 Carles Marti
    @param h_acceptor: List of atom types or atom numbers that are H-acceptors.
872 c25aa299 Carles Marti
    @param max_hel: Maximum value for sampling the helicopter
873 5261a07f Carles Marti
        (surf-adsorbate dihedral) angle.
874 5261a07f Carles Marti
    @return: list of ase.Atoms object conatining all the orientations of a given
875 5261a07f Carles Marti
        conformer.
876 5261a07f Carles Marti
    """
877 7dd94df7 Carles Marti
    from copy import deepcopy
878 7dd94df7 Carles Marti
    slab_ads_list = []
879 70d3cf53 Carles Marti
    # Rotation over bond angle # TODO Check sampling
880 7dd94df7 Carles Marti
    for alpha in np.arange(90, 180, 90 / min(num_pts, 2)):
881 7dd94df7 Carles Marti
        # Rotation over surf-adsorbate dihedral angle
882 c25aa299 Carles Marti
        for beta in np.arange(0, max_hel, max_hel / num_pts):
883 7dd94df7 Carles Marti
            # Rotation over adsorbate bond dihedral angle
884 7dd94df7 Carles Marti
            for gamma in np.arange(0, 360, 360 / num_pts):
885 7dd94df7 Carles Marti
                new_molec = deepcopy(orig_molec)
886 7dd94df7 Carles Marti
                chemcat_rotate(new_molec, slab, ctr1_mol, ctr2_mol, ctr3_mol,
887 7dd94df7 Carles Marti
                               ctr1_surf, ctr2_surf, norm_vect, alpha,
888 7dd94df7 Carles Marti
                               beta, gamma)
889 7dd94df7 Carles Marti
                site_coords = get_atom_coords(slab, ctr1_surf)
890 7dd94df7 Carles Marti
                ctr_coords = get_atom_coords(new_molec, ctr1_mol)
891 7dd94df7 Carles Marti
                slab_molec, collision = correct_coll(new_molec, slab,
892 7dd94df7 Carles Marti
                                                     ctr_coords, site_coords,
893 7dd94df7 Carles Marti
                                                     num_pts, min_coll_height,
894 7dd94df7 Carles Marti
                                                     norm_vect, slab_nghbs,
895 e8bebcca Carles Marti
                                                     molec_nghbs, coll_coeff)
896 7dd94df7 Carles Marti
                if not collision and \
897 5261a07f Carles Marti
                        not any([np.allclose(slab_molec.positions,
898 5261a07f Carles Marti
                                             conf.positions)
899 7dd94df7 Carles Marti
                                 for conf in slab_ads_list]):
900 7dd94df7 Carles Marti
                    slab_ads_list.append(slab_molec)
901 c25aa299 Carles Marti
                    if h_donor is not False:
902 c25aa299 Carles Marti
                        slab_ads_list.extend(dissociation(slab_molec, h_donor,
903 c25aa299 Carles Marti
                                                          h_acceptor,
904 c25aa299 Carles Marti
                                                          len(slab)))
905 7dd94df7 Carles Marti
906 7dd94df7 Carles Marti
    return slab_ads_list
907 f3d1e601 Carles Marti
908 f3d1e601 Carles Marti
909 7dd94df7 Carles Marti
def adsorb_confs(conf_list, surf, inp_vars):
910 a5cc42ff Carles Marti
    """Generates a number of adsorbate-surface structure coordinates.
911 a5cc42ff Carles Marti

912 a5cc42ff Carles Marti
    Given a list of conformers, a surface, a list of atom indices (or list of
913 a5cc42ff Carles Marti
    list of indices) of both the surface and the adsorbate, it generates a
914 a5cc42ff Carles Marti
    number of adsorbate-surface structures for every possible combination of
915 a5cc42ff Carles Marti
    them at different orientations.
916 a5cc42ff Carles Marti
    @param conf_list: list of ase.Atoms of the different conformers
917 a5cc42ff Carles Marti
    @param surf: the ase.Atoms object of the surface
918 7dd94df7 Carles Marti
    @param inp_vars: Calculation parameters from input file.
919 a5cc42ff Carles Marti
    @return: list of ase.Atoms for the adsorbate-surface structures
920 a5cc42ff Carles Marti
    """
921 bb55f47c Carles Marti
    from ase.neighborlist import natural_cutoffs, neighbor_list
922 7dd94df7 Carles Marti
    molec_ctrs = inp_vars['molec_ctrs']
923 7dd94df7 Carles Marti
    sites = inp_vars['sites']
924 7dd94df7 Carles Marti
    angles = inp_vars['set_angles']
925 7dd94df7 Carles Marti
    num_pts = inp_vars['sample_points_per_angle']
926 7dd94df7 Carles Marti
    norm_vect = inp_vars['surf_norm_vect']
927 7dd94df7 Carles Marti
    min_coll_height = inp_vars['min_coll_height']
928 7dd94df7 Carles Marti
    coll_coeff = inp_vars['collision_threshold']
929 c25aa299 Carles Marti
    h_donor = inp_vars['h_donor']
930 c25aa299 Carles Marti
    h_acceptor = inp_vars['h_acceptor']
931 7dd94df7 Carles Marti
932 bc703cab Carles Marti
    if inp_vars['pbc_cell'] is not False:
933 bc703cab Carles Marti
        surf.set_pbc(True)
934 bc703cab Carles Marti
        surf.set_cell(inp_vars['pbc_cell'])
935 bc703cab Carles Marti
936 f3d1e601 Carles Marti
    surf_ads_list = []
937 f3d1e601 Carles Marti
    sites_coords = get_atom_coords(surf, sites)
938 e8bebcca Carles Marti
    if coll_coeff is not False:
939 5fb01677 Carles Marti
        surf_cutoffs = natural_cutoffs(surf, mult=coll_coeff)
940 5fb01677 Carles Marti
        surf_nghbs = len(neighbor_list("i", surf, surf_cutoffs))
941 5fb01677 Carles Marti
    else:
942 5fb01677 Carles Marti
        surf_nghbs = 0
943 7dd94df7 Carles Marti
    for i, conf in enumerate(conf_list):
944 bb55f47c Carles Marti
        molec_ctr_coords = get_atom_coords(conf, molec_ctrs)
945 bc703cab Carles Marti
        if inp_vars['pbc_cell'] is not False:
946 bc703cab Carles Marti
            conf.set_pbc(True)
947 bc703cab Carles Marti
            conf.set_cell(inp_vars['pbc_cell'])
948 e8bebcca Carles Marti
        if coll_coeff is not False:
949 5fb01677 Carles Marti
            conf_cutoffs = natural_cutoffs(conf, mult=coll_coeff)
950 5fb01677 Carles Marti
            molec_nghbs = len(neighbor_list("i", conf, conf_cutoffs))
951 5fb01677 Carles Marti
        else:
952 5fb01677 Carles Marti
            molec_nghbs = 0
953 7dd94df7 Carles Marti
        for s, site in enumerate(sites_coords):
954 d6da8693 Carles Marti
            if norm_vect == 'auto':
955 d6da8693 Carles Marti
                norm_vect = compute_norm_vect(surf, sites[s],
956 d6da8693 Carles Marti
                                              inp_vars['pbc_cell'])
957 7dd94df7 Carles Marti
            for c, ctr in enumerate(molec_ctr_coords):
958 7dd94df7 Carles Marti
                if angles == 'euler':
959 bb55f47c Carles Marti
                    surf_ads_list.extend(ads_euler(conf, surf, ctr, site,
960 5fb01677 Carles Marti
                                                   num_pts, min_coll_height,
961 bb55f47c Carles Marti
                                                   coll_coeff, norm_vect,
962 b4b2f307 Carles Marti
                                                   surf_nghbs, molec_nghbs,
963 c25aa299 Carles Marti
                                                   h_donor, h_acceptor))
964 7dd94df7 Carles Marti
                elif angles == 'chemcat':
965 7dd94df7 Carles Marti
                    mol_ctr1 = molec_ctrs[c]
966 7dd94df7 Carles Marti
                    mol_ctr2 = inp_vars["molec_ctrs2"][c]
967 7dd94df7 Carles Marti
                    mol_ctr3 = inp_vars["molec_ctrs3"][c]
968 7dd94df7 Carles Marti
                    surf_ctr1 = sites[s]
969 7dd94df7 Carles Marti
                    surf_ctr2 = inp_vars["surf_ctrs2"][s]
970 a98d4172 Carles Marti
                    max_h = inp_vars["max_helic_angle"]
971 7dd94df7 Carles Marti
                    surf_ads_list.extend(ads_chemcat(conf, surf, mol_ctr1,
972 7dd94df7 Carles Marti
                                                     mol_ctr2, mol_ctr3,
973 7dd94df7 Carles Marti
                                                     surf_ctr1, surf_ctr2,
974 7dd94df7 Carles Marti
                                                     num_pts, min_coll_height,
975 7dd94df7 Carles Marti
                                                     coll_coeff, norm_vect,
976 7dd94df7 Carles Marti
                                                     surf_nghbs, molec_nghbs,
977 c25aa299 Carles Marti
                                                     h_donor, h_acceptor,
978 c25aa299 Carles Marti
                                                     max_h))
979 f3d1e601 Carles Marti
    return surf_ads_list
980 f3d1e601 Carles Marti
981 f3d1e601 Carles Marti
982 4614bb6a Carles
def run_screening(inp_vars):
983 91ae8d86 Carles Marti
    """Carries out the screening of adsorbate structures on a surface.
984 e07c09eb Carles

985 e07c09eb Carles
    @param inp_vars: Calculation parameters from input file.
986 e07c09eb Carles
    """
987 e07c09eb Carles
    import os
988 57e3a8c7 Carles Marti
    import random
989 fd2384fc Carles Marti
    from modules.formats import collect_coords, adapt_format
990 cf8fe0e3 Carles Marti
    from modules.calculation import run_calc, check_finished_calcs
991 e07c09eb Carles
992 76f4ac19 Carles Marti
    logger.info('Carrying out procedures for the screening of adsorbate-surface'
993 76f4ac19 Carles Marti
                ' structures.')
994 e07c09eb Carles
    if not os.path.isdir("isolated"):
995 e07c09eb Carles
        err = "'isolated' directory not found. It is needed in order to carry "
996 e07c09eb Carles
        "out the screening of structures to be adsorbed"
997 e07c09eb Carles
        logger.error(err)
998 cf8fe0e3 Carles Marti
        raise FileNotFoundError(err)
999 e07c09eb Carles
1000 cf8fe0e3 Carles Marti
    finished_calcs, unfinished_calcs = check_finished_calcs('isolated',
1001 cf8fe0e3 Carles Marti
                                                            inp_vars['code'])
1002 1a1164e0 Carles Marti
    logger.info(f"Found {len(finished_calcs)} structures of isolated "
1003 1a1164e0 Carles Marti
                f"conformers whose calculation finished normally.")
1004 1a1164e0 Carles Marti
    if len(unfinished_calcs) != 0:
1005 cf8fe0e3 Carles Marti
        logger.warning(f"Found {len(unfinished_calcs)} calculations more that "
1006 76f4ac19 Carles Marti
                       f"did not finish normally: {unfinished_calcs}. \n"
1007 76f4ac19 Carles Marti
                       f"Using only the ones that finished normally: "
1008 76f4ac19 Carles Marti
                       f"{finished_calcs}.")
1009 cf8fe0e3 Carles Marti
1010 fd2384fc Carles Marti
    conformer_atoms_list = collect_coords(finished_calcs, inp_vars['code'],
1011 fd2384fc Carles Marti
                                          'isolated', inp_vars['special_atoms'])
1012 1e9e784d Carles Marti
    selected_confs = select_confs(conformer_atoms_list, finished_calcs,
1013 fd2384fc Carles Marti
                                  inp_vars['select_magns'],
1014 bfe93f0d Carles Marti
                                  inp_vars['confs_per_magn'],
1015 bfe93f0d Carles Marti
                                  inp_vars['code'])
1016 90819cc3 Carles Marti
    surf = adapt_format('ase', inp_vars['surf_file'], inp_vars['special_atoms'])
1017 7dd94df7 Carles Marti
    surf_ads_list = adsorb_confs(selected_confs, surf, inp_vars)
1018 7d97341d Carles Marti
    if len(surf_ads_list) > inp_vars['max_structures']:
1019 57e3a8c7 Carles Marti
        surf_ads_list = random.sample(surf_ads_list, inp_vars['max_structures'])
1020 bfe93f0d Carles Marti
    logger.info(f'Generated {len(surf_ads_list)} adsorbate-surface atomic '
1021 d9167fea Carles Marti
                f'configurations to carry out a calculation of.')
1022 d9167fea Carles Marti
1023 f3d1e601 Carles Marti
    run_calc('screening', inp_vars, surf_ads_list)
1024 14f39d2a Carles Marti
    logger.info('Finished the procedures for the screening of adsorbate-surface'
1025 14f39d2a Carles Marti
                ' structures section.')