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

dockonsurf / modules / dos_input.py @ 8279a51d

Historique | Voir | Annoter | Télécharger (32,7 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', 'chemcat']
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
# Refinement
682

    
683
def get_refine_inp_file():  # TODO if not specified try isol_inp_file.
684
    refine_inp_file = dos_inp.get('Refinement', 'refine_inp_file')
685
    if not os.path.isfile(refine_inp_file):
686
        logger.error(f'File {refine_inp_file} not found.')
687
        raise FileNotFoundError(f'File {refine_inp_file} not found')
688

    
689
    return refine_inp_file
690

    
691

    
692
def get_energy_cutoff():
693
    err_msg = num_error % ('energy_cutoff', 'positive decimal number')
694
    energy_cutoff = try_command(dos_inp.getfloat,
695
                                [(ValueError, err_msg)],
696
                                'Refinement', 'energy_cutoff', fallback=0.5)
697
    if energy_cutoff < 0:
698
        logger.error(err_msg)
699
        raise ValueError(err_msg)
700
    return energy_cutoff
701

    
702

    
703
# Read input parameters
704

    
705
def read_input(in_file):
706
    err = False
707
    try:
708
        dos_inp.read(in_file)
709
    except MissingSectionHeaderError as e:
710
        logger.error('There are options in the input file without a Section '
711
                     'header.')
712
        err = e
713
    except DuplicateOptionError as e:
714
        logger.error('There is an option in the input file that has been '
715
                     'specified more than once.')
716
        err = e
717
    except Exception as e:
718
        err = e
719
    else:
720
        err = False
721
    finally:
722
        if isinstance(err, BaseException):
723
            raise err
724

    
725
    inp_vars = {}
726

    
727
    # Global
728
    if not dos_inp.has_section('Global'):
729
        logger.error(no_sect_err % 'Global')
730
        raise NoSectionError('Global')
731

    
732
    # Mandatory options
733
    # Checks whether the mandatory options 'run_type', 'code', etc. are present.
734
    glob_mand_opts = ['run_type', 'batch_q_sys']
735
    for opt in glob_mand_opts:
736
        if not dos_inp.has_option('Global', opt):
737
            logger.error(no_opt_err % (opt, 'Global'))
738
            raise NoOptionError(opt, 'Global')
739

    
740
    # Gets which sections are to be carried out
741
    isolated, screening, refinement = get_run_type()
742
    inp_vars['isolated'] = isolated
743
    inp_vars['screening'] = screening
744
    inp_vars['refinement'] = refinement
745
    inp_vars['batch_q_sys'] = get_batch_q_sys()
746

    
747
    # Dependent options:
748
    inp_vars['code'] = get_code()
749
    if inp_vars['batch_q_sys']:
750
        inp_vars['max_jobs'] = get_max_jobs()
751
        if inp_vars['batch_q_sys'] != 'local':
752
            if not dos_inp.has_option('Global', 'subm_script'):
753
                logger.error(no_opt_err % ('subm_script', 'Global'))
754
                raise NoOptionError('subm_script', 'Global')
755
            inp_vars['subm_script'] = get_subm_script()
756

    
757
    # Facultative options (Default/Fallback value present)
758
    inp_vars['project_name'] = get_project_name()
759
    # inp_vars['relaunch_err'] = get_relaunch_err()
760
    inp_vars['special_atoms'] = get_special_atoms()
761

    
762
    # Isolated
763
    if isolated:
764
        if not dos_inp.has_section('Isolated'):
765
            logger.error(no_sect_err % 'Isolated')
766
            raise NoSectionError('Isolated')
767
        # Mandatory options
768
        # Checks whether the mandatory options are present.
769
        iso_mand_opts = ['isol_inp_file', 'molec_file']
770
        for opt in iso_mand_opts:
771
            if not dos_inp.has_option('Isolated', opt):
772
                logger.error(no_opt_err % (opt, 'Isolated'))
773
                raise NoOptionError(opt, 'Isolated')
774
        inp_vars['isol_inp_file'] = get_isol_inp_file()
775
        if 'code ' in inp_vars:
776
            check_inp_file(inp_vars['isol_inp_file'], inp_vars['code'])
777
        inp_vars['molec_file'] = get_molec_file()
778

    
779
        # Facultative options (Default/Fallback value present)
780
        inp_vars['num_conformers'] = get_num_conformers()
781
        # inp_vars['num_prom_cand'] = get_num_prom_cand()
782
        # inp_vars['iso_rmsd'] = get_iso_rmsd()
783
        inp_vars['pre_opt'] = get_pre_opt()
784

    
785
    # Screening
786
    if screening:
787
        if not dos_inp.has_section('Screening'):
788
            logger.error(no_sect_err % 'Screening')
789
            raise NoSectionError('Screening')
790
        # Mandatory options:
791
        # Checks whether the mandatory options are present.
792
        screen_mand_opts = ['screen_inp_file', 'surf_file', 'sites',
793
                            'molec_ctrs']
794
        for opt in screen_mand_opts:
795
            if not dos_inp.has_option('Screening', opt):
796
                logger.error(no_opt_err % (opt, 'Screening'))
797
                raise NoOptionError(opt, 'Screening')
798
        inp_vars['screen_inp_file'] = get_screen_inp_file()
799
        inp_vars['surf_file'] = get_surf_file()
800
        inp_vars['sites'] = get_sites()
801
        inp_vars['molec_ctrs'] = get_molec_ctrs()
802

    
803
        # Facultative options (Default value present)
804
        inp_vars['select_magns'] = get_select_magns()
805
        inp_vars['confs_per_magn'] = get_confs_per_magn()
806
        inp_vars['surf_norm_vect'] = get_surf_norm_vect()
807
        inp_vars['set_angles'] = get_set_angles()
808
        inp_vars['sample_points_per_angle'] = get_pts_per_angle()
809
        inp_vars['pbc_cell'] = get_pbc_cell()
810
        inp_vars['collision_threshold'] = get_coll_thrsld()
811
        inp_vars['min_coll_height'] = get_min_coll_height(
812
            inp_vars['surf_norm_vect'])
813
        inp_vars['h_donor'] = get_H_donor(inp_vars['special_atoms'])
814
        inp_vars['max_structures'] = get_max_structures()
815
        if inp_vars['min_coll_height'] is False \
816
                and inp_vars['collision_threshold'] is False:
817
            logger.warning("Collisions are deactivated: Overlapping of "
818
                           "adsorbate and surface is possible")
819

    
820
        # Options depending on the value of others
821
        if inp_vars['set_angles'] == "chemcat":
822
            chemcat_opts = ['molec_ctrs2', 'molec_ctrs3', 'surf_ctrs2',
823
                            'max_helic_angle']
824
            for opt in chemcat_opts:
825
                if not dos_inp.has_option('Screening', opt):
826
                    logger.error(no_opt_err % (opt, 'Screening'))
827
                    raise NoOptionError(opt, 'Screening')
828
            inp_vars['max_helic_angle'] = get_max_helic_angle()
829
            inp_vars['molec_ctrs2'] = get_molec_ctrs2()
830
            inp_vars['molec_ctrs3'] = get_molec_ctrs3()
831
            inp_vars['surf_ctrs2'] = get_surf_ctrs2()
832
            if len(inp_vars["molec_ctrs2"]) != len(inp_vars['molec_ctrs']) \
833
                    or len(inp_vars["molec_ctrs3"]) != \
834
                    len(inp_vars['molec_ctrs']) \
835
                    or len(inp_vars['surf_ctrs2']) != len(inp_vars['sites']):
836
                err = "'molec_ctrs' 'molec_ctrs2' and 'molec_ctrs2' must have " \
837
                      "the same number of indices "
838
                logger.error(err)
839
                raise ValueError(err)
840

    
841
        if inp_vars['h_donor'] is False:
842
            inp_vars['h_acceptor'] = False
843
        else:
844
            inp_vars['h_acceptor'] = get_H_acceptor(inp_vars['special_atoms'])
845

    
846
    # Refinement
847
    if refinement:
848
        if not dos_inp.has_section('Refinement'):
849
            logger.error(no_sect_err % 'Refinement')
850
            raise NoSectionError('Refinement')
851
        # Mandatory options
852
        # Checks whether the mandatory options are present.
853
        ref_mand_opts = ['refine_inp_file']
854
        for opt in ref_mand_opts:
855
            if not dos_inp.has_option('Refinement', opt):
856
                logger.error(no_opt_err % (opt, 'Refinement'))
857
                raise NoOptionError(opt, 'Refinement')
858
        inp_vars['refine_inp_file'] = get_refine_inp_file()
859

    
860
        # Facultative options (Default value present)
861
        inp_vars['energy_cutoff'] = get_energy_cutoff()
862
        # end energy_cutoff
863

    
864
    return_vars_str = "\n\t".join([str(key) + ": " + str(value)
865
                                   for key, value in inp_vars.items()])
866
    logger.info(
867
        f'Correctly read {in_file} parameters: \n\n\t{return_vars_str}\n')
868

    
869
    return inp_vars
870

    
871

    
872
if __name__ == "__main__":
873
    import sys
874

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