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

dockonsurf / modules / dos_input.py @ 365d5b9a

Historique | Voir | Annoter | Télécharger (33,16 ko)

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

3
Functions
4
try_command:Tries to run a command and logs its exceptions (expected and not).
5
str2lst: Converts a string of integers, and groups of them, to a list of lists.
6
check_expect_val: Checks whether the value of an option has an adequate value.
7
read_input: Sets up the calculation by reading the parameters from input file.
8
get_run_type: Gets 'run_type' value and checks that its value is acceptable.
9
get_code: Gets 'code' value and checks that its value is acceptable.
10
get_batch_q_sys: Gets 'batch_q_sys' value and checks that its value is
11
acceptable.
12
get_relaunch_err: Gets 'relaunch_err' value and checks that its value is
13
acceptable.
14
get_max_jobs: Gets 'max_jobs' value and checks that its value is acceptable.
15
get_special_atoms: Gets 'special_atoms' value and checks that its value is
16
acceptable.
17
get_isol_inp_file: Gets 'isol_inp_file' value and checks that its value is
18
acceptable.
19
get_cluster_magns: Gets 'cluster_magns' value and checks that its value is
20
acceptable.
21
get_num_conformers: Gets 'num_conformers' value and checks that its value is
22
acceptable.
23
get_num_prom_cand: Gets 'num_prom_cand' value and checks that its value is
24
acceptable.
25
get_iso_rmsd: Gets 'iso_rmsd' value and checks that its value is acceptable.
26
get_pre_opt: Gets 'pre_opt' value and checks that its value is acceptable.
27
get_screen_inp_file: Gets 'screen_inp_file' value and checks that its value is
28
acceptable.
29
get_sites: Gets 'sites' value and checks that its value is acceptable.
30
get_molec_ctrs: Gets 'molec_ctrs' value and checks that its value is
31
acceptable.
32
get_try_disso: Gets 'try_disso' value and checks that its value is acceptable.
33
get_pts_per_angle: Gets 'pts_per_angle' value and checks that its value is
34
acceptable.
35
get_coll_thrsld: Gets 'coll_thrsld' value and checks that its value is
36
acceptable.
37
get_screen_rmsd: Gets 'screen_rmsd' value and checks that its value is
38
acceptable.
39
get_coll_bottom_z: Gets 'coll_bottom_z' value and checks that its value is
40
acceptable.
41
get_refine_inp_file: Gets 'refine_inp_file' value and checks that its value is
42
acceptable.
43
get_energy_cutoff: Gets 'energy_cutoff' value and checks that its value is
44
acceptable.
45
"""
46
import os.path
47
import logging
48
from configparser import ConfigParser, NoSectionError, NoOptionError, \
49
    MissingSectionHeaderError, DuplicateOptionError
50
import numpy as np
51
from modules.utilities import try_command
52

    
53
logger = logging.getLogger('DockOnSurf')
54

    
55
dos_inp = ConfigParser(inline_comment_prefixes='#',
56
                       empty_lines_in_values=False)
57

    
58
new_answers = {'n': False, 'none': False, 'nay': False,
59
               'y': True, '': True, 'aye': True, 'sure': True}
60
for answer, val in new_answers.items():
61
    dos_inp.BOOLEAN_STATES[answer] = val
62
turn_false_answers = [answer for answer in dos_inp.BOOLEAN_STATES
63
                      if dos_inp.BOOLEAN_STATES[answer] is False]
64

    
65
no_sect_err = "Section '%s' not found on input file"
66
no_opt_err = "Option '%s' not found on section '%s'"
67
num_error = "'%s' value must be a %s"
68

    
69

    
70
def str2lst(cmplx_str, func=int):  # TODO: enable deeper level of nested lists
71
    # TODO Treat all-enclosing parenthesis as a list instead of list of lists.
72
    """Converts a string of integers, and groups of them, to a list.
73

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

82
    @return list, list of integers (or floats), or list of integers (or floats)
83
    in the case they were grouped. First, the singlets are placed, and then the
84
    groups in input order.
85

86
    eg. '128,(135 138;141] 87 {45, 68}' -> [128, 87, [135, 138, 141], [45, 68]]
