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

dockonsurf / modules / dos_input.py @ bf33f563

Historique | Voir | Annoter | Télécharger (39,38 ko)

1 b77be9ad Carles
"""Functions to deal with DockOnSurf input files.
2 b77be9ad Carles

3 b77be9ad Carles
Functions
4 0e83e6a6 Carles
try_command:Tries to run a command and logs its exceptions (expected and not).
5 0e83e6a6 Carles
str2lst: Converts a string of integers, and groups of them, to a list of lists.
6 0e83e6a6 Carles
check_expect_val: Checks whether the value of an option has an adequate value.
7 0e83e6a6 Carles
read_input: Sets up the calculation by reading the parameters from input file.
8 0e83e6a6 Carles
get_run_type: Gets 'run_type' value and checks that its value is acceptable.
9 0e83e6a6 Carles
get_code: Gets 'code' value and checks that its value is acceptable.
10 0e83e6a6 Carles
get_batch_q_sys: Gets 'batch_q_sys' value and checks that its value is
11 8887f41d Carles
acceptable.
12 0e83e6a6 Carles
get_relaunch_err: Gets 'relaunch_err' value and checks that its value is
13 8887f41d Carles
acceptable.
14 09c3325a Carles Marti
get_max_jobs: Gets 'max_jobs' value and checks that its value is acceptable.
15 0e83e6a6 Carles
get_special_atoms: Gets 'special_atoms' value and checks that its value is
16 8887f41d Carles
acceptable.
17 0e83e6a6 Carles
get_isol_inp_file: Gets 'isol_inp_file' value and checks that its value is
18 8887f41d Carles
acceptable.
19 0e83e6a6 Carles
get_cluster_magns: Gets 'cluster_magns' value and checks that its value is
20 8887f41d Carles
acceptable.
21 0e83e6a6 Carles
get_num_conformers: Gets 'num_conformers' value and checks that its value is
22 8887f41d Carles
acceptable.
23 0e83e6a6 Carles
get_num_prom_cand: Gets 'num_prom_cand' value and checks that its value is
24 8887f41d Carles
acceptable.
25 a5c74494 Carles
get_iso_rmsd: Gets 'iso_rmsd' value and checks that its value is acceptable.
26 4670488d Carles Marti
get_pre_opt: Gets 'pre_opt' value and checks that its value is acceptable.
27 0e83e6a6 Carles
get_screen_inp_file: Gets 'screen_inp_file' value and checks that its value is
28 8887f41d Carles
acceptable.
29 0e83e6a6 Carles
get_sites: Gets 'sites' value and checks that its value is acceptable.
30 7dd94df7 Carles Marti
get_molec_ctrs: Gets 'molec_ctrs' value and checks that its value is
31 8887f41d Carles
acceptable.
32 0e83e6a6 Carles
get_try_disso: Gets 'try_disso' value and checks that its value is acceptable.
33 0e83e6a6 Carles
get_pts_per_angle: Gets 'pts_per_angle' value and checks that its value is
34 8887f41d Carles
acceptable.
35 0e83e6a6 Carles
get_coll_thrsld: Gets 'coll_thrsld' value and checks that its value is
36 8887f41d Carles
acceptable.
37 0e83e6a6 Carles
get_screen_rmsd: Gets 'screen_rmsd' value and checks that its value is
38 8887f41d Carles
acceptable.
39 0e83e6a6 Carles
get_coll_bottom_z: Gets 'coll_bottom_z' value and checks that its value is
40 8887f41d Carles
acceptable.
41 0e83e6a6 Carles
get_refine_inp_file: Gets 'refine_inp_file' value and checks that its value is
42 8887f41d Carles
acceptable.
43 0e83e6a6 Carles
get_energy_cutoff: Gets 'energy_cutoff' value and checks that its value is
44 8887f41d Carles
acceptable.
45 17e72a49 Carles
"""
46 8887f41d Carles
import os.path
47 b5b2af64 Carles
import logging
48 8887f41d Carles
from configparser import ConfigParser, NoSectionError, NoOptionError, \
49 8887f41d Carles
    MissingSectionHeaderError, DuplicateOptionError
50 5fb01677 Carles Marti
import numpy as np
51 af3e2441 Carles Marti
from modules.utilities import try_command
52 8887f41d Carles
53 8887f41d Carles
logger = logging.getLogger('DockOnSurf')
54 8887f41d Carles
55 8887f41d Carles
dos_inp = ConfigParser(inline_comment_prefixes='#',
56 8887f41d Carles
                       empty_lines_in_values=False)
57 8887f41d Carles
58 8887f41d Carles
new_answers = {'n': False, 'none': False, 'nay': False,
59 8887f41d Carles
               'y': True, '': True, 'aye': True, 'sure': True}
60 8887f41d Carles
for answer, val in new_answers.items():
61 9cd032cf Carles Marti
    dos_inp.BOOLEAN_STATES[answer] = val  # TODO Check value 0
62 8887f41d Carles
turn_false_answers = [answer for answer in dos_inp.BOOLEAN_STATES
63 8887f41d Carles
                      if dos_inp.BOOLEAN_STATES[answer] is False]
64 9cd032cf Carles Marti
turn_true_answers = [answer for answer in dos_inp.BOOLEAN_STATES
65 9cd032cf Carles Marti
                     if dos_inp.BOOLEAN_STATES[answer]]
66 8887f41d Carles
67 8887f41d Carles
no_sect_err = "Section '%s' not found on input file"
68 8887f41d Carles
no_opt_err = "Option '%s' not found on section '%s'"
69 8887f41d Carles
num_error = "'%s' value must be a %s"
70 8887f41d Carles
71 4533134f Carles Marti
72 14e0b660 Carles Martí
# Auxilary functions
73 14e0b660 Carles Martí
74 4533134f Carles Marti
def str2lst(cmplx_str, func=int):  # TODO: enable deeper level of nested lists
75 a7128ce1 Carles Marti
    # TODO Treat all-enclosing parenthesis as a list instead of list of lists.
76 b77be9ad Carles
    """Converts a string of integers, and groups of them, to a list.
77 b77be9ad Carles

78 b77be9ad Carles
    Keyword arguments:
79 4533134f Carles Marti
    @param cmplx_str: str, string of integers (or floats) and groups of them
80 4533134f Carles Marti
    enclosed by parentheses-like characters.
81 b77be9ad Carles
    - Group enclosers: '()' '[]' and '{}'.
82 4533134f Carles Marti
    - Separators: ',' ';' and ' '.
83 b77be9ad Carles
    - Nested groups are not allowed: '3 ((6 7) 8) 4'.
84 4533134f Carles Marti
    @param func: either to use int or float
85 b77be9ad Carles

86 4533134f Carles Marti
    @return list, list of integers (or floats), or list of integers (or floats)
87 4533134f Carles Marti
    in the case they were grouped. First, the singlets are placed, and then the
88 4533134f Carles Marti
    groups in input order.
89 b77be9ad Carles

90 b77be9ad Carles
    eg. '128,(135 138;141] 87 {45, 68}' -> [128, 87, [135, 138, 141], [45, 68]]
91 17e72a49 Carles
    """
92 b77be9ad Carles
93 8887f41d Carles
    # Checks
94 5261a07f Carles Marti
    error_msg = "Function argument should be a str, sequence of " \
95 8887f41d Carles
                "numbers separated by ',' ';' or ' '." \
96 79e3db42 Carles
                "\nThey can be grouped in parentheses-like enclosers: '()', " \
97 79e3db42 Carles
                "'[]' or {}. Nested groups are not allowed. \n" \
98 79e3db42 Carles
                "eg. 128,(135 138;141) 87 {45, 68}"
99 8887f41d Carles
    cmplx_str = try_command(cmplx_str.replace, [(AttributeError, error_msg)],
100 8887f41d Carles
                            ',', ' ')
101 8887f41d Carles
102 8887f41d Carles
    cmplx_str = cmplx_str.replace(';', ' ').replace('[', '(').replace(
103 8887f41d Carles
        ']', ')').replace('{', '(').replace('}', ')')
104 8887f41d Carles
105 4533134f Carles Marti
    try_command(list, [(ValueError, error_msg)], map(func, cmplx_str.replace(
106 8887f41d Carles
        ')', '').replace('(', '').split()))
107 8887f41d Carles
108 b5b2af64 Carles
    deepness = 0
109 b5b2af64 Carles
    for el in cmplx_str.split():
110 b5b2af64 Carles
        if '(' in el:
111 b5b2af64 Carles
            deepness += 1
112 b5b2af64 Carles
        if ')' in el:
113 b5b2af64 Carles
            deepness += -1
114 b5b2af64 Carles
        if deepness > 1 or deepness < 0:
115 9f7bb440 Carles
            logger.error(error_msg)
116 b5b2af64 Carles
            raise ValueError(error_msg)
117 17e72a49 Carles
118 772b40e5 Carles
    init_list = cmplx_str.split()
119 772b40e5 Carles
    start_group = []
120 772b40e5 Carles
    end_group = []
121 772b40e5 Carles
    for i, element in enumerate(init_list):
122 73402e22 Carles
        if '(' in element:
123 772b40e5 Carles
            start_group.append(i)
124 17e72a49 Carles
            init_list[i] = element.replace('(', '')
