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

dockonsurf / modules / calculation.py @ dbef6e98

Historique | Voir | Annoter | Télécharger (13,54 ko)

1 3d6a9d3c Carles
import os
2 3d6a9d3c Carles
import logging
3 3d6a9d3c Carles
4 3d6a9d3c Carles
logger = logging.getLogger('DockOnSurf')
5 3d6a9d3c Carles
6 3d6a9d3c Carles
7 f85667b9 Carles Marti
def check_finished_calcs(run_type, code):
8 dbef6e98 Carles Martí
    from modules.utilities import _human_key
9 f85667b9 Carles Marti
    """Returns two lists of calculations finished normally and abnormally.
10 f85667b9 Carles Marti

11 f85667b9 Carles Marti
    @param run_type: The type of calculation to check.
12 f85667b9 Carles Marti
    @param code: The code used for the specified job.
13 f85667b9 Carles Marti
    @return finished_calcs: List of calculations that have finished normally.
14 f85667b9 Carles Marti
    @return unfinished_calcs: List of calculations that have finished abnormally
15 f85667b9 Carles Marti
    """
16 f85667b9 Carles Marti
    from glob import glob
17 0d2f159a Carles Martí
    import ase.io
18 f85667b9 Carles Marti
    from modules.utilities import tail
19 f85667b9 Carles Marti
20 f85667b9 Carles Marti
    finished_calcs = []
21 f85667b9 Carles Marti
    unfinished_calcs = []
22 dbef6e98 Carles Martí
    for conf in sorted(os.listdir(run_type), key=_human_key):
23 f85667b9 Carles Marti
        if not os.path.isdir(f'{run_type}/{conf}') or 'conf_' not in conf:
24 f85667b9 Carles Marti
            continue
25 f85667b9 Carles Marti
        if code == 'cp2k':
26 f85667b9 Carles Marti
            out_file_list = glob(f"{run_type}/{conf}/*.out")
27 f85667b9 Carles Marti
            restart_file_list = glob(f"{run_type}/{conf}/*-1.restart")
28 f85667b9 Carles Marti
            if len(out_file_list) == 0 or len(restart_file_list) == 0:
29 1f1d0644 Carles Martí
                unfinished_calcs.append(conf)
30 f85667b9 Carles Marti
            elif len(out_file_list) > 1 or len(restart_file_list) > 1:
31 f85667b9 Carles Marti
                warn_msg = f'There is more than one file matching the {code} ' \
32 f85667b9 Carles Marti
                           f'pattern for finished calculation (*.out / ' \
33 f85667b9 Carles Marti
                           f'*-1.restart) in {run_type}/{conf}: ' \
34 f85667b9 Carles Marti
                           f'{out_file_list, restart_file_list}. ' \
35 f85667b9 Carles Marti
                           f'Skipping directory.'
36 f85667b9 Carles Marti
                logger.warning(warn_msg)
37 f85667b9 Carles Marti
                unfinished_calcs.append(conf)
38 f85667b9 Carles Marti
            else:
39 f85667b9 Carles Marti
                with open(out_file_list[0], 'rb') as out_fh:
40 f85667b9 Carles Marti
                    if "PROGRAM STOPPED IN" not in tail(out_fh):
41 f85667b9 Carles Marti
                        unfinished_calcs.append(conf)
42 f85667b9 Carles Marti
                    else:
43 f85667b9 Carles Marti
                        finished_calcs.append(conf)
44 234eefed Carles Martí
        elif code == 'vasp':
45 234eefed Carles Martí
            out_file_list = glob(f"{run_type}/{conf}/OUTCAR")
46 234eefed Carles Martí
            if len(out_file_list) == 0:
47 234eefed Carles Martí
                unfinished_calcs.append(conf)
48 234eefed Carles Martí
            elif len(out_file_list) > 1:
49 234eefed Carles Martí
                warn_msg = f'There is more than one file matching the {code} ' \
50 234eefed Carles Martí
                           f'pattern for finished calculation (*.out / ' \
51 234eefed Carles Martí
                           f'*-1.restart) in {run_type}/{conf}: ' \
52 234eefed Carles Martí
                           f'{out_file_list}. Skipping directory.'
53 234eefed Carles Martí
                logger.warning(warn_msg)
54 234eefed Carles Martí
                unfinished_calcs.append(conf)
