Salome HOME
Handling gmsh global parameter for a gmsh ParallelMesh
[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 from os import environ, path
26 import sys
27 import subprocess as sp
28
29 from argparse import ArgumentParser
30 import pydefx
31 import pylauncher
32
33 MESHER_HANDLED = ["NETGEN3D","NETGEN2D","NETGEN1D","NETGEN1D2D","NETGEN1D2D","GMSH3D"]
34
35 CMD_TEMPLATE = \
36 """{runner} {mesher} {mesh_file} {shape_file} {param_file} {elem_orientation_file} {new_element_file} {output_mesh_file} > {log_file} 2>&1"""
37
38 PYTHON_CODE = \
39 """
40 import subprocess as sp
41 def _exec(cmd):
42     error_code = -1
43     try:
44         output = sp.check_output(cmd, shell=True)
45         error_code = 0
46     except sp.CalledProcessError as e:
47         print('Code crash')
48         print(e.output)
49         error_code = e.returncode
50         raise e
51     return error_code
52 """
53
54 def create_launcher():
55     """ Initialise pylauncher
56     """
57     launcher = pylauncher.Launcher_cpp()
58     launcher.SetResourcesManager(create_resources_manager())
59     return launcher
60
61 def create_resources_manager():
62     """ Look for the catalog file and create a ressource manager with it """
63     # localhost is defined anyway, even if the catalog file does not exist.
64     catalog_path = environ.get("USER_CATALOG_RESOURCES_FILE", "")
65     if not path.isfile(catalog_path):
66         salome_path = environ.get("ROOT_SALOME_INSTALL", "")
67         catalog_path = path.join(salome_path, "CatalogResources.xml")
68     if not path.isfile(catalog_path):
69         catalog_path = ""
70
71     return pylauncher.ResourcesManager_cpp(catalog_path)
72
73 def create_job_parameters():
74     """ Initialsie JobParameters """
75     jparam = pylauncher.JobParameters_cpp()
76     jparam.resource_required = create_resource_parameters()
77     return jparam
78
79 def create_resource_parameters():
80     """ Init resourceParams """
81     return pylauncher.resourceParams()
82
83 def get_runner(mesher):
84     """
85     Get path to exe for mesher
86
87     Arguments:
88     mesher: Name of the mesher (NETGEN2D/NETGEN3D...)
89
90     retuns (string) Path to the exe
91     """
92     if sys.platform.startswith('win'):
93         ext = ".exe"
94     else:
95         ext = ""
96
97     if mesher in ['NETGEN3D','NETGEN2D','NETGEN1D','NETGEN1D2D','NETGEN1D2D']:
98         exe_path = path.join("${NETGENPLUGIN_ROOT_DIR}",
99                              "bin",
100                              "salome",
101                              "NETGENPlugin_Runner"+ext)
102     elif mesher in ['GMSH3D']:
103         exe_path = path.join("${GMSHPLUGIN_ROOT_DIR}",
104                              "bin",
105                              "salome",
106                              "GMSHPlugin_Runner"+ext)
107     else:
108         raise Exception("Mesher {mesher} is not handled".format(mesher=mesher))
109
110     return exe_path
111
112 def run_local(args):
113     """ Simple Local run """
114     cmd = CMD_TEMPLATE.format(\
115         runner=get_runner(args.mesher),
116         mesher=args.mesher,
117         mesh_file=args.input_mesh_file,
118         shape_file=args.shape_file,
119         param_file=args.hypo_file,
120         elem_orientation_file=args.elem_orient_file,
121         new_element_file=args.new_element_file,
122         log_file=path.join(path.dirname(args.shape_file), "run.log"),
123         output_mesh_file=args.output_mesh_file)
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     job_params.maximum_duration = args.walltime
161
162     # job_params.pre_command = pre_command # command to run on frontal
163     # script to run in batch mode
164     run_script = path.join(path.dirname(args.shape_file), "run.sh")
165     with open(run_script, "w") as f:
166         f.write("#!/bin/bash\n")
167         f.write(cmd)
168     job_params.job_file = run_script
169
170     local_dir = path.dirname(args.shape_file)
171
172     # files to copy to remote working dir
173     # Directories are copied recursively.
174     # job_file script is automaticaly copied.
175     job_params.in_files = [args.shape_file,
176                            args.hypo_file,
177                            args.elem_orient_file]
178
179     print("in_files", job_params.in_files)
180     # local path for in_files
181     job_params.local_directory = local_dir
182     # result files you want to bring back with getJobResults
183     # TODO: replace run.log by argument ? by path
184     out_files = ["run.log"]
185     if args.new_element_file != "NONE":
186         out_files.append(path.relpath(args.new_element_file, local_dir))
187     if args.output_mesh_file != "NONE":
188         out_files.append(path.relpath(args.output_mesh_file, local_dir))
189     job_params.out_files = out_files
190     print("out_files", job_params.out_files)
191     # local path where to copy out_files
192     job_params.result_directory = local_dir
193
194     job_params.job_name = "SMESH_parallel"
195     job_params.resource_required.name = args.resource
196
197     # Extra parameters
198     # String that is directly added to the job submission file
199     # job_params.extra_params = "#SBATCH --nodes=2"
200
201     # remote job directory
202     # Retrieve working dir from catalog
203     res_manager = create_resources_manager()
204     res_params = res_manager.GetResourceDefinition(args.resource)
205     job_params.work_directory = path.join(\
206             res_params.working_directory,
207             path.basename(path.dirname(path.dirname(args.shape_file))),
208             path.basename(path.dirname(args.shape_file)))
209     print("work directory", job_params.work_directory)
210
211     job_id = launcher.createJob(job_params) #SALOME id of the job
212     launcher.launchJob(job_id) # copy files, run pre_command, submit job
213
214     # wait for the end of the job
215     job_state = launcher.getJobState(job_id)
216     print("Job %d state: %s" % (job_id, job_state))
217     while job_state not in ["FINISHED", "FAILED"]:
218         time.sleep(3)
219         job_state = launcher.getJobState(job_id)
220
221     if job_state == "FAILED":
222         raise Exception("Job failed")
223     else:
224         # verify the return code of the execution
225         if(launcher.getJobWorkFile(job_id,
226                                    "logs/exit_code.log",
227                                    job_params.result_directory)):
228             exit_code_file = path.join(job_params.result_directory,
229                                        "exit_code.log")
230             exit_code = ""
231             if path.isfile(exit_code_file):
232                 with open(exit_code_file) as myfile:
233                     exit_code = myfile.read()
234                     exit_code = exit_code.strip()
235             if exit_code != "0":
236                 raise Exception(\
237                     "An error occured during the execution of the job.")
238         else:
239             raise Exception("Failed to get the exit code of the job.")
240
241     # Retrieve result files
242     launcher.getJobResults(job_id, "")
243
244     # Delete remote working dir
245     del_tmp_folder = True
246     try:
247        val = int(environ.get("SMESH_KEEP_TMP", "0"))
248        del_tmp_folder = val < 0
249     except Exception as e:
250         del_tmp_folder = True
251
252     if del_tmp_folder:
253         launcher.clearJobWorkingDir(job_id)
254
255 def def_arg():
256     """ Define and parse arguments for the script """
257     parser = ArgumentParser()
258     parser.add_argument("mesher",
259                         choices=MESHER_HANDLED,
260                         help="mesher to use from ("+",".join(MESHER_HANDLED)+")")
261     parser.add_argument("input_mesh_file",\
262         help="MED File containing lower-dimension-elements already meshed")
263     parser.add_argument("shape_file",
264                         help="STEP file containing the shape to mesh")
265     parser.add_argument("hypo_file",
266                         help="Ascii file containint the list of parameters")
267     parser.add_argument("--elem-orient-file",\
268         help="binary file containing the list of elements from "\
269              "INPUT_MESH_FILE associated to the shape and their orientation")
270     # Output file parameters
271     output = parser.add_argument_group("Output files", "Possible output files")
272     output.add_argument("--new-element-file",
273                         default="NONE",
274                         help="contains elements and nodes added by the meshing")
275     output.add_argument(\
276         "--output-mesh-file",
277         default="NONE",
278         help="MED File containing the mesh after the run of the mesher")
279
280     # Run parameters
281     run_param = parser.add_argument_group(\
282             "Run parameters",
283             "Parameters for the run of the mesher")
284     run_param.add_argument("--method",
285                            default="local",
286                            choices=["local", "cluster"],
287                            help="Running method (default: local)")
288
289     run_param.add_argument("--resource",
290                            help="resource from SALOME Catalog")
291     run_param.add_argument("--nb-proc",
292                            default=1,
293                            type=int,
294                            help="Number of processors")
295     run_param.add_argument("--nb-proc-per-node",
296                            default=1,
297                            type=int,
298                            help="Number of processeor per node")
299     run_param.add_argument("--nb-node",
300                            default=1,
301                            type=int,
302                            help="Number of node")
303     run_param.add_argument("--walltime",
304                            default="01:00:00",
305                            help="walltime for job submission HH:MM:SS (default 01:00:00)")
306     run_param.add_argument("--wc-key",
307                            default="P11N0:SALOME",
308                            help="wc-key for job submission (default P11N0:SALOME)")
309
310     args = parser.parse_args()
311
312     return args
313
314 def main():
315     """ Main function """
316     args = def_arg()
317     if args.method == "local":
318         run_local(args)
319     elif args.method == "cluster":
320         run_pylauncher(args)
321     else:
322         raise Exception("Unknown method {}".format(args.method))
323
324 if __name__ == "__main__":
325     main()