Salome HOME
[bos #38045][bos #38046] New SA versions of NETGEN and GMSH.
[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     #TODO: Check on how to handle log for windows (through sp.check_output)
115     cmd = CMD_TEMPLATE.format(\
116         runner=get_runner(args.mesher),
117         mesher=args.mesher,
118         mesh_file=args.input_mesh_file,
119         shape_file=args.shape_file,
120         param_file=args.hypo_file,
121         elem_orientation_file=args.elem_orient_file,
122         new_element_file=args.new_element_file,
123         log_file=path.join(path.dirname(args.shape_file), "run.log"),
124         output_mesh_file=args.output_mesh_file)
125     print("Executing:")
126     print(cmd)
127     sp.check_output(cmd, shell=True, cwd=path.dirname(args.shape_file))
128
129 def run_pylauncher(args):
130     """ Run exe throuhg pylauncher """
131     import time
132     print("Cluster run")
133
134     cmd = CMD_TEMPLATE.format(\
135         runner=get_runner(args.mesher),
136         mesher=args.mesher,
137         mesh_file="../"+path.basename(args.input_mesh_file),
138         shape_file=path.basename(args.shape_file),
139         param_file=path.basename(args.hypo_file),
140         elem_orientation_file=path.basename(args.elem_orient_file),
141         new_element_file=path.basename(args.new_element_file),
142         log_file="run.log",
143         output_mesh_file=path.basename(args.output_mesh_file))
144
145     print("Cmd: ", cmd)
146
147     # salome launcher
148     launcher = create_launcher()
149
150     # See SALOME_Launcher documentation for parameters
151     job_params = create_job_parameters()
152     # different type are:
153     # command Shell out of salome session
154     # command_salome Shell in salome shell
155     # python_salome Python script
156     # yacs_file
157     job_params.job_type = "command_salome" # creates CatalogResources.xml
158
159     job_params.wckey = args.wc_key
160     job_params.resource_required.nb_proc = args.nb_proc
161     job_params.resource_required.nb_proc_per_node = args.nb_proc_per_node
162     job_params.resource_required.nb_node = args.nb_node
163     job_params.maximum_duration = args.walltime
164
165     # job_params.pre_command = pre_command # command to run on frontal
166     # script to run in batch mode
167     run_script = path.join(path.dirname(args.shape_file), "run.sh")
168     with open(run_script, "w") as f:
169         f.write("#!/bin/bash\n")
170         f.write(cmd)
171     job_params.job_file = run_script
172
173     local_dir = path.dirname(args.shape_file)
174
175     # files to copy to remote working dir
176     # Directories are copied recursively.
177     # job_file script is automaticaly copied.
178     job_params.in_files = [args.shape_file,
179                            args.hypo_file,
180                            args.elem_orient_file]
181
182     print("in_files", job_params.in_files)
183     # local path for in_files
184     job_params.local_directory = local_dir
185     # result files you want to bring back with getJobResults
186     # TODO: replace run.log by argument ? by path
187     out_files = ["run.log"]
188     if args.new_element_file != "NONE":
189         out_files.append(path.relpath(args.new_element_file, local_dir))
190     if args.output_mesh_file != "NONE":
191         out_files.append(path.relpath(args.output_mesh_file, local_dir))
192     job_params.out_files = out_files
193     print("out_files", job_params.out_files)
194     # local path where to copy out_files
195     job_params.result_directory = local_dir
196
197     job_params.job_name = "SMESH_parallel"
198     job_params.resource_required.name = args.resource
199
200     # Extra parameters
201     # String that is directly added to the job submission file
202     # job_params.extra_params = "#SBATCH --nodes=2"
203
204     # remote job directory
205     # Retrieve working dir from catalog
206     res_manager = create_resources_manager()
207     res_params = res_manager.GetResourceDefinition(args.resource)
208     job_params.work_directory = path.join(\
209             res_params.working_directory,
210             path.basename(path.dirname(path.dirname(args.shape_file))),
211             path.basename(path.dirname(args.shape_file)))
212     print("work directory", job_params.work_directory)
213
214     job_id = launcher.createJob(job_params) #SALOME id of the job
215     launcher.launchJob(job_id) # copy files, run pre_command, submit job
216
217     # wait for the end of the job
218     job_state = launcher.getJobState(job_id)
219     print("Job %d state: %s" % (job_id, job_state))
220     while job_state not in ["FINISHED", "FAILED"]:
221         time.sleep(3)
222         job_state = launcher.getJobState(job_id)
223
224     if job_state == "FAILED":
225         raise Exception("Job failed")
226     else:
227         # verify the return code of the execution
228         if(launcher.getJobWorkFile(job_id,
229                                    "logs/exit_code.log",
230                                    job_params.result_directory)):
231             exit_code_file = path.join(job_params.result_directory,
232                                        "exit_code.log")
233             exit_code = ""
234             if path.isfile(exit_code_file):
235                 with open(exit_code_file) as myfile:
236                     exit_code = myfile.read()
237                     exit_code = exit_code.strip()
238             if exit_code != "0":
239                 raise Exception(\
240                     "An error occured during the execution of the job.")
241         else:
242             raise Exception("Failed to get the exit code of the job.")
243
244     # Retrieve result files
245     launcher.getJobResults(job_id, "")
246
247     # Delete remote working dir
248     del_tmp_folder = True
249     try:
250        val = int(environ.get("SMESH_KEEP_TMP", "0"))
251        del_tmp_folder = val < 0
252     except Exception as e:
253         del_tmp_folder = True
254
255     if del_tmp_folder:
256         launcher.clearJobWorkingDir(job_id)
257
258 def def_arg():
259     """ Define and parse arguments for the script """
260     parser = ArgumentParser()
261     parser.add_argument("mesher",
262                         choices=MESHER_HANDLED,
263                         help="mesher to use from ("+",".join(MESHER_HANDLED)+")")
264     parser.add_argument("input_mesh_file",\
265         help="MED File containing lower-dimension-elements already meshed")
266     parser.add_argument("shape_file",
267                         help="STEP file containing the shape to mesh")
268     parser.add_argument("hypo_file",
269                         help="Ascii file containint the list of parameters")
270     parser.add_argument("--elem-orient-file",\
271         help="binary file containing the list of elements from "\
272              "INPUT_MESH_FILE associated to the shape and their orientation")
273     # Output file parameters
274     output = parser.add_argument_group("Output files", "Possible output files")
275     output.add_argument("--new-element-file",
276                         default="NONE",
277                         help="contains elements and nodes added by the meshing")
278     output.add_argument(\
279         "--output-mesh-file",
280         default="NONE",
281         help="MED File containing the mesh after the run of the mesher")
282
283     # Run parameters
284     run_param = parser.add_argument_group(\
285             "Run parameters",
286             "Parameters for the run of the mesher")
287     run_param.add_argument("--method",
288                            default="local",
289                            choices=["local", "cluster"],
290                            help="Running method (default: local)")
291
292     run_param.add_argument("--resource",
293                            help="resource from SALOME Catalog")
294     run_param.add_argument("--nb-proc",
295                            default=1,
296                            type=int,
297                            help="Number of processors")
298     run_param.add_argument("--nb-proc-per-node",
299                            default=1,
300                            type=int,
301                            help="Number of processeor per node")
302     run_param.add_argument("--nb-node",
303                            default=1,
304                            type=int,
305                            help="Number of node")
306     run_param.add_argument("--walltime",
307                            default="01:00:00",
308                            help="walltime for job submission HH:MM:SS (default 01:00:00)")
309     run_param.add_argument("--wc-key",
310                            default="P11N0:SALOME",
311                            help="wc-key for job submission (default P11N0:SALOME)")
312
313     args = parser.parse_args()
314
315     return args
316
317 def main():
318     """ Main function """
319     args = def_arg()
320     if args.method == "local":
321         run_local(args)
322     elif args.method == "cluster":
323         run_pylauncher(args)
324     else:
325         raise Exception("Unknown method {}".format(args.method))
326
327 if __name__ == "__main__":
328     main()