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

dockonsurf / modules / dos_input.py @ 75d6a31b

Historique | Voir | Annoter | Télécharger (28,09 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_ads_ctrs: Gets 'molec_ads_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 integer " \
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
    check_expect_val(dos_inp.get('Global', 'run_type').lower(), run_type_vals)
171

    
172
    run_type = dos_inp.get('Global', 'run_type').lower()
173
    if 'isolated' in run_type:
174
        isolated = True
175
    if 'screening' in run_type:
176
        screening = True
177
    if 'refinement' in run_type:
178
        refinement = True
179
    if 'adsorption' in run_type:
180
        screening, refinement = (True, True)
181
    if 'full' in run_type:
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 acceoted. 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_num_prom_cand():
327
    err_msg = num_error % ('num_prom_cand', 'positive integer')
328
    num_prom_cand = try_command(dos_inp.getint, [(ValueError, err_msg)],
329
                                'Isolated', 'num_prom_cand', fallback=3)
330
    if num_prom_cand < 1:
331
        logger.error(err_msg)
332
        raise ValueError(err_msg)
333
    return num_prom_cand
334

    
335

    
336
def get_iso_rmsd():
337
    err_msg = num_error % ('iso_rmsd', 'positive decimal number')
338
    iso_rmsd = try_command(dos_inp.getfloat, [(ValueError, err_msg)],
339
                           'Isolated', 'iso_rmsd', fallback=0.05)
340
    if iso_rmsd <= 0.0:
341
        logger.error(err_msg)
342
        raise ValueError(err_msg)
343
    return iso_rmsd
344

    
345

    
346
def get_pre_opt():
347
    pre_opt_vals = ['uff', 'mmff'] + turn_false_answers
348
    err_msg = "'pre_opt' should be UFF, MMFF or False"
349
    check_expect_val(dos_inp.get('Isolated', 'pre_opt').lower(), pre_opt_vals)
350
    pre_opt = dos_inp.get('Isolated', 'pre_opt')
351
    if pre_opt.lower() in turn_false_answers:
352
        return False
353
    else:
354
        return pre_opt
355

    
356

    
357
# Screening
358

    
359
def get_screen_inp_file():
360
    screen_inp_file = dos_inp.get('Screening', 'screen_inp_file')
361
    if not os.path.isfile(screen_inp_file):
362
        logger.error(f'File {screen_inp_file} not found.')
363
        raise FileNotFoundError(f'File {screen_inp_file} not found')
364
    return screen_inp_file
365

    
366

    
367
def get_surf_file():
368
    surf_file = dos_inp.get('Screening', 'surf_file')
369
    if not os.path.isfile(surf_file):
370
        logger.error(f'File {surf_file} not found.')
371
        raise FileNotFoundError(f'File {surf_file} not found')
372
    return surf_file
373

    
374

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

    
399
    return sites
400

    
401

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

    
427
    return molec_ads_ctrs
428

    
429

    
430
def get_molec_neigh_ctrs():
431
    err_msg = 'The value of molec_neigh_ctrs should be a list of atom ' \
432
              'numbers (ie. positive integers) or groups of atom ' \
433
              'numbers enclosed by parentheses-like characters. \n' \
434
              'eg. 128,(135 138;141) 87 {45, 68}'
435
    # Convert the string into a list of lists
436
    molec_neigh_ctrs = try_command(str2lst, [(ValueError, err_msg),
437
                                             (AttributeError, err_msg)],
438
                                   dos_inp.get('Screening', 'molec_neigh_ctrs'))
439

    
440
    # Check all elements of the list (of lists) are positive integers
441
    for ctr in molec_neigh_ctrs:
442
        if isinstance(ctr, list):
443
            for atom in ctr:
444
                if atom < 0:
445
                    logger.error(err_msg)
446
                    raise ValueError(err_msg)
447
        elif isinstance(ctr, int):
448
            if ctr < 0:
449
                logger.error(err_msg)
450
                raise ValueError(err_msg)
451
        else:
452
            logger.error(err_msg)
453
            raise ValueError(err_msg)
454

    
455
    return molec_neigh_ctrs
456

    
457

    
458
def get_select_magns():
459
    select_magns_vals = ['energy', 'moi']
460
    select_magns_str = dos_inp.get('Screening', 'select_magns',
461
                                   fallback='energy')
462
    select_magns_str.replace(',', ' ').replace(';', ' ')
463
    select_magns = select_magns_str.split(' ')
464
    select_magns = [m.lower() for m in select_magns]
465
    for m in select_magns:
466
        check_expect_val(m, select_magns_vals)
467
    return select_magns
468

    
469

    
470
def get_confs_per_magn():
471
    err_msg = num_error % ('confs_per_magn', 'positive integer')
472
    confs_per_magn = try_command(dos_inp.getint, [(ValueError, err_msg)],
473
                                 'Screening', 'confs_per_magn', fallback=2)
474
    if confs_per_magn <= 0:
475
        logger.error(err_msg)
476
        raise ValueError(err_msg)
477
    return confs_per_magn
478

    
479

    
480
def get_surf_norm_vect():
481
    err = "'surf_norm_vect' must be either a 3 component vector or 'x', 'y' " \
482
          "or 'z'"
483
    cart_axes = {'x': [1.0, 0.0, 0.0], '-x': [-1.0, 0.0, 0.0],
484
                 'y': [0.0, 1.0, 0.0], '-y': [0.0, -1.0, 0.0],
485
                 'z': [0.0, 0.0, 1.0], '-z': [0.0, 0.0, -1.0]}
486
    surf_norm_vect_str = dos_inp.get('Screening', 'surf_norm_vect',
487
                                     fallback="z")
488

    
489
    if surf_norm_vect_str in cart_axes:
490
        surf_norm_vect = cart_axes[surf_norm_vect_str]
491
    else:
492
        surf_norm_vect = try_command(str2lst, [(ValueError, err)],
493
                                     surf_norm_vect_str, float)
494
        if len(surf_norm_vect) != 3:
495
            logger.error(err)
496
            raise ValueError(err)
497

    
498
    return np.array(surf_norm_vect)
499

    
500

    
501
def get_ads_algo():
502
    algo_vals = ['euler', 'chemcat']
503
    check_expect_val(dos_inp.get('Screening', 'ads_algo').lower(), algo_vals)
504
    ads_algo = dos_inp.get('Screening', 'ads_algo', fallback='euler').lower()
505
    return ads_algo
506

    
507

    
508
def get_pts_per_angle():
509
    err_msg = num_error % ('sample_points_per_angle', 'positive integer')
510
    pts_per_angle = try_command(dos_inp.getint,
511
                                [(ValueError, err_msg)],
512
                                'Screening', 'sample_points_per_angle',
513
                                fallback=3)
514
    if pts_per_angle <= 0:
515
        logger.error(err_msg)
516
        raise ValueError(err_msg)
517
    return pts_per_angle
518

    
519

    
520
def get_max_structures():
521
    err_msg = num_error % ('max_structures', 'positive integer')
522
    max_structures = try_command(dos_inp.getint, [(ValueError, err_msg)],
523
                                 'Screening', 'max_structures', fallback=np.inf)
524
    if max_structures <= 0:
525
        logger.error(err_msg)
526
        raise ValueError(err_msg)
527
    return max_structures
528

    
529

    
530
def get_coll_thrsld():
531
    err_msg = num_error % ('collision_threshold',
532
                           'positive decimal number')
533

    
534
    coll_thrsld = try_command(dos_inp.getfloat,
535
                              [(ValueError, err_msg)],
536
                              'Screening', 'collision_threshold', fallback=1.2)
537
    if coll_thrsld <= 0:
538
        logger.error(err_msg)
539
        raise ValueError(err_msg)
540

    
541
    return coll_thrsld
542

    
543

    
544
def get_screen_rmsd():
545
    err_msg = num_error % ('screen_rmsd', 'positive decimal number')
546
    screen_rmsd = try_command(dos_inp.getfloat,
547
                              [(ValueError, err_msg)],
548
                              'Screening', 'screen_rmsd', fallback=0.05)
549
    if screen_rmsd <= 0:
550
        logger.error(err_msg)
551
        raise ValueError(err_msg)
552

    
553
    return screen_rmsd
554

    
555

    
556
def get_min_coll_height():
557
    err_msg = num_error % ('min_coll_height', 'decimal number')
558
    min_coll_height = dos_inp.get('Screening', 'min_coll_height',
559
                                  fallback="False")
560
    if min_coll_height.lower() in turn_false_answers:
561
        min_coll_height = False
562
    else:
563
        min_coll_height = try_command(float, [(ValueError, err_msg)],
564
                                      min_coll_height)
565

    
566
    return min_coll_height
567

    
568

    
569
def get_disso_atoms():
570
    err_msg = "try_disso should be have a boolean value (True or False)"
571
    disso_atoms_str = dos_inp.get('Screening', 'disso_atoms', fallback="False")
572
    disso_atoms = []
573
    if disso_atoms_str.lower() in turn_false_answers:
574
        pass
575
    else:
576
        for el in disso_atoms_str.split():
577
            try:
578
                disso_atoms.append(int(el))
579
            except ValueError:
580
                disso_atoms.append(el)
581
    return disso_atoms
582

    
583

    
584
# Refinement
585

    
586
def get_refine_inp_file():  # TODO if not specified try isol_inp_file.
587
    refine_inp_file = dos_inp.get('Refinement', 'refine_inp_file')
588
    if not os.path.isfile(refine_inp_file):
589
        logger.error(f'File {refine_inp_file} not found.')
590
        raise FileNotFoundError(f'File {refine_inp_file} not found')
591

    
592
    return refine_inp_file
593

    
594

    
595
def get_energy_cutoff():
596
    err_msg = num_error % ('energy_cutoff', 'positive decimal number')
597
    energy_cutoff = try_command(dos_inp.getfloat,
598
                                [(ValueError, err_msg)],
599
                                'Refinement', 'energy_cutoff', fallback=0.5)
600
    if energy_cutoff < 0:
601
        logger.error(err_msg)
602
        raise ValueError(err_msg)
603
    return energy_cutoff
604

    
605

    
606
def read_input(in_file):
607
    err = False
608
    try:
609
        dos_inp.read(in_file)
610
    except MissingSectionHeaderError as e:
611
        logger.error('There are options in the input file without a Section '
612
                     'header.')
613
        err = e
614
    except DuplicateOptionError as e:
615
        logger.error('There is an option in the input file that has been '
616
                     'specified more than once.')
617
        err = e
618
    except Exception as e:
619
        err = e
620
    else:
621
        err = False
622
    finally:
623
        if isinstance(err, BaseException):
624
            raise err
625

    
626
    return_vars = {}
627

    
628
    # Global
629
    if not dos_inp.has_section('Global'):
630
        logger.error(no_sect_err % 'Global')
631
        raise NoSectionError('Global')
632

    
633
    # Mandatory options
634
    # Checks whether the mandatory options 'run_type', 'code', etc. are present.
635
    glob_mand_opts = ['run_type', 'batch_q_sys']
636
    for opt in glob_mand_opts:
637
        if not dos_inp.has_option('Global', opt):
638
            logger.error(no_opt_err % (opt, 'Global'))
639
            raise NoOptionError(opt, 'Global')
640

    
641
    # Gets which sections are to be carried out
642
    isolated, screening, refinement = get_run_type()
643
    return_vars['isolated'] = isolated
644
    return_vars['screening'] = screening
645
    return_vars['refinement'] = refinement
646
    return_vars['batch_q_sys'] = get_batch_q_sys()
647

    
648
    # Dependent options:
649
    return_vars['code'] = get_code()
650
    if return_vars['batch_q_sys']:
651
        return_vars['type_max'] = get_type_max()
652
        return_vars['max_jobs'] = get_max_jobs()
653
        if return_vars['batch_q_sys'] != 'local':
654
            if not dos_inp.has_option('Global', 'subm_script'):
655
                logger.error(no_opt_err % ('subm_script', 'Global'))
656
                raise NoOptionError('subm_script', 'Global')
657
            return_vars['subm_script'] = get_subm_script()
658
        elif return_vars['type_max'] == "p":
659
            err = "'type_max' cannot be set to 'pending'/'queued' when " \
660
                  "'batch_q_sys' is set to 'local' "
661
            logger.error(err)
662
            raise ValueError(err)
663

    
664
    # Facultative options (Default/Fallback value present)
665
    return_vars['project_name'] = get_project_name()
666
    return_vars['relaunch_err'] = get_relaunch_err()
667
    return_vars['special_atoms'] = get_special_atoms()
668

    
669
    # Isolated
670
    if isolated:
671
        if not dos_inp.has_section('Isolated'):
672
            logger.error(no_sect_err % 'Isolated')
673
            raise NoSectionError('Isolated')
674
        # Mandatory options
675
        # Checks whether the mandatory options are present.
676
        iso_mand_opts = ['isol_inp_file', 'molec_file']
677
        for opt in iso_mand_opts:
678
            if not dos_inp.has_option('Isolated', opt):
679
                logger.error(no_opt_err % (opt, 'Isolated'))
680
                raise NoOptionError(opt, 'Isolated')
681
        return_vars['isol_inp_file'] = get_isol_inp_file()
682
        if 'code ' in return_vars:
683
            check_inp_file(return_vars['isol_inp_file'], return_vars['code'])
684
        return_vars['molec_file'] = get_molec_file()
685

    
686
        # Facultative options (Default/Fallback value present)
687
        return_vars['num_conformers'] = get_num_conformers()
688
        # return_vars['num_prom_cand'] = get_num_prom_cand()
689
        # return_vars['iso_rmsd'] = get_iso_rmsd()
690
        return_vars['pre_opt'] = get_pre_opt()
691

    
692
    # Screening
693
    if screening:
694
        if not dos_inp.has_section('Screening'):
695
            logger.error(no_sect_err % 'Screening')
696
            raise NoSectionError('Screening')
697
        # Mandatory options:
698
        # Checks whether the mandatory options are present.
699
        screen_mand_opts = ['screen_inp_file', 'surf_file', 'sites',
700
                            'molec_ads_ctrs', 'molec_neigh_ctrs']
701
        for opt in screen_mand_opts:
702
            if not dos_inp.has_option('Screening', opt):
703
                logger.error(no_opt_err % (opt, 'Screening'))
704
                raise NoOptionError(opt, 'Screening')
705
        return_vars['screen_inp_file'] = get_screen_inp_file()
706
        return_vars['surf_file'] = get_surf_file()
707
        return_vars['sites'] = get_sites()
708
        return_vars['molec_ads_ctrs'] = get_molec_ads_ctrs()
709
        return_vars['molec_neigh_ctrs'] = get_molec_neigh_ctrs()
710
        if len(return_vars['molec_ads_ctrs']) != \
711
                len(return_vars['molec_neigh_ctrs']):
712
            err = "'molec_ads_ctrs' and 'molec_neigh_ctrs' must have the same" \
713
                  "number of indides"
714
            logger.error(err)
715
            raise ValueError(err)
716

    
717
        # Facultative options (Default value present)
718
        return_vars['select_magns'] = get_select_magns()
719
        return_vars['confs_per_magn'] = get_confs_per_magn()
720
        return_vars['surf_norm_vect'] = get_surf_norm_vect()
721
        return_vars['disso_atoms'] = get_disso_atoms()
722
        return_vars['ads_algo'] = get_ads_algo()
723
        return_vars['sample_points_per_angle'] = get_pts_per_angle()
724
        return_vars['max_structures'] = get_max_structures()
725
        return_vars['collision_threshold'] = get_coll_thrsld()
726
        return_vars['min_coll_height'] = get_min_coll_height()
727
        cart_axes = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0],