125 73402e22 Carles
        if ')' in element:
126 772b40e5 Carles
            end_group.append(i)
127 17e72a49 Carles
            init_list[i] = element.replace(')', '')
128 772b40e5 Carles
129 4533134f Carles Marti
    init_list = list(map(func, init_list))
130 772b40e5 Carles
131 17e72a49 Carles
    new_list = []
132 772b40e5 Carles
    for start_el, end_el in zip(start_group, end_group):
133 17e72a49 Carles
        new_list.append(init_list[start_el:end_el + 1])
134 772b40e5 Carles
135 772b40e5 Carles
    for v in new_list:
136 772b40e5 Carles
        for el in v:
137 772b40e5 Carles
            init_list.remove(el)
138 772b40e5 Carles
    return init_list + new_list
139 772b40e5 Carles
140 17e72a49 Carles
141 5f9f1400 Carles Marti
def check_expect_val(value, expect_vals, err_msg=None):
142 b77be9ad Carles
    """Checks whether an option lies within its expected values.
143 b77be9ad Carles

144 b77be9ad Carles
    Keyword arguments:
145 b77be9ad Carles
    @param value: The variable to check if its value lies within the expected
146 b77be9ad Carles
    ones
147 b77be9ad Carles
    @param expect_vals: list, list of values allowed for the present option.
148 5f9f1400 Carles Marti
    @param err_msg: The error message to be prompted in both log and screen.
149 b77be9ad Carles
    @raise ValueError: if the value is not among the expected ones.
150 b77be9ad Carles
    @return True if the value is among the expected ones.
151 b77be9ad Carles
    """
152 5f9f1400 Carles Marti
    if err_msg is None:
153 5f9f1400 Carles Marti
        err_msg = f"'{value}' is not an adequate value.\n" \
154 5f9f1400 Carles Marti
                  f"Adequate values: {expect_vals}"
155 821dca42 Carles Marti
    if not any([exp_val == value for exp_val in expect_vals]):
156 5f9f1400 Carles Marti
        logger.error(err_msg)
157 5f9f1400 Carles Marti
        raise ValueError(err_msg)
158 b77be9ad Carles
159 b77be9ad Carles
    return True
160 b77be9ad Carles
161 b77be9ad Carles
162 9d3b680c Carles Martí
def check_inp_file(inp_files, code):
163 03fab2dd Carles
    if code == 'cp2k':
164 03fab2dd Carles
        from pycp2k import CP2K
165 9d3b680c Carles Martí
        if not isinstance(inp_files, str):
166 9d3b680c Carles Martí
            err_msg = "When using CP2K, only one input file is allowed"
167 9d3b680c Carles Martí
            logger.error(err_msg)
168 9d3b680c Carles Martí
            ValueError(err_msg)
169 9d3b680c Carles Martí
        elif not os.path.isfile(inp_files):
170 9d3b680c Carles Martí
            err_msg = f"Input file {inp_files} was not found."
171 9d3b680c Carles Martí
            logger.error(err_msg)
172 9d3b680c Carles Martí
            raise FileNotFoundError(err_msg)
173 03fab2dd Carles
        cp2k = CP2K()
174 03fab2dd Carles
        try_command(cp2k.parse,
175 9d3b680c Carles Martí
                    [(UnboundLocalError, "Invalid CP2K input file")], inp_files)
176 9d3b680c Carles Martí
    elif code == "vasp":
177 9d3b680c Carles Martí
        mand_files = ["INCAR", "KPOINTS", "POTCAR"]
178 9d3b680c Carles Martí
        # Check that it inp_files is a list of file paths
179 9d3b680c Carles Martí
        if not isinstance(inp_files, list) and all(isinstance(inp_file, str)
180 9d3b680c Carles Martí
                                                   for inp_file in inp_files):
181 9d3b680c Carles Martí
            err_msg = "'inp_files' should be a list of file names/paths"
182 9d3b680c Carles Martí
            logger.error(err_msg)
183 9d3b680c Carles Martí
            ValueError(err_msg)
184 9d3b680c Carles Martí
        # Check that all mandatory files are defined once and just once.
185 9d3b680c Carles Martí
        elif [[mand_file in inp_file for inp_file in inp_files].count(True)
186 9d3b680c Carles Martí
              for mand_file in mand_files].count(1) != len(mand_files):
187 9d3b680c Carles Martí
            err_msg = f"Each of the mandatory files {mand_files} must be " \
188 9d3b680c Carles Martí
                      f"defined once and just once."
189 9d3b680c Carles Martí
            logger.error(err_msg)
190 9d3b680c Carles Martí
            raise FileNotFoundError(err_msg)
191 9d3b680c Carles Martí
        # Check that the defined files exist
192 9d3b680c Carles Martí
        elif any(not os.path.isfile(inp_file) for inp_file in inp_files):
193 9d3b680c Carles Martí
            err_msg = f"At least one of the mandatory files {mand_files} was " \
194 9d3b680c Carles Martí
                      "not found."
195 9d3b680c Carles Martí
            logger.error(err_msg)
196 9d3b680c Carles Martí
            raise FileNotFoundError(err_msg)
197 9d3b680c Carles Martí
        # Check that mandatory files are actual vasp files.
198 9d3b680c Carles Martí
        else:
199 9d3b680c Carles Martí
            from pymatgen.io.vasp.inputs import Incar, Kpoints, Potcar
200 9d3b680c Carles Martí
            for inp_file in inp_files:
201 9d3b680c Carles Martí
                file_name = inp_file.split("/")[-1]
202 9d3b680c Carles Martí
                if not any(mand_file in file_name for mand_file in mand_files):
203 9d3b680c Carles Martí
                    continue
204 9d3b680c Carles Martí
                file_type = ""
205 9d3b680c Carles Martí
                for mand_file in mand_files:
206 9d3b680c Carles Martí
                    if mand_file in inp_file:
207 9d3b680c Carles Martí
                        file_type = mand_file
208 9d3b680c Carles Martí
                err = False
209 9d3b680c Carles Martí
                err_msg = f"'{inp_file}' is not a valid {file_name} file."
210 9d3b680c Carles Martí
                try:
211 9d3b680c Carles Martí
                    eval(file_type.capitalize()).from_file(inp_file)
212 9d3b680c Carles Martí
                except ValueError:
213 9d3b680c Carles Martí
                    logger.error(err_msg)
214 9d3b680c Carles Martí
                    err = ValueError(err_msg)
215 9d3b680c Carles Martí
                except IndexError:
216 9d3b680c Carles Martí
                    logger.error(err_msg)
217 9d3b680c Carles Martí
                    err = IndexError(err_msg)
218 9d3b680c Carles Martí
                else:
219 9d3b680c Carles Martí
                    if file_name == "INCAR":
220 9d3b680c Carles Martí
                        Incar.from_file("INCAR").check_params()
221 9d3b680c Carles Martí
                finally:
222 9d3b680c Carles Martí
                    if isinstance(err, BaseException):
223 9d3b680c Carles Martí
                        raise err
224 03fab2dd Carles
225 03fab2dd Carles
226 a7128ce1 Carles Marti
# Global
227 a7128ce1 Carles Marti
228 8887f41d Carles
def get_run_type():
229 b1d27be5 Carles
    isolated, screening, refinement = (False, False, False)
230 5cc4994b Carles
    run_type_vals = ['isolated', 'screening', 'refinement', 'adsorption',
231 5cc4994b Carles
                     'full']
232 081548a0 Carles Marti
    run_types = dos_inp.get('Global', 'run_type').split()
233 081548a0 Carles Marti
    for run_type in run_types:
234 081548a0 Carles Marti
        check_expect_val(run_type.lower(), run_type_vals)
235 081548a0 Carles Marti
        if 'isol' in run_type.lower():
236 081548a0 Carles Marti
            isolated = True
237 081548a0 Carles Marti
        if 'screen' in run_type.lower():
238 081548a0 Carles Marti
            screening = True
239 081548a0 Carles Marti
        if 'refine' in run_type.lower():
240 081548a0 Carles Marti
            refinement = True
241 081548a0 Carles Marti
        if 'adsor' in run_type.lower():
242 081548a0 Carles Marti
            screening, refinement = (True, True)
243 081548a0 Carles Marti
        if 'full' in run_type.lower():
244 081548a0 Carles Marti
            isolated, screening, refinement = (True, True, True)
245 5cc4994b Carles
246 8887f41d Carles
    return isolated, screening, refinement
247 17e72a49 Carles
248 8887f41d Carles
249 8887f41d Carles
def get_code():
250 9d3b680c Carles Martí
    code_vals = ['cp2k', 'vasp']
251 5cc4994b Carles
    check_expect_val(dos_inp.get('Global', 'code').lower(), code_vals)
252 5cc4994b Carles
    code = dos_inp.get('Global', 'code').lower()
253 8887f41d Carles
    return code
254 8887f41d Carles
255 17e72a49 Carles
256 8887f41d Carles
def get_batch_q_sys():
257 ec5bba46 Carles Marti
    batch_q_sys_vals = ['sge', 'lsf', 'irene', 'local'] + turn_false_answers
258 5cc4994b Carles
    check_expect_val(dos_inp.get('Global', 'batch_q_sys').lower(),
259 5cc4994b Carles
                     batch_q_sys_vals)