55 234eefed Carles Martí
            else:
56 0d2f159a Carles Martí
                try:
57 0d2f159a Carles Martí
                    ase.io.read(f"{run_type}/{conf}/OUTCAR")
58 0d2f159a Carles Martí
                except ValueError:
59 0d2f159a Carles Martí
                    unfinished_calcs.append(conf)
60 0d2f159a Carles Martí
                    continue
61 0d2f159a Carles Martí
                except IndexError:
62 0d2f159a Carles Martí
                    unfinished_calcs.append(conf)
63 0d2f159a Carles Martí
                    continue
64 234eefed Carles Martí
                with open(f"{run_type}/{conf}/OUTCAR", 'rb') as out_fh:
65 234eefed Carles Martí
                    if "General timing and accounting" not in tail(out_fh):
66 234eefed Carles Martí
                        unfinished_calcs.append(conf)
67 234eefed Carles Martí
                    else:
68 234eefed Carles Martí
                        finished_calcs.append(conf)
69 234eefed Carles Martí
        else:
70 234eefed Carles Martí
            err_msg = f"Check not implemented for '{code}'."
71 234eefed Carles Martí
            logger.error(err_msg)
72 234eefed Carles Martí
            raise NotImplementedError(err_msg)
73 f85667b9 Carles Marti
    return finished_calcs, unfinished_calcs
74 f85667b9 Carles Marti
75 f85667b9 Carles Marti
76 14e0b660 Carles Martí
def prep_cp2k(inp_file: str, run_type: str, atms_list: list, proj_name: str):
77 14e0b660 Carles Martí
    """Prepares the directories to run calculations with CP2K.
78 3d6a9d3c Carles

79 f3004731 Carles
    @param inp_file: CP2K Input file to run the calculations with.
80 f3004731 Carles
    @param run_type: Type of calculation. 'isolated', 'screening' or
81 f3004731 Carles
        'refinement'
82 f3004731 Carles
    @param atms_list: list of ase.Atoms objects to run the calculation of.
83 1a158b9f Carles Marti
    @param proj_name: name of the project
84 f3004731 Carles
    @return: None
85 f3004731 Carles
    """
86 f3004731 Carles
    from shutil import copy
87 f3004731 Carles
    from pycp2k import CP2K
88 af3e2441 Carles Marti
    from modules.utilities import check_bak
89 9d3b680c Carles Martí
    if not isinstance(inp_file, str):
90 9d3b680c Carles Martí
        err_msg = "'inp_file' must be a string with the path of the CP2K " \
91 9d3b680c Carles Martí
                  "input file."
92 9d3b680c Carles Martí
        logger.error(err_msg)
93 9d3b680c Carles Martí
        raise ValueError(err_msg)
94 f3004731 Carles
    cp2k = CP2K()
95 f3004731 Carles
    cp2k.parse(inp_file)
96 1a158b9f Carles Marti
    cp2k.CP2K_INPUT.GLOBAL.Project_name = proj_name+"_"+run_type
97 f3004731 Carles
    force_eval = cp2k.CP2K_INPUT.FORCE_EVAL_list[0]
98 b05058e1 Carles
    if force_eval.SUBSYS.TOPOLOGY.Coord_file_name is None:
99 2dfa562f Carles Martí
        logger.warning("'COORD_FILE_NAME' not specified on CP2K input. Using\n"
100 f601942f Carles Marti
                       "'coord.xyz'. A new CP2K input file with "
101 f601942f Carles Marti
                       "the 'COORD_FILE_NAME' variable is created.")
102 b05058e1 Carles
        force_eval.SUBSYS.TOPOLOGY.Coord_file_name = 'coord.xyz'
103 2dfa562f Carles Martí
        check_bak(inp_file.split('/')[-1])
104 1a158b9f Carles Marti
    cp2k.write_input_file(inp_file.split('/')[-1])
105 b05058e1 Carles
106 f3004731 Carles
    coord_file = force_eval.SUBSYS.TOPOLOGY.Coord_file_name
107 f3004731 Carles
108 99afde40 Carles
    # Creating and setting up directories for every configuration.
109 f3004731 Carles
    for i, conf in enumerate(atms_list):
110 9d3b680c Carles Martí
        subdir = f'{run_type}/conf_{i}/'