728
                     [-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]]
729
        if return_vars['min_coll_height'] is not False and \
730
                return_vars['surf_norm_vect'].tolist() not in cart_axes:
731
            logger.warning("'min_coll_height' option is only implemented for "
732
                           "'surf_norm_vect' to be one of the x, y or z axes.")
733

    
734
    # Refinement
735
    if refinement:
736
        if not dos_inp.has_section('Refinement'):
737
            logger.error(no_sect_err % 'Refinement')
738
            raise NoSectionError('Refinement')
739
        # Mandatory options
740
        # Checks whether the mandatory options are present.
741
        ref_mand_opts = ['refine_inp_file']
742
        for opt in ref_mand_opts:
743
            if not dos_inp.has_option('Refinement', opt):
744
                logger.error(no_opt_err % (opt, 'Refinement'))
745
                raise NoOptionError(opt, 'Refinement')
746
        return_vars['refine_inp_file'] = get_refine_inp_file()
747

    
748
        # Facultative options (Default value present)
749
        return_vars['energy_cutoff'] = get_energy_cutoff()
750
        # end energy_cutoff
751

    
752
    return_vars_str = "\n\t".join([str(key) + ": " + str(value)
753
                                   for key, value in return_vars.items()])
754
    logger.info(
755
        f'Correctly read {in_file} parameters: \n\n\t{return_vars_str}\n')
756

    
757
    return return_vars
758

    
759

    
760
if __name__ == "__main__":
761
    import sys
762

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