260 5cc4994b Carles
    batch_q_sys = dos_inp.get('Global', 'batch_q_sys').lower()
261 821dca42 Carles Marti
    if batch_q_sys.lower() in turn_false_answers:
262 821dca42 Carles Marti
        return False
263 821dca42 Carles Marti
    else:
264 821dca42 Carles Marti
        return batch_q_sys
265 17e72a49 Carles
266 8887f41d Carles
267 9d3b680c Carles Martí
def get_pbc_cell():
268 a5d76bfb Carles Martí
    from ase.atoms import Cell
269 9d3b680c Carles Martí
    err_msg = "'pbc_cell' must be either 3 vectors of size 3 or False."
270 9d3b680c Carles Martí
    pbc_cell_str = dos_inp.get('Global', 'pbc_cell', fallback="False")
271 9d3b680c Carles Martí
    if pbc_cell_str.lower() in turn_false_answers:
272 9d3b680c Carles Martí
        return False
273 9d3b680c Carles Martí
    else:
274 9d3b680c Carles Martí
        pbc_cell = np.array(try_command(str2lst, [(ValueError, err_msg)],
275 9d3b680c Carles Martí
                                        pbc_cell_str, float))
276 9d3b680c Carles Martí
        if pbc_cell.shape != (3, 3):
277 9d3b680c Carles Martí
            logger.error(err_msg)
278 9d3b680c Carles Martí
            raise ValueError(err_msg)
279 9d3b680c Carles Martí
        if np.linalg.det(pbc_cell) == 0.0:
280 9d3b680c Carles Martí
            err_msg = "The volume of the defined cell is 0"
281 9d3b680c Carles Martí
            logger.error(err_msg)
282 9d3b680c Carles Martí
            raise ValueError(err_msg)
283 a5d76bfb Carles Martí
        return Cell(pbc_cell)
284 9d3b680c Carles Martí
285 9d3b680c Carles Martí
286 99afde40 Carles
def get_subm_script():
287 14e0b660 Carles Martí
    subm_script = dos_inp.get('Global', 'subm_script')
288 14e0b660 Carles Martí
    if not os.path.isfile(subm_script):
289 695dcff8 Carles Marti
        logger.error(f'File {subm_script} not found.')
290 99afde40 Carles
        raise FileNotFoundError(f'File {subm_script} not found')
291 99afde40 Carles
    return subm_script
292 99afde40 Carles
293 99afde40 Carles
294 99afde40 Carles
def get_project_name():
295 bd573212 Carles
    project_name = dos_inp.get('Global', 'project_name', fallback='')
296 99afde40 Carles
    return project_name
297 99afde40 Carles
298 99afde40 Carles
299 8887f41d Carles
def get_relaunch_err():
300 09c3325a Carles Marti
    relaunch_err_vals = ['geo_not_conv']
301 b1d27be5 Carles
    relaunch_err = dos_inp.get('Global', 'relaunch_err',
302 17e72a49 Carles
                               fallback="False")
303 b1d27be5 Carles
    if relaunch_err.lower() in turn_false_answers:
304 8887f41d Carles
        return False
305 79e3db42 Carles
    else:
306 79e3db42 Carles
        check_expect_val(relaunch_err.lower(), relaunch_err_vals)
307 8887f41d Carles
    return relaunch_err
308 8887f41d Carles
309 8887f41d Carles
310 09c3325a Carles Marti
def get_max_jobs():
311 b6b1b03e Carles Marti
    import re
312 b6b1b03e Carles Marti
    err_msg = "'max_jobs' must be a list of, number plus 'p', 'q' or 'r', or " \
313 b6b1b03e Carles Marti
              "a combination of them without repeating letters.\n" \
314 b6b1b03e Carles Marti
              "eg: '2r 3p 4pr', '5q' or '3r 3p'"
315 b6b1b03e Carles Marti
    max_jobs_str = dos_inp.get('Global', 'max_jobs', fallback="inf").lower()
316 b6b1b03e Carles Marti
    str_vals = ["r", "p", "q", "rp", "rq", "pr", "qr"]
317 b6b1b03e Carles Marti
    max_jobs = {"r": np.inf, "p": np.inf, "rp": np.inf}
318 b6b1b03e Carles Marti
    if "inf" == max_jobs_str:
319 b6b1b03e Carles Marti
        return {"r": np.inf, "p": np.inf, "rp": np.inf}
320 b6b1b03e Carles Marti
    # Iterate over the number of requirements:
321 b6b1b03e Carles Marti
    for req in max_jobs_str.split():
322 b6b1b03e Carles Marti
        # Split numbers from letters into a list
323 b6b1b03e Carles Marti
        req_parts = re.findall(r'[a-z]+|\d+', req)
324 b6b1b03e Carles Marti
        if len(req_parts) != 2:
325 b6b1b03e Carles Marti
            logger.error(err_msg)
326 b6b1b03e Carles Marti
            raise ValueError(err_msg)
327 b6b1b03e Carles Marti
        if req_parts[0].isdecimal():
328 b6b1b03e Carles Marti
            req_parts[1] = req_parts[1].replace('q', 'p').replace('pr', 'rp')
329 b6b1b03e Carles Marti
            if req_parts[1] in str_vals and max_jobs[req_parts[1]] == np.inf:
330 b6b1b03e Carles Marti
                max_jobs[req_parts[1]] = int(req_parts[0])
331 b6b1b03e Carles Marti
        elif req_parts[1].isdecimal():
332 b6b1b03e Carles Marti
            req_parts[0] = req_parts[0].replace('q', 'p').replace('pr', 'rp')
333 b6b1b03e Carles Marti
            if req_parts[0] in str_vals and max_jobs[req_parts[0]] == np.inf:
334 b6b1b03e Carles Marti
                max_jobs[req_parts[0]] = int(req_parts[1])
335 b6b1b03e Carles Marti
        else:
336 b6b1b03e Carles Marti
            logger.error(err_msg)
337 b6b1b03e Carles Marti
            raise ValueError(err_msg)
338 09c3325a Carles Marti
339 09c3325a Carles Marti
    return max_jobs
340 09c3325a Carles Marti
341 09c3325a Carles Marti
342 8887f41d Carles
def get_special_atoms():
343 8887f41d Carles
    from ase.data import chemical_symbols
344 17e72a49 Carles
345 5cc4994b Carles
    spec_at_err = '\'special_atoms\' does not have an adequate format.\n' \
346 5cc4994b Carles
                  'Adequate format: (Fe1 Fe) (O1 O)'
347 5cc4994b Carles
    special_atoms = dos_inp.get('Global', 'special_atoms', fallback="False")
348 b1d27be5 Carles
    if special_atoms.lower() in turn_false_answers:
349 90819cc3 Carles Marti
        special_atoms = []
350 b1d27be5 Carles
    else:
351 8949edd0 Carles
        # Converts the string into a list of tuples
352 8949edd0 Carles
        lst_tple = [tuple(pair.replace("(", "").split()) for pair in
353 b1d27be5 Carles
                    special_atoms.split(")")[:-1]]
354 5cc4994b Carles
        if len(lst_tple) == 0:
355 9f7bb440 Carles
            logger.error(spec_at_err)
356 5cc4994b Carles
            raise ValueError(spec_at_err)
357 9f7bb440 Carles
        for i, tup in enumerate(lst_tple):
358 90819cc3 Carles Marti
            if not isinstance(tup, tuple) or len(tup) != 2:
359 9f7bb440 Carles
                logger.error(spec_at_err)
360 5cc4994b Carles
                raise ValueError(spec_at_err)
361 5cc4994b Carles
            if tup[1].capitalize() not in chemical_symbols:
362 5cc4994b Carles
                elem_err = "The second element of the couple should be an " \
363 9f7bb440 Carles
                           "actual element of the periodic table"
364 9f7bb440 Carles
                logger.error(elem_err)
365 5cc4994b Carles
                raise ValueError(elem_err)
366 9f7bb440 Carles
            if tup[0].capitalize() in chemical_symbols:
367 9f7bb440 Carles
                elem_err = "The first element of the couple is already an " \
368 9f7bb440 Carles
                           "actual element of the periodic table, "
369 9f7bb440 Carles
                logger.error(elem_err)
370 9f7bb440 Carles
                raise ValueError(elem_err)
371 9f7bb440 Carles
            for j, tup2 in enumerate(lst_tple):
372 9f7bb440 Carles
                if j <= i:
373 9f7bb440 Carles
                    continue
374 9f7bb440 Carles
                if tup2[0] == tup[0]:
375 9f7bb440 Carles
                    label_err = f'You have specified the label {tup[0]} to ' \
376 8887f41d Carles
                                f'more than one special atom'
377 9f7bb440 Carles
                    logger.error(label_err)
378 9f7bb440 Carles
                    raise ValueError(label_err)
379 b1d27be5 Carles
        special_atoms = lst_tple
380 8887f41d Carles
    return special_atoms
381 8887f41d Carles
382 8887f41d Carles
383 a7128ce1 Carles Marti
# Isolated
384 a7128ce1 Carles Marti
385 9d3b680c Carles Martí
def get_isol_inp_file(code):  # TODO allow spaces in path names
386 9d3b680c Carles Martí
    inp_file_lst = dos_inp.get('Isolated', 'isol_inp_file').split()
