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

dockonsurf / modules / dos_input.py @ cea7af64

Historique | Voir | Annoter | Télécharger (32,45 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
    err_msg = num_error % ('max_jobs', 'positive integer')
233
    max_jobs = try_command(dos_inp.getint, [(ValueError, err_msg)],
234
                           'Global', 'max_jobs', fallback=3)
235

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

    
241

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

    
258

    
259
def get_special_atoms():
260
    from ase.data import chemical_symbols
261

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

    
299

    
300
# Isolated
301

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

    
309

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

    
317

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

    
327

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

    
337

    
338
# Screening
339

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

    
347

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

    
355

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

    
380
    return sites
381

    
382

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

    
407
    return surf_ctrs2
408

    
409

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

    
435
    return molec_ctrs
436

    
437

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

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

    
463
    return molec_ctrs2
464

    
465

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

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

    
491
    return molec_ctrs3
492

    
493

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

    
503
    return max_helic_angle
504

    
505

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

    
517

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

    
527

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

    
541

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

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

    
560
    return np.array(surf_norm_vect)/np.linalg.norm(surf_norm_vect)
561

    
562

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

    
570

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

    
582

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

    
593

    
594
def get_coll_thrsld():
595
    err_msg = num_error % ('collision_threshold',
596
                           'positive decimal number')
597

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

    
605
    return coll_thrsld
606

    
607

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

    
617
    return screen_rmsd
618

    
619

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

    
630
    return min_coll_height
631

    
632

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

    
657

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

    
682

    
683
# Refinement
684

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

    
691
    return refine_inp_file
692

    
693

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

    
704

    
705
# Read input parameters
706

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

    
727
    inp_vars = {}
728

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

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

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

    
749
    # Dependent options:
750
    inp_vars['code'] = get_code()
751
    if inp_vars['batch_q_sys']:
752
        inp_vars['type_max'] = get_type_max()
753
        inp_vars['max_jobs'] = get_max_jobs()
754
        if inp_vars['batch_q_sys'] != 'local':
755
            if not dos_inp.has_option('Global', 'subm_script'):
756
                logger.error(no_opt_err % ('subm_script', 'Global'))
757
                raise NoOptionError('subm_script', 'Global')
758
            inp_vars['subm_script'] = get_subm_script()
759
        elif inp_vars['type_max'] == "p":
760
            err = "'type_max' cannot be set to 'pending'/'queued' when " \
761
                  "'batch_q_sys' is set to 'local' "
762
            logger.error(err)
763
            raise ValueError(err)
764

    
765
    # Facultative options (Default/Fallback value present)
766
    inp_vars['project_name'] = get_project_name()
767
    inp_vars['relaunch_err'] = get_relaunch_err()
768
    inp_vars['special_atoms'] = get_special_atoms()
769

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

    
787
        # Facultative options (Default/Fallback value present)
788
        inp_vars['num_conformers'] = get_num_conformers()
789
        # inp_vars['num_prom_cand'] = get_num_prom_cand()
790
        # inp_vars['iso_rmsd'] = get_iso_rmsd()
791
        inp_vars['pre_opt'] = get_pre_opt()
792

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

    
811
        # Facultative options (Default value present)
812
        inp_vars['select_magns'] = get_select_magns()
813
        inp_vars['confs_per_magn'] = get_confs_per_magn()
814
        inp_vars['surf_norm_vect'] = get_surf_norm_vect()
815
        inp_vars['set_angles'] = get_set_angles()
816
        inp_vars['sample_points_per_angle'] = get_pts_per_angle()
817
        inp_vars['pbc_cell'] = get_pbc_cell()
818
        inp_vars['collision_threshold'] = get_coll_thrsld()
819
        inp_vars['min_coll_height'] = get_min_coll_height()
820
        inp_vars['h_donor'] = get_H_donor(inp_vars['special_atoms'])
821
        inp_vars['max_structures'] = get_max_structures()
822

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

    
843
        cart_axes = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0],
844
                     [-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]]
845
        if inp_vars['min_coll_height'] is not False \
846
                and inp_vars['surf_norm_vect'].tolist() not in cart_axes:
847
            logger.warning("'min_coll_height' option is only implemented for "
848
                           "'surf_norm_vect' to be one of the x, y or z axes.")
849

    
850
        if inp_vars['h_donor'] is False:
851
            inp_vars['h_acceptor'] = False
852
        else:
853
            inp_vars['h_acceptor'] = get_H_acceptor(inp_vars['special_atoms'])
854

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

    
869
        # Facultative options (Default value present)
870
        inp_vars['energy_cutoff'] = get_energy_cutoff()
871
        # end energy_cutoff
872

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

    
878
    return inp_vars
879

    
880

    
881
if __name__ == "__main__":
882
    import sys
883

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