Salome HOME
bos #37027 [EDF] Define more general geomTol value to detect mesh edges in geom edges...
[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"]
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']:
98         exe_path = path.join("${NETGENPLUGIN_ROOT_DIR}",
99                              "bin",
100                              "salome",
101                              "NETGENPlugin_Runner"+ext)
102     else:
103         raise Exception("Mesher {mesher} is not handled".format(mesher=mesher))
104
105     return exe_path
106
107 def run_local(args):
108     """ Simple Local run """
109     print("Local run")
110     #TODO: Check on how to handle log for windows (through sp.check_output)
111     cmd = CMD_TEMPLATE.format(\
112         runner=get_runner(args.mesher),
113         mesher=args.mesher,
114         mesh_file=args.input_mesh_file,
115         shape_file=args.shape_file,
116         param_file=args.hypo_file,
117         elem_orientation_file=args.elem_orient_file,
118         new_element_file=args.new_element_file,
119         log_file=path.join(path.dirname(args.shape_file), "run.log"),
120         output_mesh_file=args.output_mesh_file)
121     print("Executing:")
122     print(cmd)
123     sp.check_output(cmd, shell=True, cwd=path.dirname(args.shape_file))
124
125 def run_pylauncher(args):
126     """ Run exe throuhg pylauncher """
127     import time
128     print("Cluster run")
129
130     cmd = CMD_TEMPLATE.format(\
131         runner=get_runner(args.mesher),
132         mesher=args.mesher,
133         mesh_file="../"+path.basename(args.input_mesh_file),
134         shape_file=path.basename(args.shape_file),
135         param_file=path.basename(args.hypo_file),
136         elem_orientation_file=path.basename(args.elem_orient_file),
137         new_element_file=path.basename(args.new_element_file),
138         log_file="run.log",
139         output_mesh_file=path.basename(args.output_mesh_file))
140
141     print("Cmd: ", cmd)
142
143     # salome launcher
144     launcher = create_launcher()
145
146     # See SALOME_Launcher documentation for parameters
147     job_params = create_job_parameters()
148     # different type are:
149     # command Shell out of salome session
150     # command_salome Shell in salome shell
151     # python_salome Python script
152     # yacs_file
153     job_params.job_type = "command_salome" # creates CatalogResources.xml
154
155     job_params.wckey = args.wc_key
156     job_params.resource_required.nb_proc = args.nb_proc
157     job_params.resource_required.nb_proc_per_node = args.nb_proc_per_node
158     job_params.resource_required.nb_node = args.nb_node
159     job_params.maximum_duration = args.walltime
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     del_tmp_folder = True
245     try:
246        val = int(environ.get("SMESH_KEEP_TMP", "0"))
247        del_tmp_folder = val > 0
248     except Exception as e:
249         del_tmp_folder = True
250
251     launcher.clearJobWorkingDir(job_id)
252
253 def def_arg():
254     """ Define and parse arguments for the script """
255     parser = ArgumentParser()
256     parser.add_argument("mesher",
257                         choices=MESHER_HANDLED,
258                         help="mesher to use from ("+",".join(MESHER_HANDLED)+")")
259     parser.add_argument("input_mesh_file",\
260         help="MED File containing lower-dimension-elements already meshed")
261     parser.add_argument("shape_file",
262                         help="STEP file containing the shape to mesh")
263     parser.add_argument("hypo_file",
264                         help="Ascii file containint the list of parameters")
265     parser.add_argument("--elem-orient-file",\
266         help="binary file containing the list of elements from "\
267              "INPUT_MESH_FILE associated to the shape and their orientation")
268     # Output file parameters
269     output = parser.add_argument_group("Output files", "Possible output files")
270     output.add_argument("--new-element-file",
271                         default="NONE",
272                         help="contains elements and nodes added by the meshing")
273     output.add_argument(\
274         "--output-mesh-file",
275         default="NONE",
276         help="MED File containing the mesh after the run of the mesher")
277
278     # Run parameters
279     run_param = parser.add_argument_group(\
280             "Run parameters",
281             "Parameters for the run of the mesher")
282     run_param.add_argument("--method",
283                            default="local",
284                            choices=["local", "cluster"],
285                            help="Running method (default: local)")
286
287     run_param.add_argument("--resource",
288                            help="resource from SALOME Catalog")
289     run_param.add_argument("--nb-proc",
290                            default=1,
291                            type=int,
292                            help="Number of processors")
293     run_param.add_argument("--nb-proc-per-node",
294                            default=1,
295                            type=int,
296                            help="Number of processeor per node")
297     run_param.add_argument("--nb-node",
298                            default=1,
299                            type=int,
300                            help="Number of node")
301     run_param.add_argument("--walltime",
302                            default="01:00:00",
303                            help="walltime for job submission HH:MM:SS (default 01:00:00)")
304     run_param.add_argument("--wc-key",
305                            default="P11N0:SALOME",
306                            help="wc-key for job submission (default P11N0:SALOME)")
307
308     args = parser.parse_args()
309
310     return args
311
312 def main():
313     """ Main function """
314     args = def_arg()
315     if args.method == "local":
316         run_local(args)
317     elif args.method == "cluster":
318         run_pylauncher(args)
319     else:
320         raise Exception("Unknown method {}".format(args.method))
321
322 if __name__ == "__main__":
323     main()