387 9d3b680c Carles Martí
    check_inp_file(inp_file_lst[0] if len(inp_file_lst) == 1 else inp_file_lst,
388 9d3b680c Carles Martí
                   code)
389 9d3b680c Carles Martí
    return inp_file_lst[0] if len(inp_file_lst) == 1 else inp_file_lst
390 8887f41d Carles
391 8887f41d Carles
392 95dc2c8e Carles
def get_molec_file():
393 95dc2c8e Carles
    molec_file = dos_inp.get('Isolated', 'molec_file')
394 95dc2c8e Carles
    if not os.path.isfile(molec_file):
395 695dcff8 Carles Marti
        logger.error(f'File {molec_file} not found.')
396 95dc2c8e Carles
        raise FileNotFoundError(f'File {molec_file} not found')
397 95dc2c8e Carles
    return molec_file
398 95dc2c8e Carles
399 95dc2c8e Carles
400 8887f41d Carles
def get_num_conformers():
401 8887f41d Carles
    err_msg = num_error % ('num_conformers', 'positive integer')
402 8887f41d Carles
    num_conformers = try_command(dos_inp.getint, [(ValueError, err_msg)],
403 8887f41d Carles
                                 'Isolated', 'num_conformers', fallback=100)
404 8887f41d Carles
    if num_conformers < 1:
405 8887f41d Carles
        logger.error(err_msg)
406 8887f41d Carles
        raise ValueError(err_msg)
407 8887f41d Carles
    return num_conformers
408 8887f41d Carles
409 8887f41d Carles
410 4670488d Carles Marti
def get_pre_opt():
411 4670488d Carles Marti
    pre_opt_vals = ['uff', 'mmff'] + turn_false_answers
412 4670488d Carles Marti
    check_expect_val(dos_inp.get('Isolated', 'pre_opt').lower(), pre_opt_vals)
413 ad57fd42 Carles Marti
    pre_opt = dos_inp.get('Isolated', 'pre_opt').lower()
414 ad57fd42 Carles Marti
    if pre_opt in turn_false_answers:
415 4670488d Carles Marti
        return False
416 4670488d Carles Marti
    else:
417 4670488d Carles Marti
        return pre_opt
418 b1f6e69d Carles
419 70fa4e74 Carles Marti
420 a7128ce1 Carles Marti
# Screening
421 a7128ce1 Carles Marti
422 9d3b680c Carles Martí
def get_screen_inp_file(code):  # TODO allow spaces in path names
423 9d3b680c Carles Martí
    inp_file_lst = dos_inp.get('Screening', 'screen_inp_file').split()
424 9d3b680c Carles Martí
    check_inp_file(inp_file_lst[0] if len(inp_file_lst) == 1 else inp_file_lst,
425 9d3b680c Carles Martí
                   code)
426 9d3b680c Carles Martí
    return inp_file_lst[0] if len(inp_file_lst) == 1 else inp_file_lst
427 8887f41d Carles
428 8887f41d Carles
429 a765b11c Carles Marti
def get_surf_file():
430 a765b11c Carles Marti
    surf_file = dos_inp.get('Screening', 'surf_file')
431 a765b11c Carles Marti
    if not os.path.isfile(surf_file):
432 695dcff8 Carles Marti
        logger.error(f'File {surf_file} not found.')
433 a765b11c Carles Marti
        raise FileNotFoundError(f'File {surf_file} not found')
434 a765b11c Carles Marti
    return surf_file
435 a765b11c Carles Marti
436 a765b11c Carles Marti
437 8887f41d Carles
def get_sites():
438 8887f41d Carles
    err_msg = 'The value of sites should be a list of atom numbers ' \
439 8887f41d Carles
              '(ie. positive integers) or groups of atom numbers ' \
440 8887f41d Carles
              'grouped by parentheses-like enclosers. \n' \
441 8887f41d Carles
              'eg. 128,(135 138;141) 87 {45, 68}'
442 8887f41d Carles
    # Convert the string into a list of lists
443 8887f41d Carles
    sites = try_command(str2lst,
444 8887f41d Carles
                        [(ValueError, err_msg), (AttributeError, err_msg)],
445 8887f41d Carles
                        dos_inp.get('Screening', 'sites'))
446 8887f41d Carles
    # Check all elements of the list (of lists) are positive integers
447 8887f41d Carles
    for site in sites:
448 8887f41d Carles
        if type(site) is list:
449 8887f41d Carles
            for atom in site:
450 8887f41d Carles
                if atom < 0:
451 8887f41d Carles
                    logger.error(err_msg)
452 8887f41d Carles
                    raise ValueError(err_msg)
453 8887f41d Carles
        elif type(site) is int:
454 8887f41d Carles
            if site < 0:
455 8887f41d Carles
                logger.error(err_msg)
456 8887f41d Carles
                raise ValueError(err_msg)
457 8887f41d Carles
        else:
458 8887f41d Carles
            logger.error(err_msg)
459 8887f41d Carles
            raise ValueError(err_msg)
460 8887f41d Carles
461 8887f41d Carles
    return sites
462 8887f41d Carles
463 8887f41d Carles
464 7dd94df7 Carles Marti
def get_surf_ctrs2():
465 7dd94df7 Carles Marti
    err_msg = 'The value of surf_ctrs2 should be a list of atom numbers ' \
466 7dd94df7 Carles Marti
              '(ie. positive integers) or groups of atom numbers ' \
467 7dd94df7 Carles Marti
              'grouped by parentheses-like enclosers. \n' \
468 7dd94df7 Carles Marti
              'eg. 128,(135 138;141) 87 {45, 68}'
469 7dd94df7 Carles Marti
    # Convert the string into a list of lists
470 7dd94df7 Carles Marti
    surf_ctrs2 = try_command(str2lst,
471 7dd94df7 Carles Marti
                             [(ValueError, err_msg), (AttributeError, err_msg)],
472 7dd94df7 Carles Marti
                             dos_inp.get('Screening', 'surf_ctrs2'))
473 7dd94df7 Carles Marti
    # Check all elements of the list (of lists) are positive integers
474 7dd94df7 Carles Marti
    for ctr in surf_ctrs2:
475 7dd94df7 Carles Marti
        if type(ctr) is list:
476 7dd94df7 Carles Marti
            for atom in ctr:
477 7dd94df7 Carles Marti
                if atom < 0:
478 7dd94df7 Carles Marti
                    logger.error(err_msg)
479 7dd94df7 Carles Marti
                    raise ValueError(err_msg)
480 7dd94df7 Carles Marti
        elif type(ctr) is int:
481 7dd94df7 Carles Marti
            if ctr < 0:
482 7dd94df7 Carles Marti
                logger.error(err_msg)
483 7dd94df7 Carles Marti
                raise ValueError(err_msg)
484 7dd94df7 Carles Marti
        else:
485 7dd94df7 Carles Marti
            logger.error(err_msg)
486 7dd94df7 Carles Marti
            raise ValueError(err_msg)
487 7dd94df7 Carles Marti
488 7dd94df7 Carles Marti
    return surf_ctrs2
489 7dd94df7 Carles Marti
490 7dd94df7 Carles Marti
491 7dd94df7 Carles Marti
def get_molec_ctrs():
492 7dd94df7 Carles Marti
    err_msg = 'The value of molec_ctrs should be a list of atom' \
493 8887f41d Carles
              ' numbers (ie. positive integers) or groups of atom ' \
494 8887f41d Carles
              'numbers enclosed by parentheses-like characters. \n' \
495 8887f41d Carles
              'eg. 128,(135 138;141) 87 {45, 68}'
496 8887f41d Carles
    # Convert the string into a list of lists
497 7dd94df7 Carles Marti
    molec_ctrs = try_command(str2lst,
498 7dd94df7 Carles Marti
                             [(ValueError, err_msg),
499 7dd94df7 Carles Marti
                              (AttributeError, err_msg)],
500 7dd94df7 Carles Marti
                             dos_inp.get('Screening', 'molec_ctrs'))
501 7dd94df7 Carles Marti
    # Check all elements of the list (of lists) are positive integers
502 7dd94df7 Carles Marti
    for ctr in molec_ctrs:
503 7dd94df7 Carles Marti
        if isinstance(ctr, list):
504 7dd94df7 Carles Marti
            for atom in ctr:
505 7dd94df7 Carles Marti
                if atom < 0:
506 7dd94df7 Carles Marti
                    logger.error(err_msg)
507 7dd94df7 Carles Marti
                    raise ValueError(err_msg)
508 7dd94df7 Carles Marti
        elif isinstance(ctr, int):
509 7dd94df7 Carles Marti
            if ctr < 0:
510 7dd94df7 Carles Marti
                logger.error(err_msg)
511 7dd94df7 Carles Marti
                raise ValueError(err_msg)
512 7dd94df7 Carles Marti
        else:
513 7dd94df7 Carles Marti
            logger.error(err_msg)
514 7dd94df7 Carles Marti
            raise ValueError(err_msg)
515 7dd94df7 Carles Marti
516 7dd94df7 Carles Marti
    return molec_ctrs
517 7dd94df7 Carles Marti
518 7dd94df7 Carles Marti
519 7dd94df7 Carles Marti
def get_molec_ctrs2():
520 7dd94df7 Carles Marti
    err_msg = 'The value of molec_ctrs2 should be a list of atom ' \