111 9d3b680c Carles Martí
        os.mkdir(subdir)
112 9d3b680c Carles Martí
        copy(inp_file, subdir)
113 9d3b680c Carles Martí
        conf.write(subdir + coord_file)
114 9d3b680c Carles Martí
115 9d3b680c Carles Martí
116 9d3b680c Carles Martí
def prep_vasp(inp_files, run_type, atms_list, proj_name, cell):
117 9d3b680c Carles Martí
    """Prepares the directories to run calculations with VASP.
118 9d3b680c Carles Martí

119 9d3b680c Carles Martí
    @param inp_files: VASP Input files to run the calculations with.
120 9d3b680c Carles Martí
    @param run_type: Type of calculation. 'isolated', 'screening' or
121 9d3b680c Carles Martí
        'refinement'
122 9d3b680c Carles Martí
    @param atms_list: list of ase.Atoms objects to run the calculation of.
123 9d3b680c Carles Martí
    @param proj_name: name of the project.
124 9d3b680c Carles Martí
    @param cell: Cell for the Periodic Boundary Conditions.
125 9d3b680c Carles Martí
    @return: None
126 9d3b680c Carles Martí
    """
127 9d3b680c Carles Martí
    from shutil import copy
128 9d3b680c Carles Martí
    import os
129 9d3b680c Carles Martí
130 9d3b680c Carles Martí
    import numpy as np
131 9d3b680c Carles Martí
    from pymatgen.io.vasp.inputs import Incar
132 9d3b680c Carles Martí
133 9d3b680c Carles Martí
    mand_files = ["INCAR", "KPOINTS", "POTCAR"]
134 9d3b680c Carles Martí
    # Check that there are many specified files
135 9d3b680c Carles Martí
    if not isinstance(inp_files, list) and all(isinstance(inp_file, str)
136 9d3b680c Carles Martí
                                               for inp_file in inp_files):
137 9d3b680c Carles Martí
        err_msg = "'inp_files' should be a list of file names/paths"
138 9d3b680c Carles Martí
        logger.error(err_msg)
139 9d3b680c Carles Martí
        ValueError(err_msg)
140 9d3b680c Carles Martí
    # Check that all mandatory files are defined
141 9d3b680c Carles Martí
    elif any(not any(mand_file in inp_file.split("/")[-1]
142 9d3b680c Carles Martí
                     for inp_file in inp_files) for mand_file in mand_files):
143 9d3b680c Carles Martí
        err_msg = f"At least one of the mandatory files {mand_files} was " \
144 9d3b680c Carles Martí
                  "not specified."
145 9d3b680c Carles Martí
        logger.error(err_msg)
146 9d3b680c Carles Martí
        raise FileNotFoundError(err_msg)
147 9d3b680c Carles Martí
    # Check that the defined files exist
148 9d3b680c Carles Martí
    elif any(not os.path.isfile(inp_file) for inp_file in inp_files):
149 9d3b680c Carles Martí
        err_msg = f"At least one of the mandatory files {mand_files} was " \
150 9d3b680c Carles Martí
                  "not found."
151 9d3b680c Carles Martí
        logger.error(err_msg)
152 9d3b680c Carles Martí
        raise FileNotFoundError(err_msg)
153 017c5dbc Carles Martí
    incar = ""
154 9d3b680c Carles Martí
    for i, inp_file in enumerate(inp_files):
155 9d3b680c Carles Martí
        file_name = inp_file.split("/")[-1]
156 9d3b680c Carles Martí
        if "INCAR" in file_name:
157 9d3b680c Carles Martí
            incar = Incar.from_file(inp_file)
158 9d3b680c Carles Martí
            incar["SYSTEM"] = proj_name+"_"+run_type
159 017c5dbc Carles Martí
160 9d3b680c Carles Martí
    for c, conf in enumerate(atms_list):
161 9d3b680c Carles Martí
        subdir = f'{run_type}/conf_{c}/'
162 9d3b680c Carles Martí
        os.mkdir(subdir)
163 9d3b680c Carles Martí
        for inp_file in inp_files:
164 9d3b680c Carles Martí
            file_name = inp_file.split("/")[-1]
165 d566f8e6 Carles Martí
            if "INCAR" in file_name:
166 017c5dbc Carles Martí
                incar.write_file(subdir+"INCAR")