87
    """
88

    
89
    # Checks
90
    error_msg = "Function argument should be a str, sequence of " \
91
                "numbers separated by ',' ';' or ' '." \
92
                "\nThey can be grouped in parentheses-like enclosers: '()', " \
93
                "'[]' or {}. Nested groups are not allowed. \n" \
94
                "eg. 128,(135 138;141) 87 {45, 68}"
95
    cmplx_str = try_command(cmplx_str.replace, [(AttributeError, error_msg)],
96
                            ',', ' ')
97

    
98
    cmplx_str = cmplx_str.replace(';', ' ').replace('[', '(').replace(
99
        ']', ')').replace('{', '(').replace('}', ')')
100

    
101
    try_command(list, [(ValueError, error_msg)], map(func, cmplx_str.replace(
102
        ')', '').replace('(', '').split()))
103

    
104
    deepness = 0
105
    for el in cmplx_str.split():
106
        if '(' in el:
107
            deepness += 1
108
        if ')' in el:
109
            deepness += -1
110
        if deepness > 1 or deepness < 0:
111
            logger.error(error_msg)
112
            raise ValueError(error_msg)
113

    
114
    init_list = cmplx_str.split()
115
    start_group = []
116
    end_group = []
117
    for i, element in enumerate(init_list):
118
        if '(' in element:
119
            start_group.append(i)
120
            init_list[i] = element.replace('(', '')
121
        if ')' in element:
122
            end_group.append(i)
123
            init_list[i] = element.replace(')', '')
124

    
125
    init_list = list(map(func, init_list))
126

    
127
    new_list = []
128
    for start_el, end_el in zip(start_group, end_group):
129
        new_list.append(init_list[start_el:end_el + 1])
130

    
131
    for v in new_list:
132
        for el in v:
133
            init_list.remove(el)
134
    return init_list + new_list
135

    
136

    
137
def check_expect_val(value, expect_vals, err_msg=None):
138
    """Checks whether an option lies within its expected values.
139

140
    Keyword arguments:
141
    @param value: The variable to check if its value lies within the expected
142
    ones
143
    @param expect_vals: list, list of values allowed for the present option.
144
    @param err_msg: The error message to be prompted in both log and screen.
145
    @raise ValueError: if the value is not among the expected ones.
146
    @return True if the value is among the expected ones.