521 7dd94df7 Carles Marti
              'numbers (ie. positive integers) or groups of atom ' \
522 7dd94df7 Carles Marti
              'numbers enclosed by parentheses-like characters. \n' \
523 7dd94df7 Carles Marti
              'eg. 128,(135 138;141) 87 {45, 68}'
524 7dd94df7 Carles Marti
    # Convert the string into a list of lists
525 7dd94df7 Carles Marti
    molec_ctrs2 = try_command(str2lst, [(ValueError, err_msg),
526 7dd94df7 Carles Marti
                                        (AttributeError, err_msg)],
527 7dd94df7 Carles Marti
                              dos_inp.get('Screening', 'molec_ctrs2'))
528 7dd94df7 Carles Marti
529 8887f41d Carles
    # Check all elements of the list (of lists) are positive integers
530 7dd94df7 Carles Marti
    for ctr in molec_ctrs2:
531 c845c6f2 Carles Marti
        if isinstance(ctr, list):
532 8887f41d Carles
            for atom in ctr:
533 8887f41d Carles
                if atom < 0:
534 8887f41d Carles
                    logger.error(err_msg)
535 8887f41d Carles
                    raise ValueError(err_msg)
536 c845c6f2 Carles Marti
        elif isinstance(ctr, int):
537 8887f41d Carles
            if ctr < 0:
538 8887f41d Carles
                logger.error(err_msg)
539 8887f41d Carles
                raise ValueError(err_msg)
540 8887f41d Carles
        else:
541 8887f41d Carles
            logger.error(err_msg)
542 8887f41d Carles
            raise ValueError(err_msg)
543 8887f41d Carles
544 7dd94df7 Carles Marti
    return molec_ctrs2
545 8887f41d Carles
546 8887f41d Carles
547 7dd94df7 Carles Marti
def get_molec_ctrs3():
548 7dd94df7 Carles Marti
    err_msg = 'The value of molec_ctrs3 should be a list of atom ' \
549 c845c6f2 Carles Marti
              'numbers (ie. positive integers) or groups of atom ' \
550 c845c6f2 Carles Marti
              'numbers enclosed by parentheses-like characters. \n' \
551 c845c6f2 Carles Marti
              'eg. 128,(135 138;141) 87 {45, 68}'
552 c845c6f2 Carles Marti
    # Convert the string into a list of lists
553 7dd94df7 Carles Marti
    molec_ctrs3 = try_command(str2lst, [(ValueError, err_msg),
554 7dd94df7 Carles Marti
                                        (AttributeError, err_msg)],
555 7dd94df7 Carles Marti
                              dos_inp.get('Screening', 'molec_ctrs3'))
556 c845c6f2 Carles Marti
557 c845c6f2 Carles Marti
    # Check all elements of the list (of lists) are positive integers
558 7dd94df7 Carles Marti
    for ctr in molec_ctrs3:
559 c845c6f2 Carles Marti
        if isinstance(ctr, list):
560 c845c6f2 Carles Marti
            for atom in ctr:
561 c845c6f2 Carles Marti
                if atom < 0:
562 c845c6f2 Carles Marti
                    logger.error(err_msg)
563 c845c6f2 Carles Marti
                    raise ValueError(err_msg)
564 c845c6f2 Carles Marti
        elif isinstance(ctr, int):
565 c845c6f2 Carles Marti
            if ctr < 0:
566 c845c6f2 Carles Marti
                logger.error(err_msg)
567 c845c6f2 Carles Marti
                raise ValueError(err_msg)
568 c845c6f2 Carles Marti
        else:
569 c845c6f2 Carles Marti
            logger.error(err_msg)
570 c845c6f2 Carles Marti
            raise ValueError(err_msg)
571 c845c6f2 Carles Marti
572 7dd94df7 Carles Marti
    return molec_ctrs3
573 c845c6f2 Carles Marti
574 c845c6f2 Carles Marti
575 a98d4172 Carles Marti
def get_max_helic_angle():
576 a98d4172 Carles Marti
    err_msg = "'max_helic_angle' must be a positive number in degrees"
577 a98d4172 Carles Marti
    max_helic_angle = try_command(dos_inp.getfloat, [(ValueError, err_msg)],
578 a98d4172 Carles Marti
                                  'Screening', 'max_helic_angle',
579 a98d4172 Carles Marti
                                  fallback=180.0)
580 a98d4172 Carles Marti
    if max_helic_angle < 0:
581 a98d4172 Carles Marti
        logger.error(err_msg)
582 a98d4172 Carles Marti
        raise ValueError(err_msg)
583 a98d4172 Carles Marti
584 a98d4172 Carles Marti
    return max_helic_angle
585 a98d4172 Carles Marti
586 a98d4172 Carles Marti
587 8af49f6d Carles Marti
def get_select_magns():
588 8af49f6d Carles Marti
    select_magns_vals = ['energy', 'moi']
589 8af49f6d Carles Marti
    select_magns_str = dos_inp.get('Screening', 'select_magns',
590 14e0b660 Carles Martí
                                   fallback='moi')
591 8af49f6d Carles Marti
    select_magns_str.replace(',', ' ').replace(';', ' ')
592 8af49f6d Carles Marti
    select_magns = select_magns_str.split(' ')
593 8af49f6d Carles Marti
    select_magns = [m.lower() for m in select_magns]
594 8af49f6d Carles Marti
    for m in select_magns:
595 8af49f6d Carles Marti
        check_expect_val(m, select_magns_vals)
596 8af49f6d Carles Marti
    return select_magns
597 8af49f6d Carles Marti
598 8af49f6d Carles Marti
599 af2bf62f Carles Marti
def get_confs_per_magn():
600 af2bf62f Carles Marti
    err_msg = num_error % ('confs_per_magn', 'positive integer')
601 af2bf62f Carles Marti
    confs_per_magn = try_command(dos_inp.getint, [(ValueError, err_msg)],
602 af2bf62f Carles Marti
                                 'Screening', 'confs_per_magn', fallback=2)
603 af2bf62f Carles Marti
    if confs_per_magn <= 0:
604 af2bf62f Carles Marti
        logger.error(err_msg)
605 af2bf62f Carles Marti
        raise ValueError(err_msg)
606 af2bf62f Carles Marti
    return confs_per_magn
607 af2bf62f Carles Marti
608 af2bf62f Carles Marti
609 c845c6f2 Carles Marti
def get_surf_norm_vect():
610 d6da8693 Carles Marti
    err = "'surf_norm_vect' must be a 3 component vector, 'x', 'y' or 'z', " \
611 d6da8693 Carles Marti
          "'auto' or 'asann'."
612 9c16a7e2 Carles Marti
    cart_axes = {'x': [1.0, 0.0, 0.0], '-x': [-1.0, 0.0, 0.0],
613 9c16a7e2 Carles Marti
                 'y': [0.0, 1.0, 0.0], '-y': [0.0, -1.0, 0.0],
614 9c16a7e2 Carles Marti
                 'z': [0.0, 0.0, 1.0], '-z': [0.0, 0.0, -1.0]}
615 c845c6f2 Carles Marti
    surf_norm_vect_str = dos_inp.get('Screening', 'surf_norm_vect',
616 d6da8693 Carles Marti
                                     fallback="auto").lower()
617 d6da8693 Carles Marti
    if surf_norm_vect_str == "asann" or surf_norm_vect_str == "auto":
618 d6da8693 Carles Marti
        return 'auto'
619 9c16a7e2 Carles Marti
    if surf_norm_vect_str in cart_axes:
620 d6da8693 Carles Marti
        return np.array(cart_axes[surf_norm_vect_str])
621 d6da8693 Carles Marti
    surf_norm_vect = try_command(str2lst, [(ValueError, err)],
622 d6da8693 Carles Marti
                                 surf_norm_vect_str, float)
623 d6da8693 Carles Marti
    if len(surf_norm_vect) != 3:
624 d6da8693 Carles Marti
        logger.error(err)
625 d6da8693 Carles Marti
        raise ValueError(err)
626 3d56e566 Carles Marti
627 b6b1b03e Carles Marti
    return np.array(surf_norm_vect) / np.linalg.norm(surf_norm_vect)
628 c845c6f2 Carles Marti
629 c845c6f2 Carles Marti
630 fe91ddb2 Carles Marti
def get_adsorption_height():
631 fe91ddb2 Carles Marti
    err_msg = num_error % ('adsorption_height', 'positive number')
632 fe91ddb2 Carles Marti
    ads_height = try_command(dos_inp.getfloat, [(ValueError, err_msg)],
633 fe91ddb2 Carles Marti
                             'Screening', 'adsorption_height', fallback=2.5)
634 fe91ddb2 Carles Marti
    if ads_height <= 0:
635 fe91ddb2 Carles Marti
        logger.error(err_msg)
636 fe91ddb2 Carles Marti
        raise ValueError(err_msg)
637 fe91ddb2 Carles Marti
    return ads_height
638 fe91ddb2 Carles Marti
639 fe91ddb2 Carles Marti
640 7dd94df7 Carles Marti
def get_set_angles():
641 609104e3 Carles Marti
    set_vals = ['euler', 'internal']