167 017c5dbc Carles Martí
            elif "KPOINTS" in file_name and "KPOINTS" != file_name:
168 017c5dbc Carles Martí
                copy(inp_file, subdir+"KPOINTS")
169 017c5dbc Carles Martí
            elif "POTCAR" in file_name and "POTCAR" != file_name:
170 017c5dbc Carles Martí
                copy(inp_file, subdir+"POTCAR")
171 9d3b680c Carles Martí
            else:
172 9d3b680c Carles Martí
                copy(inp_file, subdir)
173 9d3b680c Carles Martí
        if cell is not False and np.linalg.det(cell) != 0.0:
174 9d3b680c Carles Martí
            conf.pbc = True
175 9d3b680c Carles Martí
            conf.cell = cell
176 25e7e44b Carles Martí
            conf.center()
177 9d3b680c Carles Martí
        elif np.linalg.det(conf.cell) == 0:
178 9d3b680c Carles Martí
            err_msg = "Cell is not defined"
179 9d3b680c Carles Martí
            logger.error(err_msg)
180 9d3b680c Carles Martí
            raise ValueError(err_msg)
181 9d3b680c Carles Martí
        conf.write(subdir+"POSCAR", format="vasp")
182 f3004731 Carles
183 f3004731 Carles
184 2be92b2c Carles Marti
def get_jobs_status(job_ids, stat_cmd, stat_dict):
185 670284be Carles
    """Returns a list of job status for a list of job ids.
186 99afde40 Carles

187 670284be Carles
    @param job_ids: list of all jobs to be checked their status.
188 2be92b2c Carles Marti
    @param stat_cmd: Command to check job status.
189 2be92b2c Carles Marti
    @param stat_dict: Dictionary with pairs of job status (r, p, f) and the
190 2be92b2c Carles Marti
        pattern it matches in the output of the stat_cmd.
191 670284be Carles
    @return: list of status for every job.
192 99afde40 Carles
    """
193 2be92b2c Carles Marti
    from subprocess import PIPE, Popen
194 670284be Carles
    status_list = []
195 670284be Carles
    for job in job_ids:
196 99c87fcc Carles Marti
        stat_msg = Popen(stat_cmd % job, shell=True,
197 2be92b2c Carles Marti
                         stdout=PIPE).communicate()[0].decode('utf-8').strip()
198 2be92b2c Carles Marti
        if stat_dict['r'] == stat_msg:
199 670284be Carles
            status_list.append('r')
200 2be92b2c Carles Marti
        elif stat_dict['p'] == stat_msg:
201 2be92b2c Carles Marti
            status_list.append('p')
202 2be92b2c Carles Marti
        elif stat_dict['f'] == stat_msg:
203 b461f289 Carles Marti
            status_list.append('f')
204 b461f289 Carles Marti
        else:
205 99c87fcc Carles Marti
            logger.warning(f'Unrecognized job {job} status: {stat_msg}')
206 b461f289 Carles Marti
    return status_list
207 b461f289 Carles Marti
208 b461f289 Carles Marti
209 09c3325a Carles Marti
def submit_jobs(run_type, sub_cmd, sub_script, stat_cmd, stat_dict, max_jobs,
210 b6b1b03e Carles Marti
                name):
211 2be92b2c Carles Marti
    """Submits jobs to a custom queuing system with the provided script
212 b461f289 Carles Marti

213 b461f289 Carles Marti
    @param run_type: Type of calculation. 'isolated', 'screening', 'refinement'
214 b6b1b03e Carles Marti
    @param sub_cmd: Bash command used to submit jobs.
215 b461f289 Carles Marti
    @param sub_script: script for the job submission.
216 b6b1b03e Carles Marti
    @param stat_cmd: Bash command to check job status.
217 b6b1b03e Carles Marti
    @param stat_dict: Dictionary with pairs of job status: r, p, f (ie. running
218 b6b1b03e Carles Marti
        pending and finished) and the pattern it matches in the output of the
219 b6b1b03e Carles Marti
        stat_cmd.
220 b6b1b03e Carles Marti
    @param max_jobs: dict: Contains the maximum number of jobs to be both
221 b6b1b03e Carles Marti
        running, pending/queued and pending+running. When the relevant maximum
222 b6b1b03e Carles Marti
        is reached no jobs more are submitted.
223 2be92b2c Carles Marti
    @param name: name of the project.
224 b461f289 Carles Marti
    """
