]> SALOME platform Git repositories - modules/smesh.git/blob - src/SMESH_SWIG/mesher_launcher.py
Salome HOME
Adding Multinode method for smesh parallelism + cleanup and doc
[modules/smesh.git] / src / SMESH_SWIG / mesher_launcher.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 ## Copyright (C) 2021-2023  CEA/DEN, EDF R&D, OPEN CASCADE
4 ##
5 ## This library is free software; you can redistribute it and/or
6 ## modify it under the terms of the GNU Lesser General Public
7 ## License as published by the Free Software Foundation; either
8 ## version 2.1 of the License, or (at your option) any later version.
9 ##
10 ## This library is distributed in the hope that it will be useful,
11 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 ## Lesser General Public License for more details.
14 ##
15 ## You should have received a copy of the GNU Lesser General Public
16 ## License along with this library; if not, write to the Free Software
17 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 ##
19 ## See http://www.salome-platform.org/ or email :
20 ##     webmaster.salome@opencascade.com
21 ##
22 """
23 File to run mesher from command line
24 """
25 #TODO: Make the execution path independant (output files are written in current directory)
26 from os import environ, path
27 import sys
28 import subprocess as sp
29
30 from argparse import ArgumentParser
31 import pydefx
32 import pylauncher
33
34 MESHER_HANDLED = ["NETGEN3D"]
35
36 CMD_TEMPLATE = \
37 """{runner} {mesher} {mesh_file} {shape_file} {param_file} {elem_orientation_file} {new_element_file} {output_mesh_file} > {log_file} 2>&1"""
38
39 PYTHON_CODE = \
40 """
41 import subprocess as sp
42 def _exec(cmd):
43     error_code = -1
44     try:
45         output = sp.check_output(cmd, shell=True)
46         error_code = 0
47     except sp.CalledProcessError as e:
48         print('Code crash')
49         print(e.output)
50         error_code = e.returncode
51         raise e
52     return error_code
53 """
54
55 def create_launcher():
56     """ Initialise pylauncher
57     """
58     launcher = pylauncher.Launcher_cpp()
59     launcher.SetResourcesManager(create_resources_manager())
60     return launcher
61
62 def create_resources_manager():
63     """ Look for the catalog file and create a ressource manager with it """
64     # localhost is defined anyway, even if the catalog file does not exist.
65     catalog_path = environ.get("USER_CATALOG_RESOURCES_FILE", "")
66     if not path.isfile(catalog_path):
67         salome_path = environ.get("ROOT_SALOME_INSTALL", "")
68         catalog_path = path.join(salome_path, "CatalogResources.xml")
69     if not path.isfile(catalog_path):
70         catalog_path = ""
71
72     return pylauncher.ResourcesManager_cpp(catalog_path)
73
74 def create_job_parameters():
75     """ Initialsie JobParameters """
76     jparam = pylauncher.JobParameters_cpp()
77     jparam.resource_required = create_resource_parameters()
78     return jparam
79
80 def create_resource_parameters():
81     """ Init resourceParams """
82     return pylauncher.resourceParams()
83
84 def get_runner(mesher):
85     """
86     Get path to exe for mesher
87
88     Arguments:
89     mesher: Name of the mesher (NETGEN2D/NETGEN3D...)
90
91     retuns (string) Path to the exe
92     """
93     if sys.platform.startswith('win'):
94         ext = ".exe"
95     else:
96         ext = ""
97
98     if mesher in ['NETGEN3D']:
99         exe_path = path.join("${NETGENPLUGIN_ROOT_DIR}",
100                              "bin",
101                              "salome",
102                              "NETGENPlugin_Runner"+ext)
103     else:
104         raise Exception("Mesher {mesher} is not handled".format(mesher=mesher))
105
106     return exe_path
107
108 def run_local(args):
109     """ Simple Local run """
110     print("Local run")
111     #TODO: Check on how to handle log for windows (through sp.check_output)
112     cmd = CMD_TEMPLATE.format(\
113         runner=get_runner(args.mesher),
114         mesher=args.mesher,
115         mesh_file=args.input_mesh_file,
116         shape_file=args.shape_file,
117         param_file=args.hypo_file,
118         elem_orientation_file=args.elem_orient_file,
119         new_element_file=args.new_element_file,
120         log_file=path.join(path.dirname(args.shape_file), "run.log"),
121         output_mesh_file=args.output_mesh_file)
122     print("Executing:")
123     print(cmd)
124     sp.check_output(cmd, shell=True, cwd=path.dirname(args.shape_file))
125
126 def run_pylauncher(args):
127     """ Run exe throuhg pylauncher """
128     import time
129     print("Cluster run")
130
131     cmd = CMD_TEMPLATE.format(\
132         runner=get_runner(args.mesher),
133         mesher=args.mesher,
134         mesh_file="../"+path.basename(args.input_mesh_file),
135         shape_file=path.basename(args.shape_file),
136         param_file=path.basename(args.hypo_file),
137         elem_orientation_file=path.basename(args.elem_orient_file),
138         new_element_file=path.basename(args.new_element_file),
139         log_file="run.log",
140         output_mesh_file=path.basename(args.output_mesh_file))
141
142     print("Cmd: ", cmd)
143
144     # salome launcher
145     launcher = create_launcher()
146
147     # See SALOME_Launcher documentation for parameters
148     job_params = create_job_parameters()
149     # different type are:
150     # command Shell out of salome session
151     # command_salome Shell in salome shell
152     # python_salome Python script
153     # yacs_file
154     job_params.job_type = "command_salome" # creates CatalogResources.xml
155
156     job_params.wckey = args.wc_key
157     job_params.resource_required.nb_proc = args.nb_proc
158     job_params.resource_required.nb_proc_per_node = args.nb_proc_per_node
159     job_params.resource_required.nb_node = args.nb_node
160
161     # job_params.pre_command = pre_command # command to run on frontal
162     # script to run in batch mode
163     run_script = path.join(path.dirname(args.shape_file), "run.sh")
164     with open(run_script, "w") as f:
165         f.write("#!/bin/bash\n")
166         f.write(cmd)
167     job_params.job_file = run_script
168
169     local_dir = path.dirname(args.shape_file)
170
171     # files to copy to remote working dir
172     # Directories are copied recursively.
173     # job_file script is automaticaly copied.
174     job_params.in_files = [args.shape_file,
175                            args.hypo_file,
176                            args.elem_orient_file]
177
178     print("in_files", job_params.in_files)
179     # local path for in_files
180     job_params.local_directory = local_dir
181     # result files you want to bring back with getJobResults
182     # TODO: replace run.log by argument ? by path
183     out_files = ["run.log"]
184     if args.new_element_file != "NONE":
185         out_files.append(path.relpath(args.new_element_file, local_dir))
186     if args.output_mesh_file != "NONE":
187         out_files.append(path.relpath(args.output_mesh_file, local_dir))
188     job_params.out_files = out_files
189     print("out_files", job_params.out_files)
190     # local path where to copy out_files
191     job_params.result_directory = local_dir
192
193     job_params.job_name = "SMESH_parallel"
194     job_params.resource_required.name = args.resource
195
196     # Extra parameters
197     # String that is directly added to the job submission file
198     # job_params.extra_params = "#SBATCH --nodes=2"
199
200     # remote job directory
201     # Retrieve working dir from catalog
202     res_manager = create_resources_manager()
203     res_params = res_manager.GetResourceDefinition(args.resource)
204     job_params.work_directory = path.join(\
205             res_params.working_directory,
206             path.basename(path.dirname(path.dirname(args.shape_file))),
207             path.basename(path.dirname(args.shape_file)))
208     print("work directory", job_params.work_directory)
209
210     job_id = launcher.createJob(job_params) #SALOME id of the job
211     launcher.launchJob(job_id) # copy files, run pre_command, submit job
212
213     # wait for the end of the job
214     job_state = launcher.getJobState(job_id)
215     print("Job %d state: %s" % (job_id, job_state))
216     while job_state not in ["FINISHED", "FAILED"]:
217         time.sleep(3)
218         job_state = launcher.getJobState(job_id)
219
220     if job_state == "FAILED":
221         raise Exception("Job failed")
222     else:
223         # verify the return code of the execution
224         if(launcher.getJobWorkFile(job_id,
225                                    "logs/exit_code.log",
226                                    job_params.result_directory)):
227             exit_code_file = path.join(job_params.result_directory,
228                                        "exit_code.log")
229             exit_code = ""
230             if path.isfile(exit_code_file):
231                 with open(exit_code_file) as myfile:
232                     exit_code = myfile.read()
233                     exit_code = exit_code.strip()
234             if exit_code != "0":
235                 raise Exception(\
236                     "An error occured during the execution of the job.")
237         else:
238             raise Exception("Failed to get the exit code of the job.")
239
240     # Retrieve result files
241     launcher.getJobResults(job_id, "")
242
243     # Delete remote working dir
244     launcher.clearJobWorkingDir(job_id)
245
246 def def_arg():
247     """ Define and parse arguments for the script """
248     parser = ArgumentParser()
249     parser.add_argument("mesher",
250                         choices=MESHER_HANDLED,
251                         help="mesher to use from ("+",".join(MESHER_HANDLED)+")")
252     parser.add_argument("input_mesh_file",\
253         help="MED File containing lower-dimension-elements already meshed")
254     parser.add_argument("shape_file",
255                         help="STEP file containing the shape to mesh")
256     parser.add_argument("hypo_file",
257                         help="Ascii file containint the list of parameters")
258     parser.add_argument("--elem-orient-file",\
259         help="binary file containing the list of elements from "\
260              "INPUT_MESH_FILE associated to the shape and their orientation")
261     # Output file parameters
262     output = parser.add_argument_group("Output files", "Possible output files")
263     output.add_argument("--new-element-file",
264                         default="NONE",
265                         help="contains elements and nodes added by the meshing")
266     output.add_argument(\
267         "--output-mesh-file",
268         default="NONE",
269         help="MED File containing the mesh after the run of the mesher")
270
271     # Run parameters
272     run_param = parser.add_argument_group(\
273             "Run parameters",
274             "Parameters for the run of the mesher")
275     run_param.add_argument("--method",
276                            default="local",
277                            choices=["local", "cluster"],
278                            help="Running method (default: local)")
279
280     run_param.add_argument("--resource",
281                            help="resource from SALOME Catalog")
282     run_param.add_argument("--nb-proc",
283                            default=1,
284                            type=int,
285                            help="Number of processors")
286     run_param.add_argument("--nb-proc-per-node",
287                            default=1,
288                            type=int,
289                            help="Number of processeor per node")
290     run_param.add_argument("--nb-node",
291                            default=1,
292                            type=int,
293                            help="Number of node")
294     run_param.add_argument("--wc-key",
295                            default="P11N0:SALOME",
296                            help="wc-key for job submission (default P11N0:SALOME)")
297
298     args = parser.parse_args()
299
300     return args
301
302 def main():
303     """ Main function """
304     args = def_arg()
305     if args.method == "local":
306         run_local(args)
307     elif args.method == "cluster":
308         run_pylauncher(args)
309     else:
310         raise Exception("Unknown method {}".format(args.method))
311
312 if __name__ == "__main__":
313     main()