642 7dd94df7 Carles Marti
    check_expect_val(dos_inp.get('Screening', 'set_angles').lower(), set_vals)
643 5261a07f Carles Marti
    set_angles = dos_inp.get('Screening', 'set_angles',
644 5261a07f Carles Marti
                             fallback='euler').lower()
645 7dd94df7 Carles Marti
    return set_angles
646 a7c7b43e Carles Marti
647 a7c7b43e Carles Marti
648 8887f41d Carles
def get_pts_per_angle():
649 d4bedc18 Carles Marti
    err_msg = num_error % ('sample_points_per_angle', 'positive integer')
650 8887f41d Carles
    pts_per_angle = try_command(dos_inp.getint,
651 8887f41d Carles
                                [(ValueError, err_msg)],
652 8887f41d Carles
                                'Screening', 'sample_points_per_angle',
653 8887f41d Carles
                                fallback=3)
654 d4bedc18 Carles Marti
    if pts_per_angle <= 0:
655 d4bedc18 Carles Marti
        logger.error(err_msg)
656 d4bedc18 Carles Marti
        raise ValueError(err_msg)
657 8887f41d Carles
    return pts_per_angle
658 8887f41d Carles
659 8887f41d Carles
660 7d97341d Carles Marti
def get_max_structures():
661 7d97341d Carles Marti
    err_msg = num_error % ('max_structures', 'positive integer')
662 21209d96 Carles Marti
    max_structs = dos_inp.get('Screening', 'max_structures', fallback="False")
663 21209d96 Carles Marti
    if max_structs.lower() in turn_false_answers:
664 21209d96 Carles Marti
        return np.inf
665 21209d96 Carles Marti
    if try_command(int, [(ValueError, err_msg)], max_structs) <= 0:
666 7d97341d Carles Marti
        logger.error(err_msg)
667 7d97341d Carles Marti
        raise ValueError(err_msg)
668 21209d96 Carles Marti
    return int(max_structs)
669 7d97341d Carles Marti
670 7d97341d Carles Marti
671 8887f41d Carles
def get_coll_thrsld():
672 fe91ddb2 Carles Marti
    err_msg = num_error % ('collision_threshold', 'positive number')
673 eef995b8 Carles Marti
    coll_thrsld_str = dos_inp.get('Screening', 'collision_threshold',
674 a44ad3c2 Carles Martí
                                  fallback="False")
675 eef995b8 Carles Marti
    if coll_thrsld_str.lower() in turn_false_answers:
676 eef995b8 Carles Marti
        return False
677 eef995b8 Carles Marti
    coll_thrsld = try_command(float, [(ValueError, err_msg)], coll_thrsld_str)
678 8887f41d Carles
679 8887f41d Carles
    if coll_thrsld <= 0:
680 8887f41d Carles
        logger.error(err_msg)
681 8887f41d Carles
        raise ValueError(err_msg)
682 8887f41d Carles
683 8887f41d Carles
    return coll_thrsld
684 8887f41d Carles
685 8887f41d Carles
686 f98ba5b0 Carles Marti
def get_min_coll_height(norm_vect):
687 5fb01677 Carles Marti
    err_msg = num_error % ('min_coll_height', 'decimal number')
688 5fb01677 Carles Marti
    min_coll_height = dos_inp.get('Screening', 'min_coll_height',
689 5fb01677 Carles Marti
                                  fallback="False")
690 5fb01677 Carles Marti
    if min_coll_height.lower() in turn_false_answers:
691 f98ba5b0 Carles Marti
        return False
692 f98ba5b0 Carles Marti
    min_coll_height = try_command(float, [(ValueError, err_msg)],
693 f98ba5b0 Carles Marti
                                  min_coll_height)
694 f98ba5b0 Carles Marti
    cart_axes = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0],
695 f98ba5b0 Carles Marti
                 [-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]]
696 f98ba5b0 Carles Marti
    err_msg = "'min_coll_height' option is only implemented for " \
697 f98ba5b0 Carles Marti
              "'surf_norm_vect' to be one of the x, y or z axes. "
698 8279a51d Carles Marti
    if not isinstance(norm_vect, str) or norm_vect != 'auto':
699 d6da8693 Carles Marti
        check_expect_val(norm_vect.tolist(), cart_axes, err_msg)
700 5fb01677 Carles Marti
    return min_coll_height
701 8887f41d Carles
702 8887f41d Carles
703 9cd032cf Carles Marti
def get_exclude_ads_ctr():
704 9cd032cf Carles Marti
    err_msg = "exclude_ads_ctr must have a boolean value."
705 9cd032cf Carles Marti
    exclude_ads_ctr = try_command(dos_inp.getboolean, [(ValueError, err_msg)],
706 9cd032cf Carles Marti
                                  "Screening", "exclude_ads_ctr",
707 9cd032cf Carles Marti
                                  fallback=False)
708 9cd032cf Carles Marti
    return exclude_ads_ctr
709 9cd032cf Carles Marti
710 9cd032cf Carles Marti
711 c25aa299 Carles Marti
def get_H_donor(spec_atoms):
712 c25aa299 Carles Marti
    from ase.data import chemical_symbols
713 14e0b660 Carles Martí
    err_msg = "The value of 'h_donor' must be either False, a chemical symbol "\
714 c25aa299 Carles Marti
              "or an atom index"
715 c25aa299 Carles Marti
    h_donor_str = dos_inp.get('Screening', 'h_donor', fallback="False")
716 c25aa299 Carles Marti
    h_donor = []
717 c25aa299 Carles Marti
    if h_donor_str.lower() in turn_false_answers:
718 c25aa299 Carles Marti
        return False
719 c25aa299 Carles Marti
    err = False
720 c25aa299 Carles Marti
    for el in h_donor_str.split():
721 c25aa299 Carles Marti
        try:
722 c25aa299 Carles Marti
            h_donor.append(int(el))
723 c25aa299 Carles Marti
        except ValueError:
724 c25aa299 Carles Marti
            if el not in chemical_symbols + [nw_sym for pairs in spec_atoms
725 c25aa299 Carles Marti
                                             for nw_sym in pairs]:
726 c25aa299 Carles Marti
                err = True
727 c25aa299 Carles Marti
            else:
728 c25aa299 Carles Marti
                h_donor.append(el)
729 c25aa299 Carles Marti
        finally:
730 c25aa299 Carles Marti
            if err:
731 c25aa299 Carles Marti
                logger.error(err_msg)
732 c25aa299 Carles Marti
                ValueError(err_msg)
733 c25aa299 Carles Marti
    return h_donor
734 c25aa299 Carles Marti
735 c25aa299 Carles Marti
736 c25aa299 Carles Marti
def get_H_acceptor(spec_atoms):
737 c25aa299 Carles Marti
    from ase.data import chemical_symbols
738 c25aa299 Carles Marti
    err_msg = "The value of 'h_acceptor' must be either 'all', a chemical " \
739 30e34792 Carles Martí
              "symbol or an atom index."
740 c25aa299 Carles Marti
    h_acceptor_str = dos_inp.get('Screening', 'h_acceptor', fallback="all")
741 c25aa299 Carles Marti
    if h_acceptor_str.lower() == "all":
742 c25aa299 Carles Marti
        return "all"
743 c25aa299 Carles Marti
    h_acceptor = []
744 c25aa299 Carles Marti
    err = False
745 c25aa299 Carles Marti
    for el in h_acceptor_str.split():
746 c25aa299 Carles Marti
        try:
747 c25aa299 Carles Marti
            h_acceptor.append(int(el))
748 c25aa299 Carles Marti
        except ValueError:
749 c25aa299 Carles Marti
            if el not in chemical_symbols + [nw_sym for pairs in spec_atoms
750 c25aa299 Carles Marti
                                             for nw_sym in pairs]:
751 c25aa299 Carles Marti
                err = True
752 c25aa299 Carles Marti
            else:
753 c25aa299 Carles Marti
                h_acceptor.append(el)
754 c25aa299 Carles Marti
        finally:
755 c25aa299 Carles Marti
            if err:
756 c25aa299 Carles Marti
                logger.error(err_msg)
757 30e34792 Carles Martí
                raise ValueError(err_msg)
758 c25aa299 Carles Marti
    return h_acceptor
759 a765b11c Carles Marti
760 a765b11c Carles Marti
761 b75bf97d Carles Marti
def get_use_molec_file():
762 b75bf97d Carles Marti
    use_molec_file = dos_inp.get('Screening', 'use_molec_file',
763 b75bf97d Carles Marti
                                 fallback='False')
764 13731dc4 Carles Marti
    if use_molec_file.lower() in turn_false_answers:
765 b75bf97d Carles Marti
        return False
766 b75bf97d Carles Marti
    if not os.path.isfile(use_molec_file):
767 b75bf97d Carles Marti
        logger.error(f'File {use_molec_file} not found.')
768 b75bf97d Carles Marti
        raise FileNotFoundError(f'File {use_molec_file} not found')
769 b75bf97d Carles Marti
770 b75bf97d Carles Marti
    return use_molec_file
771 b75bf97d Carles Marti
772 b75bf97d Carles Marti
773 a7128ce1 Carles Marti
# Refinement
774 a7128ce1 Carles Marti
775 9d3b680c Carles Martí
def get_refine_inp_file(code):
776 9d3b680c Carles Martí
    inp_file_lst = dos_inp.get('Refinement', 'refine_inp_file').split()