225 b461f289 Carles Marti
    from shutil import copy
226 b461f289 Carles Marti
    from time import sleep
227 2be92b2c Carles Marti
    from subprocess import PIPE, Popen
228 b461f289 Carles Marti
    subm_jobs = []
229 b461f289 Carles Marti
    init_dir = os.getcwd()
230 b461f289 Carles Marti
    for conf in os.listdir(run_type):
231 b461f289 Carles Marti
        i = conf.split('_')[1]
232 b6b1b03e Carles Marti
        while get_jobs_status(subm_jobs, stat_cmd, stat_dict).count("r") + \
233 b6b1b03e Carles Marti
                get_jobs_status(subm_jobs, stat_cmd, stat_dict).count("p") \
234 b6b1b03e Carles Marti
                >= max_jobs['rp']\
235 b6b1b03e Carles Marti
                or get_jobs_status(subm_jobs, stat_cmd, stat_dict).count("r") \
236 b6b1b03e Carles Marti
                >= max_jobs['r'] \
237 b6b1b03e Carles Marti
                or get_jobs_status(subm_jobs, stat_cmd, stat_dict).count("p") \
238 b6b1b03e Carles Marti
                >= max_jobs['p']:
239 b461f289 Carles Marti
            sleep(30)
240 b461f289 Carles Marti
        copy(sub_script, f"{run_type}/{conf}")
241 b461f289 Carles Marti
        os.chdir(f"{run_type}/{conf}")
242 ca309a2c Carles Marti
        job_name = f'{name[:5]}{run_type[:3].capitalize()}{i}'
243 2be92b2c Carles Marti
        sub_order = sub_cmd % (job_name, sub_script)
244 2be92b2c Carles Marti
        subm_msg = Popen(sub_order, shell=True, stdout=PIPE).communicate()[0]
245 2be92b2c Carles Marti
        job_id = None
246 2be92b2c Carles Marti
        for word in subm_msg.decode("utf-8").split():
247 2be92b2c Carles Marti
            try:
248 118974d5 Carles Marti
                job_id = int(word.replace('>', '').replace('<', ''))
249 2be92b2c Carles Marti
                break
250 2be92b2c Carles Marti
            except ValueError:
251 2be92b2c Carles Marti
                continue
252 2be92b2c Carles Marti
        subm_jobs.append(job_id)
253 b461f289 Carles Marti
        os.chdir(init_dir)
254 b461f289 Carles Marti
255 b461f289 Carles Marti
    logger.info('All jobs have been submitted, waiting for them to finish.')
256 2be92b2c Carles Marti
    while not all([stat == 'f' for stat in
257 2be92b2c Carles Marti
                   get_jobs_status(subm_jobs, stat_cmd, stat_dict)]):
258 b461f289 Carles Marti
        sleep(30)
259 b461f289 Carles Marti
    logger.info('All jobs have finished.')
260 12001182 Carles
261 12001182 Carles
262 f3004731 Carles
def run_calc(run_type, inp_vars, atms_list):
263 3d6a9d3c Carles
    """Directs the calculation run according to the provided arguments.
264 3d6a9d3c Carles

265 f3004731 Carles
    @param run_type: Type of calculation. 'isolated', 'screening' or
266 f3004731 Carles
    'refinement'
267 3d6a9d3c Carles
    @param inp_vars: Calculation parameters from input file.
268 f3004731 Carles
    @param atms_list: List of ase.Atoms objects containing the sets of atoms
269 f3004731 Carles
    aimed to run the calculations of.
270 3d6a9d3c Carles
    """
271 821dca42 Carles Marti
    from modules.utilities import check_bak
272 14e0b660 Carles Martí
273 3d6a9d3c Carles
    run_types = ['isolated', 'screening', 'refinement']
274 3d6a9d3c Carles
    if not isinstance(run_type, str) or run_type.lower() not in run_types:
275 1e36f905 Carles Marti
        run_type_err = f"'run_type' must be one of the following: {run_types}"
276 3d6a9d3c Carles
        logger.error(run_type_err)
277 3d6a9d3c Carles
        raise ValueError(run_type_err)
