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

dockonsurf / modules / dos_input.py @ 5261a07f

Historique | Voir | Annoter | Télécharger (30,84 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):
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
    @raise ValueError: if the value is not among the expected ones.
145
    @return True if the value is among the expected ones.
146
    """
147
    adeq_val_err = "'%s' is not an adequate value.\n" \
148
                   "Adequate values: %s"
149
    if not any([exp_val == value for exp_val in expect_vals]):
150
        logger.error(adeq_val_err % (value, expect_vals))
151
        raise ValueError(adeq_val_err % (value, expect_vals))
152

    
153
    return True
154

    
155

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

    
163

    
164
# Global
165

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

    
184
    return isolated, screening, refinement
185

    
186

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

    
193

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

    
204

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

    
212

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

    
217

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

    
228

    
229
def get_max_jobs():
230
    err_msg = num_error % ('max_jobs', 'positive integer')
231
    max_jobs = try_command(dos_inp.getint, [(ValueError, err_msg)],
232
                           'Global', 'max_jobs', fallback=3)
233

    
234
    if max_jobs < 1:
235
        logger.error(num_error % ('max_jobs', 'positive integer'))
236
        raise ValueError(num_error % ('max_jobs', 'positive integer'))
237
    return max_jobs
238

    
239

    
240
def get_type_max():
241
    running = ['r', 'run', 'running']
242
    pending = ['p', 'pending', 'q', 'qw', 'queued']
243
    type_max = dos_inp.get('Global', 'type_max', fallback="False")
244
    if type_max.lower() in turn_false_answers:
245
        return False
246
    elif type_max.lower() in running:
247
        return 'r'
248
    elif type_max.lower() in pending:
249
        return 'p'
250
    else:
251
        err = f"Value for 'type_max' is not accepted. Accepted values: " \
252
              f"{running + pending + turn_false_answers}."
253
        logger.error(err)
254
        raise ValueError(err)
255

    
256

    
257
def get_special_atoms():
258
    from ase.data import chemical_symbols
259

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

    
297

    
298
# Isolated
299

    
300
def get_isol_inp_file():
301
    isol_inp_file = dos_inp.get('Isolated', 'isol_inp_file')
302
    if not os.path.isfile(isol_inp_file):
303
        logger.error(f'File {isol_inp_file} not found.')
304
        raise FileNotFoundError(f'File {isol_inp_file} not found')
305
    return isol_inp_file
306

    
307

    
308
def get_molec_file():
309
    molec_file = dos_inp.get('Isolated', 'molec_file')
310
    if not os.path.isfile(molec_file):
311
        logger.error(f'File {molec_file} not found.')
312
        raise FileNotFoundError(f'File {molec_file} not found')
313
    return molec_file
314

    
315

    
316
def get_num_conformers():
317
    err_msg = num_error % ('num_conformers', 'positive integer')
318
    num_conformers = try_command(dos_inp.getint, [(ValueError, err_msg)],
319
                                 'Isolated', 'num_conformers', fallback=100)
320
    if num_conformers < 1:
321
        logger.error(err_msg)
322
        raise ValueError(err_msg)
323
    return num_conformers
324

    
325

    
326
def get_pre_opt():
327
    pre_opt_vals = ['uff', 'mmff'] + turn_false_answers
328
    check_expect_val(dos_inp.get('Isolated', 'pre_opt').lower(), pre_opt_vals)
329
    pre_opt = dos_inp.get('Isolated', 'pre_opt').lower()
330
    if pre_opt in turn_false_answers:
331
        return False
332
    else:
333
        return pre_opt
334

    
335

    
336
# Screening
337

    
338
def get_screen_inp_file():
339
    screen_inp_file = dos_inp.get('Screening', 'screen_inp_file')
340
    if not os.path.isfile(screen_inp_file):
341
        logger.error(f'File {screen_inp_file} not found.')
342
        raise FileNotFoundError(f'File {screen_inp_file} not found')
343
    return screen_inp_file
344

    
345

    
346
def get_surf_file():
347
    surf_file = dos_inp.get('Screening', 'surf_file')
348
    if not os.path.isfile(surf_file):
349
        logger.error(f'File {surf_file} not found.')
350
        raise FileNotFoundError(f'File {surf_file} not found')
351
    return surf_file
352

    
353

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

    
378
    return sites
379

    
380

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

    
405
    return surf_ctrs2
406

    
407

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

    
433
    return molec_ctrs
434

    
435

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

    
446
    # Check all elements of the list (of lists) are positive integers
447
    for ctr in molec_ctrs2:
448
        if isinstance(ctr, list):
449
            for atom in ctr:
450
                if atom < 0:
451
                    logger.error(err_msg)
452
                    raise ValueError(err_msg)
453
        elif isinstance(ctr, int):
454
            if ctr < 0:
455
                logger.error(err_msg)
456
                raise ValueError(err_msg)
457
        else:
458
            logger.error(err_msg)
459
            raise ValueError(err_msg)
460

    
461
    return molec_ctrs2
462

    
463

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

    
474
    # Check all elements of the list (of lists) are positive integers
475
    for ctr in molec_ctrs3:
476
        if isinstance(ctr, list):
477
            for atom in ctr:
478
                if atom < 0:
479
                    logger.error(err_msg)
480
                    raise ValueError(err_msg)
481
        elif isinstance(ctr, int):
482
            if ctr < 0:
483
                logger.error(err_msg)
484
                raise ValueError(err_msg)
485
        else:
486
            logger.error(err_msg)
487
            raise ValueError(err_msg)
488

    
489
    return molec_ctrs3
490

    
491

    
492
def get_max_helic_angle():
493
    err_msg = "'max_helic_angle' must be a positive number in degrees"
494
    max_helic_angle = try_command(dos_inp.getfloat, [(ValueError, err_msg)],
495
                                  'Screening', 'max_helic_angle',
496
                                  fallback=180.0)
497
    if max_helic_angle < 0:
498
        logger.error(err_msg)
499
        raise ValueError(err_msg)
500

    
501
    return max_helic_angle
502

    
503

    
504
def get_select_magns():
505
    select_magns_vals = ['energy', 'moi']
506
    select_magns_str = dos_inp.get('Screening', 'select_magns',
507
                                   fallback='energy')
508
    select_magns_str.replace(',', ' ').replace(';', ' ')
509
    select_magns = select_magns_str.split(' ')
510
    select_magns = [m.lower() for m in select_magns]
511
    for m in select_magns:
512
        check_expect_val(m, select_magns_vals)
513
    return select_magns
514

    
515

    
516
def get_confs_per_magn():
517
    err_msg = num_error % ('confs_per_magn', 'positive integer')
518
    confs_per_magn = try_command(dos_inp.getint, [(ValueError, err_msg)],
519
                                 'Screening', 'confs_per_magn', fallback=2)
520
    if confs_per_magn <= 0:
521
        logger.error(err_msg)
522
        raise ValueError(err_msg)
523
    return confs_per_magn
524

    
525

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

    
539

    
540
def get_surf_norm_vect():
541
    err = "'surf_norm_vect' must be either a 3 component vector or 'x', 'y' " \
542
          "or 'z'"
543
    cart_axes = {'x': [1.0, 0.0, 0.0], '-x': [-1.0, 0.0, 0.0],
544
                 'y': [0.0, 1.0, 0.0], '-y': [0.0, -1.0, 0.0],
545
                 'z': [0.0, 0.0, 1.0], '-z': [0.0, 0.0, -1.0]}
546
    surf_norm_vect_str = dos_inp.get('Screening', 'surf_norm_vect',
547
                                     fallback="z")
548

    
549
    if surf_norm_vect_str in cart_axes:
550
        surf_norm_vect = cart_axes[surf_norm_vect_str]
551
    else:
552
        surf_norm_vect = try_command(str2lst, [(ValueError, err)],
553
                                     surf_norm_vect_str, float)
554
        if len(surf_norm_vect) != 3:
555
            logger.error(err)
556
            raise ValueError(err)
557

    
558
    return np.array(surf_norm_vect)
559

    
560

    
561
def get_set_angles():
562
    set_vals = ['euler', 'chemcat']
563
    check_expect_val(dos_inp.get('Screening', 'set_angles').lower(), set_vals)
564
    set_angles = dos_inp.get('Screening', 'set_angles',
565
                             fallback='euler').lower()
566
    return set_angles
567

    
568

    
569
def get_pts_per_angle():
570
    err_msg = num_error % ('sample_points_per_angle', 'positive integer')
571
    pts_per_angle = try_command(dos_inp.getint,
572
                                [(ValueError, err_msg)],
573
                                'Screening', 'sample_points_per_angle',
574
                                fallback=3)
575
    if pts_per_angle <= 0:
576
        logger.error(err_msg)
577
        raise ValueError(err_msg)
578
    return pts_per_angle
579

    
580

    
581
def get_max_structures():
582
    err_msg = num_error % ('max_structures', 'positive integer')
583
    max_structures = try_command(dos_inp.getint, [(ValueError, err_msg)],
584
                                 'Screening', 'max_structures', fallback=np.inf)
585
    if max_structures <= 0:
586
        logger.error(err_msg)
587
        raise ValueError(err_msg)
588
    return max_structures
589

    
590

    
591
def get_coll_thrsld():
592
    err_msg = num_error % ('collision_threshold',
593
                           'positive decimal number')
594

    
595
    coll_thrsld = try_command(dos_inp.getfloat,
596
                              [(ValueError, err_msg)],
597
                              'Screening', 'collision_threshold', fallback=1.2)
598
    if coll_thrsld <= 0:
599
        logger.error(err_msg)
600
        raise ValueError(err_msg)
601

    
602
    return coll_thrsld
603

    
604

    
605
def get_screen_rmsd():
606
    err_msg = num_error % ('screen_rmsd', 'positive decimal number')
607
    screen_rmsd = try_command(dos_inp.getfloat,
608
                              [(ValueError, err_msg)],
609
                              'Screening', 'screen_rmsd', fallback=0.05)
610
    if screen_rmsd <= 0:
611
        logger.error(err_msg)
612
        raise ValueError(err_msg)
613

    
614
    return screen_rmsd
615

    
616

    
617
def get_min_coll_height():
618
    err_msg = num_error % ('min_coll_height', 'decimal number')
619
    min_coll_height = dos_inp.get('Screening', 'min_coll_height',
620
                                  fallback="False")
621
    if min_coll_height.lower() in turn_false_answers:
622
        min_coll_height = False
623
    else:
624
        min_coll_height = try_command(float, [(ValueError, err_msg)],
625
                                      min_coll_height)
626

    
627
    return min_coll_height
628

    
629

    
630
def get_disso_atoms():
631
    disso_atoms_str = dos_inp.get('Screening', 'disso_atoms', fallback="False")
632
    disso_atoms = []
633
    if disso_atoms_str.lower() in turn_false_answers:
634
        pass
635
    else:
636
        for el in disso_atoms_str.split():
637
            try:
638
                disso_atoms.append(int(el))
639
            except ValueError:
640
                disso_atoms.append(el)
641
    return disso_atoms
642

    
643

    
644
# Refinement
645

    
646
def get_refine_inp_file():  # TODO if not specified try isol_inp_file.
647
    refine_inp_file = dos_inp.get('Refinement', 'refine_inp_file')
648
    if not os.path.isfile(refine_inp_file):
649
        logger.error(f'File {refine_inp_file} not found.')
650
        raise FileNotFoundError(f'File {refine_inp_file} not found')
651

    
652
    return refine_inp_file
653

    
654

    
655
def get_energy_cutoff():
656
    err_msg = num_error % ('energy_cutoff', 'positive decimal number')
657
    energy_cutoff = try_command(dos_inp.getfloat,
658
                                [(ValueError, err_msg)],
659
                                'Refinement', 'energy_cutoff', fallback=0.5)
660
    if energy_cutoff < 0:
661
        logger.error(err_msg)
662
        raise ValueError(err_msg)
663
    return energy_cutoff
664

    
665

    
666
def read_input(in_file):
667
    err = False
668
    try:
669
        dos_inp.read(in_file)
670
    except MissingSectionHeaderError as e:
671
        logger.error('There are options in the input file without a Section '
672
                     'header.')
673
        err = e
674
    except DuplicateOptionError as e:
675
        logger.error('There is an option in the input file that has been '
676
                     'specified more than once.')
677
        err = e
678
    except Exception as e:
679
        err = e
680
    else:
681
        err = False
682
    finally:
683
        if isinstance(err, BaseException):
684
            raise err
685

    
686
    inp_vars = {}
687

    
688
    # Global
689
    if not dos_inp.has_section('Global'):
690
        logger.error(no_sect_err % 'Global')
691
        raise NoSectionError('Global')
692

    
693
    # Mandatory options
694
    # Checks whether the mandatory options 'run_type', 'code', etc. are present.
695
    glob_mand_opts = ['run_type', 'batch_q_sys']
696
    for opt in glob_mand_opts:
697
        if not dos_inp.has_option('Global', opt):
698
            logger.error(no_opt_err % (opt, 'Global'))
699
            raise NoOptionError(opt, 'Global')
700

    
701
    # Gets which sections are to be carried out
702
    isolated, screening, refinement = get_run_type()
703
    inp_vars['isolated'] = isolated
704
    inp_vars['screening'] = screening
705
    inp_vars['refinement'] = refinement
706
    inp_vars['batch_q_sys'] = get_batch_q_sys()
707

    
708
    # Dependent options:
709
    inp_vars['code'] = get_code()
710
    if inp_vars['batch_q_sys']:
711
        inp_vars['type_max'] = get_type_max()
712
        inp_vars['max_jobs'] = get_max_jobs()
713
        if inp_vars['batch_q_sys'] != 'local':
714
            if not dos_inp.has_option('Global', 'subm_script'):
715
                logger.error(no_opt_err % ('subm_script', 'Global'))
716
                raise NoOptionError('subm_script', 'Global')
717
            inp_vars['subm_script'] = get_subm_script()
718
        elif inp_vars['type_max'] == "p":
719
            err = "'type_max' cannot be set to 'pending'/'queued' when " \
720
                  "'batch_q_sys' is set to 'local' "
721
            logger.error(err)
722
            raise ValueError(err)
723

    
724
    # Facultative options (Default/Fallback value present)
725
    inp_vars['project_name'] = get_project_name()
726
    inp_vars['relaunch_err'] = get_relaunch_err()
727
    inp_vars['special_atoms'] = get_special_atoms()
728

    
729
    # Isolated
730
    if isolated:
731
        if not dos_inp.has_section('Isolated'):
732
            logger.error(no_sect_err % 'Isolated')
733
            raise NoSectionError('Isolated')
734
        # Mandatory options
735
        # Checks whether the mandatory options are present.
736
        iso_mand_opts = ['isol_inp_file', 'molec_file']
737
        for opt in iso_mand_opts:
738
            if not dos_inp.has_option('Isolated', opt):
739
                logger.error(no_opt_err % (opt, 'Isolated'))
740
                raise NoOptionError(opt, 'Isolated')
741
        inp_vars['isol_inp_file'] = get_isol_inp_file()
742
        if 'code ' in inp_vars:
743
            check_inp_file(inp_vars['isol_inp_file'], inp_vars['code'])
744
        inp_vars['molec_file'] = get_molec_file()
745

    
746
        # Facultative options (Default/Fallback value present)
747
        inp_vars['num_conformers'] = get_num_conformers()
748
        # inp_vars['num_prom_cand'] = get_num_prom_cand()
749
        # inp_vars['iso_rmsd'] = get_iso_rmsd()
750
        inp_vars['pre_opt'] = get_pre_opt()
751

    
752
    # Screening
753
    if screening:
754
        if not dos_inp.has_section('Screening'):
755
            logger.error(no_sect_err % 'Screening')
756
            raise NoSectionError('Screening')
757
        # Mandatory options:
758
        # Checks whether the mandatory options are present.
759
        screen_mand_opts = ['screen_inp_file', 'surf_file', 'sites',
760
                            'molec_ctrs']
761
        for opt in screen_mand_opts:
762
            if not dos_inp.has_option('Screening', opt):
763
                logger.error(no_opt_err % (opt, 'Screening'))
764
                raise NoOptionError(opt, 'Screening')
765
        inp_vars['screen_inp_file'] = get_screen_inp_file()
766
        inp_vars['surf_file'] = get_surf_file()
767
        inp_vars['sites'] = get_sites()
768
        inp_vars['molec_ctrs'] = get_molec_ctrs()
769

    
770
        # Facultative options (Default value present)
771
        inp_vars['select_magns'] = get_select_magns()
772
        inp_vars['confs_per_magn'] = get_confs_per_magn()
773
        inp_vars['pbc_cell'] = get_pbc_cell()
774
        inp_vars['surf_norm_vect'] = get_surf_norm_vect()
775
        inp_vars['disso_atoms'] = get_disso_atoms()
776
        inp_vars['set_angles'] = get_set_angles()
777
        inp_vars['sample_points_per_angle'] = get_pts_per_angle()
778
        inp_vars['max_structures'] = get_max_structures()
779
        inp_vars['collision_threshold'] = get_coll_thrsld()
780
        inp_vars['min_coll_height'] = get_min_coll_height()
781
        cart_axes = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0],
782
                     [-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]]
783
        if inp_vars['min_coll_height'] is not False \
784
                and inp_vars['surf_norm_vect'].tolist() not in cart_axes:
785
            logger.warning("'min_coll_height' option is only implemented for "
786
                           "'surf_norm_vect' to be one of the x, y or z axes.")
787

    
788
        if inp_vars['set_angles'] == "chemcat":
789
            chemcat_opts = ['molec_ctrs2', 'molec_ctrs3', 'surf_ctrs2',
790
                            'max_helic_angle']
791
            for opt in chemcat_opts:
792
                if not dos_inp.has_option('Screening', opt):
793
                    logger.error(no_opt_err % (opt, 'Screening'))
794
                    raise NoOptionError(opt, 'Screening')
795
            inp_vars['max_helic_angle'] = get_max_helic_angle()
796
            inp_vars['molec_ctrs2'] = get_molec_ctrs2()
797
            inp_vars['molec_ctrs3'] = get_molec_ctrs3()
798
            inp_vars['surf_ctrs2'] = get_surf_ctrs2()
799
            if len(inp_vars["molec_ctrs2"]) != len(inp_vars['molec_ctrs']) \
800
                    or len(inp_vars["molec_ctrs3"]) != \
801
                    len(inp_vars['molec_ctrs']) \
802
                    or len(inp_vars['surf_ctrs2']) != len(inp_vars['sites']):
803
                err = "'molec_ctrs' and 'molec_ctrs2' must have the same " \
804
                      "number of indices "
805
                logger.error(err)
806
                raise ValueError(err)
807

    
808
    # Refinement
809
    if refinement:
810
        if not dos_inp.has_section('Refinement'):
811
            logger.error(no_sect_err % 'Refinement')
812
            raise NoSectionError('Refinement')
813
        # Mandatory options
814
        # Checks whether the mandatory options are present.
815
        ref_mand_opts = ['refine_inp_file']
816
        for opt in ref_mand_opts:
817
            if not dos_inp.has_option('Refinement', opt):
818
                logger.error(no_opt_err % (opt, 'Refinement'))
819
                raise NoOptionError(opt, 'Refinement')
820
        inp_vars['refine_inp_file'] = get_refine_inp_file()
821

    
822
        # Facultative options (Default value present)
823
        inp_vars['energy_cutoff'] = get_energy_cutoff()
824
        # end energy_cutoff
825

    
826
    return_vars_str = "\n\t".join([str(key) + ": " + str(value)
827
                                   for key, value in inp_vars.items()])
828
    logger.info(
829
        f'Correctly read {in_file} parameters: \n\n\t{return_vars_str}\n')
830

    
831
    return inp_vars
832

    
833

    
834
if __name__ == "__main__":
835
    import sys
836

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