777 9d3b680c Carles Martí
    check_inp_file(inp_file_lst[0] if len(inp_file_lst) == 1 else inp_file_lst,
778 9d3b680c Carles Martí
                   code)
779 9d3b680c Carles Martí
    return inp_file_lst[0] if len(inp_file_lst) == 1 else inp_file_lst
780 8887f41d Carles
781 8887f41d Carles
782 8887f41d Carles
def get_energy_cutoff():
783 8887f41d Carles
    err_msg = num_error % ('energy_cutoff', 'positive decimal number')
784 8887f41d Carles
    energy_cutoff = try_command(dos_inp.getfloat,
785 8887f41d Carles
                                [(ValueError, err_msg)],
786 8887f41d Carles
                                'Refinement', 'energy_cutoff', fallback=0.5)
787 8887f41d Carles
    if energy_cutoff < 0:
788 8887f41d Carles
        logger.error(err_msg)
789 8887f41d Carles
        raise ValueError(err_msg)
790 8887f41d Carles
    return energy_cutoff
791 8887f41d Carles
792 8887f41d Carles
793 c25aa299 Carles Marti
# Read input parameters
794 c25aa299 Carles Marti
795 8887f41d Carles
def read_input(in_file):
796 9d3b680c Carles Martí
    from modules.formats import adapt_format
797 9d3b680c Carles Martí
798 9d3b680c Carles Martí
    err_msg = False
799 8887f41d Carles
    try:
800 8887f41d Carles
        dos_inp.read(in_file)
801 8887f41d Carles
    except MissingSectionHeaderError as e:
802 8887f41d Carles
        logger.error('There are options in the input file without a Section '
803 695dcff8 Carles Marti
                     'header.')
804 14e0b660 Carles Martí
        err_msg = e
805 8887f41d Carles
    except DuplicateOptionError as e:
806 8887f41d Carles
        logger.error('There is an option in the input file that has been '
807 0bca5abb Carles Marti
                     'specified more than once.')
808 14e0b660 Carles Martí
        err_msg = e
809 8887f41d Carles
    except Exception as e:
810 14e0b660 Carles Martí
        err_msg = e
811 8887f41d Carles
    else:
812 14e0b660 Carles Martí
        err_msg = False
813 8887f41d Carles
    finally:
814 14e0b660 Carles Martí
        if isinstance(err_msg, BaseException):
815 14e0b660 Carles Martí
            raise err_msg
816 17e72a49 Carles
817 7dd94df7 Carles Marti
    inp_vars = {}
818 8887f41d Carles
819 8887f41d Carles
    # Global
820 8887f41d Carles
    if not dos_inp.has_section('Global'):
821 8887f41d Carles
        logger.error(no_sect_err % 'Global')
822 8887f41d Carles
        raise NoSectionError('Global')
823 8887f41d Carles
824 8887f41d Carles
    # Mandatory options
825 8887f41d Carles
    # Checks whether the mandatory options 'run_type', 'code', etc. are present.
826 14e0b660 Carles Martí
    glob_mand_opts = ['run_type', 'code', 'batch_q_sys']
827 7fd58762 Carles
    for opt in glob_mand_opts:
828 8887f41d Carles
        if not dos_inp.has_option('Global', opt):
829 8887f41d Carles
            logger.error(no_opt_err % (opt, 'Global'))
830 8887f41d Carles
            raise NoOptionError(opt, 'Global')
831 8887f41d Carles
832 8887f41d Carles
    # Gets which sections are to be carried out
833 8887f41d Carles
    isolated, screening, refinement = get_run_type()
834 7dd94df7 Carles Marti
    inp_vars['isolated'] = isolated
835 7dd94df7 Carles Marti
    inp_vars['screening'] = screening
836 7dd94df7 Carles Marti
    inp_vars['refinement'] = refinement
837 14e0b660 Carles Martí
    inp_vars['code'] = get_code()
838 7dd94df7 Carles Marti
    inp_vars['batch_q_sys'] = get_batch_q_sys()
839 8887f41d Carles
840 99afde40 Carles
    # Dependent options:
841 7dd94df7 Carles Marti
    if inp_vars['batch_q_sys']:
842 7dd94df7 Carles Marti
        inp_vars['max_jobs'] = get_max_jobs()
843 7dd94df7 Carles Marti
        if inp_vars['batch_q_sys'] != 'local':
844 09c3325a Carles Marti
            if not dos_inp.has_option('Global', 'subm_script'):
845 09c3325a Carles Marti
                logger.error(no_opt_err % ('subm_script', 'Global'))
846 09c3325a Carles Marti
                raise NoOptionError('subm_script', 'Global')
847 7dd94df7 Carles Marti
            inp_vars['subm_script'] = get_subm_script()
848 99afde40 Carles
849 8887f41d Carles
    # Facultative options (Default/Fallback value present)
850 9d3b680c Carles Martí
    inp_vars['pbc_cell'] = get_pbc_cell()
851 7dd94df7 Carles Marti
    inp_vars['project_name'] = get_project_name()
852 8b3d4772 Carles Marti
    # inp_vars['relaunch_err'] = get_relaunch_err()
853 7dd94df7 Carles Marti
    inp_vars['special_atoms'] = get_special_atoms()
854 8887f41d Carles
855 8887f41d Carles
    # Isolated
856 b1d27be5 Carles
    if isolated:
857 b606c71a Carles
        if not dos_inp.has_section('Isolated'):
858 b606c71a Carles
            logger.error(no_sect_err % 'Isolated')
859 b606c71a Carles
            raise NoSectionError('Isolated')
860 8887f41d Carles
        # Mandatory options
861 8887f41d Carles
        # Checks whether the mandatory options are present.
862 95dc2c8e Carles
        iso_mand_opts = ['isol_inp_file', 'molec_file']
863 8887f41d Carles
        for opt in iso_mand_opts:
864 8887f41d Carles
            if not dos_inp.has_option('Isolated', opt):
865 8887f41d Carles
                logger.error(no_opt_err % (opt, 'Isolated'))
866 8887f41d Carles
                raise NoOptionError(opt, 'Isolated')
867 9d3b680c Carles Martí
        inp_vars['isol_inp_file'] = get_isol_inp_file(inp_vars['code'])
868 7dd94df7 Carles Marti
        inp_vars['molec_file'] = get_molec_file()
869 8887f41d Carles
870 9d3b680c Carles Martí
        # Checks for PBC
871 9d3b680c Carles Martí
        atms = adapt_format('ase', inp_vars['molec_file'],
872 9d3b680c Carles Martí
                            inp_vars['special_atoms'])
873 9d3b680c Carles Martí
        if inp_vars['code'] == 'vasp' and np.linalg.det(atms.cell) == 0.0 \
874 9d3b680c Carles Martí
                and inp_vars['pbc_cell'] is False:
875 a5d76bfb Carles Martí
            err_msg = "When running calculations with 'VASP', the PBC cell " \
876 9d3b680c Carles Martí
                      "should be provided either implicitely inside " \
877 9d3b680c Carles Martí
                      "'molec_file' or by setting the 'pbc_cell' option."
878 9d3b680c Carles Martí
            logger.error(err_msg)
879 9d3b680c Carles Martí
            raise ValueError(err_msg)
880 a5d76bfb Carles Martí
        elif inp_vars['pbc_cell'] is False and np.linalg.det(atms.cell) != 0.0:
881 a5d76bfb Carles Martí
            inp_vars['pbc_cell'] = atms.cell
882 a5d76bfb Carles Martí
            logger.info(f"Obtained pbc_cell from '{inp_vars['molec_file']}' "
883 a5d76bfb Carles Martí
                        f"file.")
884 a5d76bfb Carles Martí
        elif (atms.cell != 0).any() and not np.allclose(inp_vars['pbc_cell'],
885 a5d76bfb Carles Martí
                                                        atms.cell):
886 9d3b680c Carles Martí
            logger.warning("'molec_file' has an implicit cell defined "
887 a5d76bfb Carles Martí
                           f"different than 'pbc_cell'.\n"
888 a5d76bfb Carles Martí
                           f"'molec_file' = {atms.cell}.\n"
889 a5d76bfb Carles Martí
                           f"'pbc_cell' = {inp_vars['pbc_cell']}).\n"
890 a5d76bfb Carles Martí
                           "'pbc_cell' value will be used.")
891 9d3b680c Carles Martí
892 8887f41d Carles
        # Facultative options (Default/Fallback value present)
893 7dd94df7 Carles Marti
        inp_vars['num_conformers'] = get_num_conformers()
894 7dd94df7 Carles Marti
        inp_vars['pre_opt'] = get_pre_opt()
895 8887f41d Carles
896 8887f41d Carles
    # Screening
897 b1d27be5 Carles
    if screening:
898 772b40e5 Carles
        if not dos_inp.has_section('Screening'):
899 9f7bb440 Carles
            logger.error(no_sect_err % 'Screening')
900 772b40e5 Carles
            raise NoSectionError('Screening')
901 8887f41d Carles
        # Mandatory options:
902 8887f41d Carles
        # Checks whether the mandatory options are present.