147
    """
148
    if err_msg is None:
149
        err_msg = f"'{value}' is not an adequate value.\n" \
150
                  f"Adequate values: {expect_vals}"
151
    if not any([exp_val == value for exp_val in expect_vals]):
152
        logger.error(err_msg)
153
        raise ValueError(err_msg)
154

    
155
    return True
156

    
157

    
158
def check_inp_file(inp_file, code):
159
    if code == 'cp2k':
160
        from pycp2k import CP2K
161
        cp2k = CP2K()
162
        try_command(cp2k.parse,
163
                    [(UnboundLocalError, "Invalid CP2K input file")], inp_file)
164

    
165

    
166
# Global
167

    
168
def get_run_type():
169
    isolated, screening, refinement = (False, False, False)
170
    run_type_vals = ['isolated', 'screening', 'refinement', 'adsorption',
171
                     'full']
172
    run_types = dos_inp.get('Global', 'run_type').split()
173
    for run_type in run_types:
174
        check_expect_val(run_type.lower(), run_type_vals)
175
        if 'isol' in run_type.lower():
176
            isolated = True
177
        if 'screen' in run_type.lower():
178
            screening = True
179
        if 'refine' in run_type.lower():
180
            refinement = True
181
        if 'adsor' in run_type.lower():
182
            screening, refinement = (True, True)
183
        if 'full' in run_type.lower():
184
            isolated, screening, refinement = (True, True, True)
185

    
186
    return isolated, screening, refinement
187

    
188

    
189
def get_code():
190
    code_vals = ['cp2k']
191
    check_expect_val(dos_inp.get('Global', 'code').lower(), code_vals)
192
    code = dos_inp.get('Global', 'code').lower()
193
    return code
194

    
195

    
196
def get_batch_q_sys():
197
    batch_q_sys_vals = ['sge', 'lsf', 'irene', 'local'] + turn_false_answers
198
    check_expect_val(dos_inp.get('Global', 'batch_q_sys').lower(),
199
                     batch_q_sys_vals)
200
    batch_q_sys = dos_inp.get('Global', 'batch_q_sys').lower()
201
    if batch_q_sys.lower() in turn_false_answers:
202
        return False
203
    else:
204
        return batch_q_sys
205

    
206

    
207
def get_subm_script():
208
    subm_script = dos_inp.get('Global', 'subm_script', fallback=False)
209
    if subm_script and not os.path.isfile(subm_script):
210
        logger.error(f'File {subm_script} not found.')
211
        raise FileNotFoundError(f'File {subm_script} not found')
212
    return subm_script
213

    
214

    
215
def get_project_name():
216
    project_name = dos_inp.get('Global', 'project_name', fallback='')
217
    return project_name
218

    
219

    
220
def get_relaunch_err():
221
    relaunch_err_vals = ['geo_not_conv']
222
    relaunch_err = dos_inp.get('Global', 'relaunch_err',
223
                               fallback="False")
224
    if relaunch_err.lower() in turn_false_answers:
225
        return False
226
    else:
227
        check_expect_val(relaunch_err.lower(), relaunch_err_vals)
228
    return relaunch_err
229

    
230

    
231
def get_max_jobs():
232
    import re
233
    err_msg = "'max_jobs' must be a list of, number plus 'p', 'q' or 'r', or " \
234
              "a combination of them without repeating letters.\n" \
235
              "eg: '2r 3p 4pr', '5q' or '3r 3p'"
236
    max_jobs_str = dos_inp.get('Global', 'max_jobs', fallback="inf").lower()
237
    str_vals = ["r", "p", "q", "rp", "rq", "pr", "qr"]
238
    max_jobs = {"r": np.inf, "p": np.inf, "rp": np.inf}
239
    if "inf" == max_jobs_str:
240
        return {"r": np.inf, "p": np.inf, "rp": np.inf}
241
    # Iterate over the number of requirements:
242
    for req in max_jobs_str.split():
243
        # Split numbers from letters into a list
244
        req_parts = re.findall(r'[a-z]+|\d+', req)
245
        if len(req_parts) != 2:
246
            logger.error(err_msg)
247
            raise ValueError(err_msg)
248
        if req_parts[0].isdecimal():
249
            req_parts[1] = req_parts[1].replace('q', 'p').replace('pr', 'rp')
250
            if req_parts[1] in str_vals and max_jobs[req_parts[1]] == np.inf:
251
                max_jobs[req_parts[1]] = int(req_parts[0])
252
        elif req_parts[1].isdecimal():
253
            req_parts[0] = req_parts[0].replace('q', 'p').replace('pr', 'rp')
254
            if req_parts[0] in str_vals and max_jobs[req_parts[0]] == np.inf:
255
                max_jobs[req_parts[0]] = int(req_parts[1])
256
        else:
257
            logger.error(err_msg)
258
            raise ValueError(err_msg)
259

    
260
    return max_jobs
261

    
262

    
263
def get_special_atoms():
264
    from ase.data import chemical_symbols
265

    
266
    spec_at_err = '\'special_atoms\' does not have an adequate format.\n' \
267
                  'Adequate format: (Fe1 Fe) (O1 O)'
268
    special_atoms = dos_inp.get('Global', 'special_atoms', fallback="False")
269
    if special_atoms.lower() in turn_false_answers:
270
        special_atoms = []
271
    else:
272
        # Converts the string into a list of tuples
273
        lst_tple = [tuple(pair.replace("(", "").split()) for pair in
274
                    special_atoms.split(")")[:-1]]
275
        if len(lst_tple) == 0:
276
            logger.error(spec_at_err)
277
            raise ValueError(spec_at_err)
278
        for i, tup in enumerate(lst_tple):
279
            if not isinstance(tup, tuple) or len(tup) != 2:
280
                logger.error(spec_at_err)
281
                raise ValueError(spec_at_err)
282
            if tup[1].capitalize() not in chemical_symbols:
283
                elem_err = "The second element of the couple should be an " \
284
                           "actual element of the periodic table"
285
                logger.error(elem_err)
286
                raise ValueError(elem_err)
287
            if tup[0].capitalize() in chemical_symbols:
288
                elem_err = "The first element of the couple is already an " \
289
                           "actual element of the periodic table, "
290
                logger.error(elem_err)
291
                raise ValueError(elem_err)
292
            for j, tup2 in enumerate(lst_tple):
293
                if j <= i:
294
                    continue
295
                if tup2[0] == tup[0]:
296
                    label_err = f'You have specified the label {tup[0]} to ' \
297
                                f'more than one special atom'
298
                    logger.error(label_err)
299
                    raise ValueError(label_err)
300
        special_atoms = lst_tple
301
    return special_atoms
302

    
303

    
304
# Isolated
305

    
306
def get_isol_inp_file():
307
    isol_inp_file = dos_inp.get('Isolated', 'isol_inp_file')
308
    if not os.path.isfile(isol_inp_file):
309
        logger.error(f'File {isol_inp_file} not found.')
310
        raise FileNotFoundError(f'File {isol_inp_file} not found')
311
    return isol_inp_file
312

    
313

    
314
def get_molec_file():
315
    molec_file = dos_inp.get('Isolated', 'molec_file')
316
    if not os.path.isfile(molec_file):
317
        logger.error(f'File {molec_file} not found.')
318
        raise FileNotFoundError(f'File {molec_file} not found')
319
    return molec_file
320

    
321

    
322
def get_num_conformers():
323
    err_msg = num_error % ('num_conformers', 'positive integer')
324
    num_conformers = try_command(dos_inp.getint, [(ValueError, err_msg)],
325
                                 'Isolated', 'num_conformers', fallback=100)
326
    if num_conformers < 1:
327
        logger.error(err_msg)
328
        raise ValueError(err_msg)
329
    return num_conformers
330

    
331

    
332
def get_pre_opt():
333
    pre_opt_vals = ['uff', 'mmff'] + turn_false_answers
334
    check_expect_val(dos_inp.get('Isolated', 'pre_opt').lower(), pre_opt_vals)
335
    pre_opt = dos_inp.get('Isolated', 'pre_opt').lower()
336
    if pre_opt in turn_false_answers:
337
        return False
338
    else:
339
        return pre_opt
340

    
341

    
342
# Screening
343

    
344
def get_screen_inp_file():
345
    screen_inp_file = dos_inp.get('Screening', 'screen_inp_file')
346
    if not os.path.isfile(screen_inp_file):
347
        logger.error(f'File {screen_inp_file} not found.')
348
        raise FileNotFoundError(f'File {screen_inp_file} not found')
349
    return screen_inp_file
350

    
351

    
352
def get_surf_file():
353
    surf_file = dos_inp.get('Screening', 'surf_file')
354
    if not os.path.isfile(surf_file):
355
        logger.error(f'File {surf_file} not found.')
356
        raise FileNotFoundError(f'File {surf_file} not found')
357
    return surf_file
358

    
359

    
360
def get_sites():
361
    err_msg = 'The value of sites should be a list of atom numbers ' \
362
              '(ie. positive integers) or groups of atom numbers ' \
363
              'grouped by parentheses-like enclosers. \n' \
364
              'eg. 128,(135 138;141) 87 {45, 68}'
365
    # Convert the string into a list of lists
366
    sites = try_command(str2lst,
367
                        [(ValueError, err_msg), (AttributeError, err_msg)],
368
                        dos_inp.get('Screening', 'sites'))
369
    # Check all elements of the list (of lists) are positive integers
370
    for site in sites:
371
        if type(site) is list:
372
            for atom in site:
373
                if atom < 0:
374
                    logger.error(err_msg)
375
                    raise ValueError(err_msg)
376
        elif type(site) is int:
377
            if site < 0:
378
                logger.error(err_msg)
379
                raise ValueError(err_msg)
380
        else:
381
            logger.error(err_msg)
382
            raise ValueError(err_msg)
383

    
384
    return sites
385

    
386

    
387
def get_surf_ctrs2():
388
    err_msg = 'The value of surf_ctrs2 should be a list of atom numbers ' \
389
              '(ie. positive integers) or groups of atom numbers ' \
390
              'grouped by parentheses-like enclosers. \n' \
391
              'eg. 128,(135 138;141) 87 {45, 68}'
392
    # Convert the string into a list of lists
393
    surf_ctrs2 = try_command(str2lst,
394
                             [(ValueError, err_msg), (AttributeError, err_msg)],
395
                             dos_inp.get('Screening', 'surf_ctrs2'))
396
    # Check all elements of the list (of lists) are positive integers
397
    for ctr in surf_ctrs2:
398
        if type(ctr) is list:
399
            for atom in ctr:
400
                if atom < 0:
401
                    logger.error(err_msg)
402
                    raise ValueError(err_msg)
403
        elif type(ctr) is int:
404
            if ctr < 0:
405
                logger.error(err_msg)
406
                raise ValueError(err_msg)
407
        else:
408
            logger.error(err_msg)
409
            raise ValueError(err_msg)
410

    
411
    return surf_ctrs2
412

    
413

    
414
def get_molec_ctrs():
415
    err_msg = 'The value of molec_ctrs should be a list of atom' \
416
              ' numbers (ie. positive integers) or groups of atom ' \
417
              'numbers enclosed by parentheses-like characters. \n' \
418
              'eg. 128,(135 138;141) 87 {45, 68}'
419
    # Convert the string into a list of lists
420
    molec_ctrs = try_command(str2lst,
421
                             [(ValueError, err_msg),
422
                              (AttributeError, err_msg)],
423
                             dos_inp.get('Screening', 'molec_ctrs'))
424
    # Check all elements of the list (of lists) are positive integers
425
    for ctr in molec_ctrs:
426
        if isinstance(ctr, list):
427
            for atom in ctr:
428
                if atom < 0:
429
                    logger.error(err_msg)
430
                    raise ValueError(err_msg)
431
        elif isinstance(ctr, int):
432
            if ctr < 0:
433
                logger.error(err_msg)
434
                raise ValueError(err_msg)
435
        else:
436
            logger.error(err_msg)
437
            raise ValueError(err_msg)
438

    
439
    return molec_ctrs
440

    
441

    
442
def get_molec_ctrs2():
443
    err_msg = 'The value of molec_ctrs2 should be a list of atom ' \
444
              'numbers (ie. positive integers) or groups of atom ' \
445
              'numbers enclosed by parentheses-like characters. \n' \
446
              'eg. 128,(135 138;141) 87 {45, 68}'
447
    # Convert the string into a list of lists
448
    molec_ctrs2 = try_command(str2lst, [(ValueError, err_msg),
449
                                        (AttributeError, err_msg)],
450
                              dos_inp.get('Screening', 'molec_ctrs2'))
451

    
452
    # Check all elements of the list (of lists) are positive integers
453
    for ctr in molec_ctrs2:
454
        if isinstance(ctr, list):
455
            for atom in ctr:
456
                if atom < 0:
457
                    logger.error(err_msg)
458
                    raise ValueError(err_msg)
459
        elif isinstance(ctr, int):
460
            if ctr < 0:
461
                logger.error(err_msg)
462
                raise ValueError(err_msg)
463
        else:
464
            logger.error(err_msg)
465
            raise ValueError(err_msg)
466

    
467
    return molec_ctrs2
468

    
469

    
470
def get_molec_ctrs3():
471
    err_msg = 'The value of molec_ctrs3 should be a list of atom ' \
472
              'numbers (ie. positive integers) or groups of atom ' \
473
              'numbers enclosed by parentheses-like characters. \n' \
474
              'eg. 128,(135 138;141) 87 {45, 68}'
475
    # Convert the string into a list of lists
476
    molec_ctrs3 = try_command(str2lst, [(ValueError, err_msg),
477
                                        (AttributeError, err_msg)],
478
                              dos_inp.get('Screening', 'molec_ctrs3'))
479

    
480
    # Check all elements of the list (of lists) are positive integers
481
    for ctr in molec_ctrs3:
482
        if isinstance(ctr, list):
483
            for atom in ctr:
484
                if atom < 0:
485
                    logger.error(err_msg)
486
                    raise ValueError(err_msg)
487
        elif isinstance(ctr, int):
488
            if ctr < 0:
489
                logger.error(err_msg)
490
                raise ValueError(err_msg)
491
        else:
492
            logger.error(err_msg)
493
            raise ValueError(err_msg)
494

    
495
    return molec_ctrs3
496

    
497

    
498
def get_max_helic_angle():
499
    err_msg = "'max_helic_angle' must be a positive number in degrees"
500
    max_helic_angle = try_command(dos_inp.getfloat, [(ValueError, err_msg)],
501
                                  'Screening', 'max_helic_angle',
502
                                  fallback=180.0)
503
    if max_helic_angle < 0:
504
        logger.error(err_msg)
505
        raise ValueError(err_msg)
506

    
507
    return max_helic_angle
508

    
509

    
510
def get_select_magns():
511
    select_magns_vals = ['energy', 'moi']
512
    select_magns_str = dos_inp.get('Screening', 'select_magns',
513
                                   fallback='energy')
514
    select_magns_str.replace(',', ' ').replace(';', ' ')
515
    select_magns = select_magns_str.split(' ')
516
    select_magns = [m.lower() for m in select_magns]
517
    for m in select_magns:
518
        check_expect_val(m, select_magns_vals)
519
    return select_magns
520

    
521

    
522
def get_confs_per_magn():
523
    err_msg = num_error % ('confs_per_magn', 'positive integer')
524
    confs_per_magn = try_command(dos_inp.getint, [(ValueError, err_msg)],
525
                                 'Screening', 'confs_per_magn', fallback=2)
526
    if confs_per_magn <= 0:
527
        logger.error(err_msg)
528
        raise ValueError(err_msg)
529
    return confs_per_magn
530

    
531

    
532
def get_pbc_cell():
533
    err = "'pbc_cell' must be either 3 vectors of size 3 or False"
534
    pbc_cell_str = dos_inp.get('Screening', 'pbc_cell', fallback="False")
535
    if pbc_cell_str.lower() in turn_false_answers:
536
        return False
537
    else:
538
        pbc_cell = np.array(try_command(str2lst, [(ValueError, err)],
539
                                        pbc_cell_str, float))
540
        if pbc_cell.shape != (3, 3):
541
            logger.error(err)
542
            raise ValueError(err)
543
        return pbc_cell
544

    
545

    
546
def get_surf_norm_vect():
547
    err = "'surf_norm_vect' must be a 3 component vector, 'x', 'y' or 'z', " \
548
          "'auto' or 'asann'."
549
    cart_axes = {'x': [1.0, 0.0, 0.0], '-x': [-1.0, 0.0, 0.0],
550
                 'y': [0.0, 1.0, 0.0], '-y': [0.0, -1.0, 0.0],
551
                 'z': [0.0, 0.0, 1.0], '-z': [0.0, 0.0, -1.0]}
552
    surf_norm_vect_str = dos_inp.get('Screening', 'surf_norm_vect',
553
                                     fallback="auto").lower()
554
    if surf_norm_vect_str == "asann" or surf_norm_vect_str == "auto":
555
        return 'auto'
556
    if surf_norm_vect_str in cart_axes:
557
        return np.array(cart_axes[surf_norm_vect_str])
558
    surf_norm_vect = try_command(str2lst, [(ValueError, err)],
559
                                 surf_norm_vect_str, float)
560
    if len(surf_norm_vect) != 3:
561
        logger.error(err)
562
        raise ValueError(err)
563

    
564
    return np.array(surf_norm_vect) / np.linalg.norm(surf_norm_vect)
565

    
566

    
567
def get_set_angles():
568
    set_vals = ['euler', 'internal']
569
    check_expect_val(dos_inp.get('Screening', 'set_angles').lower(), set_vals)
570
    set_angles = dos_inp.get('Screening', 'set_angles',
571
                             fallback='euler').lower()
572
    return set_angles
573

    
574

    
575
def get_pts_per_angle():
576
    err_msg = num_error % ('sample_points_per_angle', 'positive integer')
577
    pts_per_angle = try_command(dos_inp.getint,
578
                                [(ValueError, err_msg)],
579
                                'Screening', 'sample_points_per_angle',
580
                                fallback=3)
581
    if pts_per_angle <= 0:
582
        logger.error(err_msg)
583
        raise ValueError(err_msg)
584
    return pts_per_angle
585

    
586

    
587
def get_max_structures():
588
    err_msg = num_error % ('max_structures', 'positive integer')
589
    max_structs = dos_inp.get('Screening', 'max_structures', fallback="False")
590
    if max_structs.lower() in turn_false_answers:
591
        return np.inf
592
    if try_command(int, [(ValueError, err_msg)], max_structs) <= 0:
593
        logger.error(err_msg)
594
        raise ValueError(err_msg)
595
    return int(max_structs)
596

    
597

    
598
def get_coll_thrsld():
599
    err_msg = num_error % ('collision_threshold',
600
                           'positive number')
601
    coll_thrsld_str = dos_inp.get('Screening', 'collision_threshold',
602
                                  fallback="1.3")
603
    if coll_thrsld_str.lower() in turn_false_answers:
604
        return False
605
    coll_thrsld = try_command(float, [(ValueError, err_msg)], coll_thrsld_str)
606

    
607
    if coll_thrsld <= 0:
608
        logger.error(err_msg)
609
        raise ValueError(err_msg)
610

    
611
    return coll_thrsld
612

    
613

    
614
def get_min_coll_height(norm_vect):
615
    err_msg = num_error % ('min_coll_height', 'decimal number')
616
    min_coll_height = dos_inp.get('Screening', 'min_coll_height',
617
                                  fallback="False")
618
    if min_coll_height.lower() in turn_false_answers:
619
        return False
620
    min_coll_height = try_command(float, [(ValueError, err_msg)],
621
                                  min_coll_height)
622
    cart_axes = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0],
623
                 [-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]]
624
    err_msg = "'min_coll_height' option is only implemented for " \
625
              "'surf_norm_vect' to be one of the x, y or z axes. "
626
    if not isinstance(norm_vect, str) or norm_vect != 'auto':
627
        check_expect_val(norm_vect.tolist(), cart_axes, err_msg)
628
    return min_coll_height
629

    
630

    
631
def get_H_donor(spec_atoms):
632
    from ase.data import chemical_symbols
633
    err_msg = "The value of 'h_donor' must be either False, a chemical symbol " \
634
              "or an atom index"
635
    h_donor_str = dos_inp.get('Screening', 'h_donor', fallback="False")
636
    h_donor = []
637
    if h_donor_str.lower() in turn_false_answers:
638
        return False
639
    err = False
640
    for el in h_donor_str.split():
641
        try:
642
            h_donor.append(int(el))
643
        except ValueError:
644
            if el not in chemical_symbols + [nw_sym for pairs in spec_atoms
645
                                             for nw_sym in pairs]:
646
                err = True
647
            else:
648
                h_donor.append(el)
649
        finally:
650
            if err:
651
                logger.error(err_msg)
652
                ValueError(err_msg)
653
    return h_donor
654

    
655

    
656
def get_H_acceptor(spec_atoms):
657
    from ase.data import chemical_symbols
658
    err_msg = "The value of 'h_acceptor' must be either 'all', a chemical " \
659
              "symbol or an atom index"
660
    h_acceptor_str = dos_inp.get('Screening', 'h_acceptor', fallback="all")
661
    if h_acceptor_str.lower() == "all":
662
        return "all"
663
    h_acceptor = []
664
    err = False
665
    for el in h_acceptor_str.split():
666
        try:
667
            h_acceptor.append(int(el))
668
        except ValueError:
669
            if el not in chemical_symbols + [nw_sym for pairs in spec_atoms
670
                                             for nw_sym in pairs]:
671
                err = True
672
            else:
673
                h_acceptor.append(el)
674
        finally:
675
            if err:
676
                logger.error(err_msg)
677
                ValueError(err_msg)
678
    return h_acceptor
679

    
680

    
681
def get_use_molec_file():
682
    use_molec_file = dos_inp.get('Screening', 'use_molec_file',
683
                                 fallback='False')
684
    if use_molec_file.lower() in turn_false_answers:
685
        return False
686
    if not os.path.isfile(use_molec_file):
687
        logger.error(f'File {use_molec_file} not found.')
688
        raise FileNotFoundError(f'File {use_molec_file} not found')
689

    
690
    return use_molec_file
691

    
692

    
693
# Refinement
694

    
695
def get_refine_inp_file():  # TODO if not specified try isol_inp_file.
696
    refine_inp_file = dos_inp.get('Refinement', 'refine_inp_file')
697
    if not os.path.isfile(refine_inp_file):
698
        logger.error(f'File {refine_inp_file} not found.')
699
        raise FileNotFoundError(f'File {refine_inp_file} not found')
700

    
701
    return refine_inp_file
702

    
703

    
704
def get_energy_cutoff():
705
    err_msg = num_error % ('energy_cutoff', 'positive decimal number')
706
    energy_cutoff = try_command(dos_inp.getfloat,
707
                                [(ValueError, err_msg)],
708
                                'Refinement', 'energy_cutoff', fallback=0.5)
709
    if energy_cutoff < 0:
710
        logger.error(err_msg)
711
        raise ValueError(err_msg)
712
    return energy_cutoff
713

    
714

    
715
# Read input parameters
716

    
717
def read_input(in_file):
718
    err = False
719
    try:
720
        dos_inp.read(in_file)
721
    except MissingSectionHeaderError as e:
722
        logger.error('There are options in the input file without a Section '
723
                     'header.')
724
        err = e
725
    except DuplicateOptionError as e:
726
        logger.error('There is an option in the input file that has been '
727
                     'specified more than once.')
728
        err = e
729
    except Exception as e:
730
        err = e
731
    else:
732
        err = False
733
    finally:
734
        if isinstance(err, BaseException):
735
            raise err
736

    
737
    inp_vars = {}
738

    
739
    # Global
740
    if not dos_inp.has_section('Global'):
741
        logger.error(no_sect_err % 'Global')
742
        raise NoSectionError('Global')
743

    
744
    # Mandatory options
745
    # Checks whether the mandatory options 'run_type', 'code', etc. are present.
746
    glob_mand_opts = ['run_type', 'batch_q_sys']
747
    for opt in glob_mand_opts:
748
        if not dos_inp.has_option('Global', opt):
749
            logger.error(no_opt_err % (opt, 'Global'))
750
            raise NoOptionError(opt, 'Global')
751

    
752
    # Gets which sections are to be carried out
753
    isolated, screening, refinement = get_run_type()
754
    inp_vars['isolated'] = isolated
755
    inp_vars['screening'] = screening
756
    inp_vars['refinement'] = refinement
757
    inp_vars['batch_q_sys'] = get_batch_q_sys()
758

    
759
    # Dependent options:
760
    inp_vars['code'] = get_code()
761
    if inp_vars['batch_q_sys']:
762
        inp_vars['max_jobs'] = get_max_jobs()
763
        if inp_vars['batch_q_sys'] != 'local':
764
            if not dos_inp.has_option('Global', 'subm_script'):
765
                logger.error(no_opt_err % ('subm_script', 'Global'))
766
                raise NoOptionError('subm_script', 'Global')
767
            inp_vars['subm_script'] = get_subm_script()
768

    
769
    # Facultative options (Default/Fallback value present)
770
    inp_vars['project_name'] = get_project_name()
771
    # inp_vars['relaunch_err'] = get_relaunch_err()
772
    inp_vars['special_atoms'] = get_special_atoms()
773

    
774
    # Isolated
775
    if isolated:
776
        if not dos_inp.has_section('Isolated'):
777
            logger.error(no_sect_err % 'Isolated')
778
            raise NoSectionError('Isolated')
779
        # Mandatory options
780
        # Checks whether the mandatory options are present.
781
        iso_mand_opts = ['isol_inp_file', 'molec_file']
782
        for opt in iso_mand_opts:
783
            if not dos_inp.has_option('Isolated', opt):
784
                logger.error(no_opt_err % (opt, 'Isolated'))
785
                raise NoOptionError(opt, 'Isolated')
786
        inp_vars['isol_inp_file'] = get_isol_inp_file()
787
        if 'code ' in inp_vars:
788
            check_inp_file(inp_vars['isol_inp_file'], inp_vars['code'])
789
        inp_vars['molec_file'] = get_molec_file()
790

    
791
        # Facultative options (Default/Fallback value present)
792
        inp_vars['num_conformers'] = get_num_conformers()
793
        # inp_vars['num_prom_cand'] = get_num_prom_cand()
794
        # inp_vars['iso_rmsd'] = get_iso_rmsd()
795
        inp_vars['pre_opt'] = get_pre_opt()
796

    
797
    # Screening
798
    if screening:
799
        if not dos_inp.has_section('Screening'):
800
            logger.error(no_sect_err % 'Screening')
801
            raise NoSectionError('Screening')
802
        # Mandatory options:
803
        # Checks whether the mandatory options are present.
804
        screen_mand_opts = ['screen_inp_file', 'surf_file', 'sites',
805
                            'molec_ctrs']
806
        for opt in screen_mand_opts:
807
            if not dos_inp.has_option('Screening', opt):
808
                logger.error(no_opt_err % (opt, 'Screening'))
809
                raise NoOptionError(opt, 'Screening')
810
        inp_vars['screen_inp_file'] = get_screen_inp_file()
811
        inp_vars['surf_file'] = get_surf_file()
812
        inp_vars['sites'] = get_sites()
813
        inp_vars['molec_ctrs'] = get_molec_ctrs()
814

    
815
        # Facultative options (Default value present)
816
        inp_vars['select_magns'] = get_select_magns()
817
        inp_vars['confs_per_magn'] = get_confs_per_magn()
818
        inp_vars['surf_norm_vect'] = get_surf_norm_vect()
819
        inp_vars['set_angles'] = get_set_angles()
820
        inp_vars['sample_points_per_angle'] = get_pts_per_angle()
821
        inp_vars['pbc_cell'] = get_pbc_cell()
822
        inp_vars['collision_threshold'] = get_coll_thrsld()
823
        inp_vars['min_coll_height'] = get_min_coll_height(
824
            inp_vars['surf_norm_vect'])
825
        if inp_vars['min_coll_height'] is False \
826
                and inp_vars['collision_threshold'] is False:
827
            logger.warning("Collisions are deactivated: Overlapping of "
828
                           "adsorbate and surface is possible")
829
        inp_vars['h_donor'] = get_H_donor(inp_vars['special_atoms'])
830
        inp_vars['max_structures'] = get_max_structures()
831
        inp_vars['use_molec_file'] = get_use_molec_file()
832

    
833
        # Options depending on the value of others
834
        if inp_vars['set_angles'] == "internal":
835
            internal_opts = ['molec_ctrs2', 'molec_ctrs3', 'surf_ctrs2',
836
                             'max_helic_angle']
837
            for opt in internal_opts:
838
                if not dos_inp.has_option('Screening', opt):
839
                    logger.error(no_opt_err % (opt, 'Screening'))
840
                    raise NoOptionError(opt, 'Screening')
841
            inp_vars['max_helic_angle'] = get_max_helic_angle()
842
            inp_vars['molec_ctrs2'] = get_molec_ctrs2()
843
            inp_vars['molec_ctrs3'] = get_molec_ctrs3()
844
            inp_vars['surf_ctrs2'] = get_surf_ctrs2()
845
            if len(inp_vars["molec_ctrs2"]) != len(inp_vars['molec_ctrs']) \
846
                    or len(inp_vars["molec_ctrs3"]) != \
847
                    len(inp_vars['molec_ctrs']) \
848
                    or len(inp_vars['surf_ctrs2']) != len(inp_vars['sites']):
849
                err = "'molec_ctrs' 'molec_ctrs2' and 'molec_ctrs2' must have " \
850
                      "the same number of indices "
851
                logger.error(err)
852
                raise ValueError(err)
853

    
854
        if inp_vars['h_donor'] is False:
855
            inp_vars['h_acceptor'] = False
856
        else:
857
            inp_vars['h_acceptor'] = get_H_acceptor(inp_vars['special_atoms'])
858

    
859
    # Refinement
860
    if refinement:
861
        if not dos_inp.has_section('Refinement'):
862
            logger.error(no_sect_err % 'Refinement')
863
            raise NoSectionError('Refinement')
864
        # Mandatory options
865
        # Checks whether the mandatory options are present.
866
        ref_mand_opts = ['refine_inp_file']
867
        for opt in ref_mand_opts:
868
            if not dos_inp.has_option('Refinement', opt):
869
                logger.error(no_opt_err % (opt, 'Refinement'))
870
                raise NoOptionError(opt, 'Refinement')
871
        inp_vars['refine_inp_file'] = get_refine_inp_file()
872

    
873
        # Facultative options (Default value present)
874
        inp_vars['energy_cutoff'] = get_energy_cutoff()
875
        # end energy_cutoff
876

    
877
    return_vars_str = "\n\t".join([str(key) + ": " + str(value)
878
                                   for key, value in inp_vars.items()])
879
    logger.info(
880
        f'Correctly read {in_file} parameters: \n\n\t{return_vars_str}\n')
881

    
882
    return inp_vars
883

    
884

    
885
if __name__ == "__main__":
886
    import sys
887

    
888
    print(read_input(sys.argv[1]))