278 3d6a9d3c Carles
279 821dca42 Carles Marti
    if inp_vars['batch_q_sys']:
280 821dca42 Carles Marti
        logger.info(f"Running {run_type} calculation with {inp_vars['code']} on"
281 695dcff8 Carles Marti
                    f" {inp_vars['batch_q_sys']}.")
282 821dca42 Carles Marti
    else:
283 695dcff8 Carles Marti
        logger.info(f"Doing a dry run of {run_type}.")
284 c3cb279a Carles
    check_bak(run_type)
285 c3cb279a Carles
    os.mkdir(run_type)
286 1e36f905 Carles Marti
287 1e36f905 Carles Marti
    # Prepare directories and files for relevant code.
288 07edc24f Carles Marti
    input_files = {'isolated': 'isol_inp_file', 'screening': 'screen_inp_file',
289 07edc24f Carles Marti
                   'refinement': 'refine_inp_file', }
290 1b54d787 Carles Marti
    if inp_vars['code'] == 'cp2k':
291 07edc24f Carles Marti
        prep_cp2k(inp_vars[input_files[run_type]], run_type, atms_list,
292 07edc24f Carles Marti
                  inp_vars['project_name'])
293 9d3b680c Carles Martí
    elif inp_vars['code'] == "vasp":
294 9d3b680c Carles Martí
        prep_vasp(inp_vars[input_files[run_type]], run_type, atms_list,
295 9d3b680c Carles Martí
                  inp_vars['project_name'], inp_vars['pbc_cell'])
296 a5f73b4c Carles
    # elif: inp_vars['code'] == 'Other codes here'
297 99afde40 Carles
298 1e36f905 Carles Marti
    # Submit/run Jobs
299 12001182 Carles
    if inp_vars['batch_q_sys'] == 'sge':
300 2be92b2c Carles Marti
        stat_cmd = "qstat | grep %s | awk '{print $5}'"
301 2be92b2c Carles Marti
        stat_dict = {'r': 'r', 'p': 'qw', 'f': ''}
302 2be92b2c Carles Marti
        submit_jobs(run_type, 'qsub -N %s %s', inp_vars['subm_script'],
303 09c3325a Carles Marti
                    stat_cmd, stat_dict, inp_vars['max_jobs'],
304 b6b1b03e Carles Marti
                    inp_vars['project_name'])
305 2be92b2c Carles Marti
    elif inp_vars['batch_q_sys'] == 'lsf':
306 118974d5 Carles Marti
        stat_cmd = "bjobs -w | grep %s | awk '{print $3}'"
307 118974d5 Carles Marti
        stat_dict = {'r': 'RUN', 'p': 'PEND', 'f': ''}
308 118974d5 Carles Marti
        submit_jobs(run_type, 'bsub -J %s < %s', inp_vars['subm_script'],
309 09c3325a Carles Marti
                    stat_cmd, stat_dict, inp_vars['max_jobs'],
310 b6b1b03e Carles Marti
                    inp_vars['project_name'])
311 ec5bba46 Carles Marti
    elif inp_vars['batch_q_sys'] == 'irene':
312 ec5bba46 Carles Marti
        stat_cmd = "ccc_mstat | grep %s | awk '{print $10}' | cut -c1"
313 ec5bba46 Carles Marti
        stat_dict = {'r': 'R', 'p': 'P', 'f': ''}
314 ec5bba46 Carles Marti
        submit_jobs(run_type, 'ccc_msub -r %s %s', inp_vars['subm_script'],
315 ec5bba46 Carles Marti
                    stat_cmd, stat_dict, inp_vars['max_jobs'],
316 b6b1b03e Carles Marti
                    inp_vars['project_name'])
317 ec5bba46 Carles Marti
318 1a1164e0 Carles Marti
    elif inp_vars['batch_q_sys'] == 'local':
319 1a1164e0 Carles Marti
        pass  # TODO implement local
320 1a1164e0 Carles Marti
    elif not inp_vars['batch_q_sys']:
321 0db30d07 Carles
        pass
322 14e0b660 Carles Martí
    else:
323 14e0b660 Carles Martí
        err_msg = "Unknown value for 'batch_q_sys'."
324 14e0b660 Carles Martí
        logger.error(err_msg)
325 14e0b660 Carles Martí
        raise ValueError(err_msg)