903 a765b11c Carles Marti
        screen_mand_opts = ['screen_inp_file', 'surf_file', 'sites',
904 7dd94df7 Carles Marti
                            'molec_ctrs']
905 8887f41d Carles
        for opt in screen_mand_opts:
906 772b40e5 Carles
            if not dos_inp.has_option('Screening', opt):
907 9f7bb440 Carles
                logger.error(no_opt_err % (opt, 'Screening'))
908 b1d27be5 Carles
                raise NoOptionError(opt, 'Screening')
909 9d3b680c Carles Martí
        inp_vars['screen_inp_file'] = get_screen_inp_file(inp_vars['code'])
910 7dd94df7 Carles Marti
        inp_vars['surf_file'] = get_surf_file()
911 7dd94df7 Carles Marti
        inp_vars['sites'] = get_sites()
912 7dd94df7 Carles Marti
        inp_vars['molec_ctrs'] = get_molec_ctrs()
913 8887f41d Carles
914 9d3b680c Carles Martí
        # Checks for PBC
915 a5d76bfb Carles Martí
        # Checks for PBC
916 9d3b680c Carles Martí
        atms = adapt_format('ase', inp_vars['surf_file'],
917 9d3b680c Carles Martí
                            inp_vars['special_atoms'])
918 9d3b680c Carles Martí
        if inp_vars['code'] == 'vasp' and np.linalg.det(atms.cell) == 0.0 \
919 9d3b680c Carles Martí
                and inp_vars['pbc_cell'] is False:
920 a5d76bfb Carles Martí
            err_msg = "When running calculations with 'VASP', the PBC cell " \
921 9d3b680c Carles Martí
                      "should be provided either implicitely inside " \
922 a5d76bfb Carles Martí
                      "'surf_file' or by setting the 'pbc_cell' option."
923 9d3b680c Carles Martí
            logger.error(err_msg)
924 9d3b680c Carles Martí
            raise ValueError(err_msg)
925 a5d76bfb Carles Martí
        elif inp_vars['pbc_cell'] is False and np.linalg.det(atms.cell) != 0.0:
926 a5d76bfb Carles Martí
            inp_vars['pbc_cell'] = atms.cell
927 a5d76bfb Carles Martí
            logger.info(f"Obtained pbc_cell from '{inp_vars['surf_file']}' "
928 a5d76bfb Carles Martí
                        f"file.")
929 a5d76bfb Carles Martí
        elif (atms.cell != 0).any() and not np.allclose(inp_vars['pbc_cell'],
930 a5d76bfb Carles Martí
                                                        atms.cell):
931 a5d76bfb Carles Martí
            logger.warning("'surf_file' has an implicit cell defined "
932 a5d76bfb Carles Martí
                           f"different than 'pbc_cell'.\n"
933 a5d76bfb Carles Martí
                           f"'surf_file' = {atms.cell}.\n"
934 a5d76bfb Carles Martí
                           f"'pbc_cell' = {inp_vars['pbc_cell']}).\n"
935 9d3b680c Carles Martí
                           "'pbc_cell' value will be used.")
936 9d3b680c Carles Martí
937 8887f41d Carles
        # Facultative options (Default value present)
938 7dd94df7 Carles Marti
        inp_vars['select_magns'] = get_select_magns()
939 7dd94df7 Carles Marti
        inp_vars['confs_per_magn'] = get_confs_per_magn()
940 7dd94df7 Carles Marti
        inp_vars['surf_norm_vect'] = get_surf_norm_vect()
941 fe91ddb2 Carles Marti
        inp_vars['adsorption_height'] = get_adsorption_height()
942 7dd94df7 Carles Marti
        inp_vars['set_angles'] = get_set_angles()
943 7dd94df7 Carles Marti
        inp_vars['sample_points_per_angle'] = get_pts_per_angle()
944 7dd94df7 Carles Marti
        inp_vars['collision_threshold'] = get_coll_thrsld()
945 f98ba5b0 Carles Marti
        inp_vars['min_coll_height'] = get_min_coll_height(
946 f98ba5b0 Carles Marti
            inp_vars['surf_norm_vect'])
947 8b3d4772 Carles Marti
        if inp_vars['min_coll_height'] is False \
948 8b3d4772 Carles Marti
                and inp_vars['collision_threshold'] is False:
949 8b3d4772 Carles Marti
            logger.warning("Collisions are deactivated: Overlapping of "
950 8b3d4772 Carles Marti
                           "adsorbate and surface is possible")
951 9cd032cf Carles Marti
        inp_vars['exclude_ads_ctr'] = get_exclude_ads_ctr()
952 b75bf97d Carles Marti
        inp_vars['h_donor'] = get_H_donor(inp_vars['special_atoms'])
953 b75bf97d Carles Marti
        inp_vars['max_structures'] = get_max_structures()
954 b75bf97d Carles Marti
        inp_vars['use_molec_file'] = get_use_molec_file()
955 8887f41d Carles
956 8b3d4772 Carles Marti
        # Options depending on the value of others
957 609104e3 Carles Marti
        if inp_vars['set_angles'] == "internal":
958 609104e3 Carles Marti
            internal_opts = ['molec_ctrs2', 'molec_ctrs3', 'surf_ctrs2',
959 609104e3 Carles Marti
                             'max_helic_angle']
960 609104e3 Carles Marti
            for opt in internal_opts:
961 7dd94df7 Carles Marti
                if not dos_inp.has_option('Screening', opt):
962 7dd94df7 Carles Marti
                    logger.error(no_opt_err % (opt, 'Screening'))
963 7dd94df7 Carles Marti
                    raise NoOptionError(opt, 'Screening')
964 a98d4172 Carles Marti
            inp_vars['max_helic_angle'] = get_max_helic_angle()
965 7dd94df7 Carles Marti
            inp_vars['molec_ctrs2'] = get_molec_ctrs2()
966 7dd94df7 Carles Marti
            inp_vars['molec_ctrs3'] = get_molec_ctrs3()
967 7dd94df7 Carles Marti
            inp_vars['surf_ctrs2'] = get_surf_ctrs2()
968 7dd94df7 Carles Marti
            if len(inp_vars["molec_ctrs2"]) != len(inp_vars['molec_ctrs']) \
969 7dd94df7 Carles Marti
                    or len(inp_vars["molec_ctrs3"]) != \
970 7dd94df7 Carles Marti
                    len(inp_vars['molec_ctrs']) \
971 7dd94df7 Carles Marti
                    or len(inp_vars['surf_ctrs2']) != len(inp_vars['sites']):
972 14e0b660 Carles Martí
                err_msg = "'molec_ctrs' 'molec_ctrs2' and 'molec_ctrs3' must " \
973 14e0b660 Carles Martí
                          "have the same number of indices "
974 14e0b660 Carles Martí
                logger.error(err_msg)
975 14e0b660 Carles Martí
                raise ValueError(err_msg)
976 7dd94df7 Carles Marti
977 c25aa299 Carles Marti
        if inp_vars['h_donor'] is False:
978 c25aa299 Carles Marti
            inp_vars['h_acceptor'] = False
979 c25aa299 Carles Marti
        else:
980 c25aa299 Carles Marti
            inp_vars['h_acceptor'] = get_H_acceptor(inp_vars['special_atoms'])
981 c25aa299 Carles Marti
982 8887f41d Carles
    # Refinement
983 b1d27be5 Carles
    if refinement:
984 8887f41d Carles
        if not dos_inp.has_section('Refinement'):
985 8887f41d Carles
            logger.error(no_sect_err % 'Refinement')
986 8887f41d Carles
            raise NoSectionError('Refinement')
987 8887f41d Carles
        # Mandatory options
988 8887f41d Carles
        # Checks whether the mandatory options are present.
989 8887f41d Carles
        ref_mand_opts = ['refine_inp_file']
990 8887f41d Carles
        for opt in ref_mand_opts:
991 8887f41d Carles
            if not dos_inp.has_option('Refinement', opt):
992 8887f41d Carles
                logger.error(no_opt_err % (opt, 'Refinement'))
993 8887f41d Carles
                raise NoOptionError(opt, 'Refinement')
994 9d3b680c Carles Martí
        inp_vars['refine_inp_file'] = get_refine_inp_file(inp_vars['code'])
995 8887f41d Carles
996 8887f41d Carles
        # Facultative options (Default value present)
997 7dd94df7 Carles Marti
        inp_vars['energy_cutoff'] = get_energy_cutoff()
998 b1d27be5 Carles
        # end energy_cutoff
999 9f7bb440 Carles
1000 a5cc42ff Carles Marti
    return_vars_str = "\n\t".join([str(key) + ": " + str(value)
1001 7dd94df7 Carles Marti
                                   for key, value in inp_vars.items()])
1002 14e0b660 Carles Martí
    logger.info(f'Correctly read {in_file} parameters:'
1003 14e0b660 Carles Martí
                f' \n\n\t{return_vars_str}\n')
1004 d8a6314e Carles
1005 7dd94df7 Carles Marti
    return inp_vars
1006 8887f41d Carles
1007 8887f41d Carles
1008 8887f41d Carles
if __name__ == "__main__":
1009 8887f41d Carles
    import sys
1010 8887f41d Carles
1011 8887f41d Carles
    print(read_input(sys.argv[1]))