Salome HOME
Increment version: 9.8.0
[plugins/ghs3dprlplugin.git] / bin / mg-tetra_hpc.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3
4 # Copyright (C) 2008-2021  CEA/DEN, EDF R&D
5 #
6 # This library is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public
8 # License as published by the Free Software Foundation; either
9 # version 2.1 of the License, or (at your option) any later version.
10 #
11 # This library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # Lesser General Public License for more details.
15 #
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with this library; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 #
20 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 #
22
23
24 """
25 run mg_tetra_hpc.exe or mg_tetra_hpc_mpi.exe
26
27 example linux usage:
28 | ./mg-tetra_hpc.py --help
29 |  ./mg-tetra_hpc.py -n 3 --in /tmp/GHS3DPRL.mesh --out /tmp/GHS3DPRL_out.mesh --gradation 1.05 --min_size 0.001 --max_size 1.1 --multithread no > /tmp/tetrahpc.log
30
31 note:
32 |  openmpi environment have to be set, may be as user choice
33 |  example for centos7/redhat7:
34 |    module load mpi/openmpi-x86_64
35 |    which mpicc
36 |  example without module load (old):
37 |    # may be prefer set before salome launch
38 |    OPENMPI_INSTALL_DIR = ".../openmpi-1.8.4/FROM_nothing/bin"
39 |    export PATH=${OPENMPI_INSTALL_DIR}:${PATH}
40 """
41
42 import os
43 import platform
44 import sys
45 import time
46
47 import argparse as AP
48 import multiprocessing as MP  # cpu_count
49 import pprint as PP  # pretty print
50 import subprocess as SP  # Popen
51
52
53 debug = False
54
55 OK = "OK"
56 KO = "KO"
57 OKSYS = 0  # for linux
58 KOSYS = 1  # for linux
59
60 NB_PROCS = MP.cpu_count()    # current cpu number of proc
61 NAME_OS = platform.system()  # 'Linux' or 'Windows'
62
63
64 ##########################################################################
65 # utilities
66 ##########################################################################
67
68
69 ##########################################################################
70 def log_debug(msg):
71   if args.verbose: 
72     log_print("DEBUG", msg)
73
74 def log_info(msg):
75   log_print("INFO", msg)
76
77 def log_warning(msg):
78   log_print("WARNING", msg)
79
80 def log_error(msg):
81   log_print("ERROR", msg)
82
83 def log_print(level, msg):
84   print("%-7s : %s" % (level, indent(msg)))
85
86 def indent(msg, ind='\n | '):
87   if '\n' in msg:
88     return msg.replace('\n', ind)
89   else:
90     return msg
91
92 def test_log(msg):
93   log_debug(msg)
94   log_info(msg)
95   log_warning(msg)
96   log_error(msg)
97
98
99 ##########################################################################
100 def okToSys(aResult, verbose=False):
101   """
102   to get windows or linux result of script
103   """
104
105   def extendList(alist):
106     """utility extend list of lists of string results with ok or KO"""
107     # bad: list(itertools.chain.from_list(alist)) iterate on str
108     res = []
109     if type(alist) != list:
110       return [alist]
111     else:
112       for i in alist:
113         if type(i) == str:
114            res.append(i)
115         else:
116            res.extend(extendList(i))
117     return res
118
119   resList = extendList(aResult)
120   if resList == []:
121     log_error("result no clear with empty list of OK/KO")
122     return KOSYS
123
124   rr = OK
125   for ri in resList:
126     if ri[0:2].upper() != OK:
127       log_debug("KO in '%s'" % ri)
128       rr = KO
129
130   log_info("result is %s" % rr)
131   if rr == OK:
132     return OKSYS
133   else:
134     return KOSYS
135
136
137 ##########################################################################
138 def getDirAndName(datafile):
139   """
140   returns (directory_realpath, os.getcwd(), name_file)
141   """
142   rootpath = os.getcwd()
143   path, namefile = os.path.split(os.path.realpath(datafile))
144   return (path, rootpath, namefile)
145
146
147 ##########################################################################
148 class ArgRange(object):
149   """
150   ArgumentParser utility for range float or in in arguments
151   """
152   def __init__(self, start, end):
153     self.start = start
154     self.end = end
155
156   def __eq__(self, other):
157     return self.start <= other <= self.end
158
159   def __repr__(self):
160     return "[%s,%s]" % (self.start, self.end)
161
162 ##########################################################################
163 def exec_command(cmd, verbose=False):
164   """Exec ONE command with popen"""
165
166   time.sleep(3)  # wait for (MPI) flush files
167   log_debug("launch subprocess:\n%s" % cmd)
168   try:
169     pipe = SP.Popen(cmd, shell=True, stdout=SP.PIPE, stderr=SP.STDOUT)
170   except Exception as e:
171     log_error("Problem launching subprocess:\n%s" % cmd)
172     result = "KO Problem launching subprocess"
173     return result
174
175   (out, error) = pipe.communicate()
176   pipe.wait()
177
178   log_info("subprocess log:\n%s" % out.decode("utf-8"))
179   time.sleep(3)  # wait for (MPI) flush files
180
181   if pipe.returncode == 0: # return code of last command
182     result = "OK subprocess result (of last command)"
183     log_debug(result)
184   else:
185     log_error("KO subprocess result (of last command): %s" % pipe.returncode)
186     result = "KO for:\n%s" % cmd
187   
188   if error is not None:
189     log_error("KO subprocess stderr:\n%s" % error.decode("utf-8"))
190     result = "KO for:\n%s" % cmd
191
192   return result
193
194 ##########################################################################
195 def force_DISTENE_LICENSE_FILE():
196   """
197   conditionaly overriding/set environ variable DISTENE_LICENSE_FILE,
198   from/if existing FORCE_DISTENE_LICENSE_FILE environ variable
199   (useful when test new version MeshGems with local DISTENE_LICENSE_FILE)
200   
201   example:
202     DISTENE_LICENSE_FILE=Use global envvar: DLIM8VAR
203     DLIM8VAR=dlim8 1:1:29030@132.166.151.49/84c419b8::87af... etc.
204     overriden by
205     FORCE_DISTENE_LICENSE_FILE=/home/$USER/licence_meshgems2.3/dlim8.key
206   """
207   force = os.getenv("FORCE_DISTENE_LICENSE_FILE")
208   if force != None:
209     os.environ["DISTENE_LICENSE_FILE"] = force
210     os.environ["DLIM8VAR"] = "NOTHING"
211
212
213 ##########################################################################
214 def launchMultithread(args):
215   log_info("launch multithread for %s" % NAME_OS)
216
217   if NAME_OS == 'Linux':
218     # --out is ONE file: basename_tetra_hpc.mesh
219     args.outputs = os.path.splitext(args.outputFiles)[0] # use args.__dict__
220     args.currentdir = os.path.splitext(args.outputFiles)[0] # use args.__dict__
221
222     # args.outputMulti = os.path.splitext(args.inputFile)[0] + "_tetra_hpc.mesh"  # only one file if mg-tetra_hpc.exe multithread
223     args.outputMultiAsMpi = os.path.splitext(args.outputFiles)[0] + ".000001.mesh"  # create one output file named as mpi (as only one process)
224
225     cmd = """
226 set -x
227
228 # mpirun mg-tetra_hpc_mpi.exe
229   rm %(outputs)s*   # clean previous case
230   mg-tetra_hpc.exe \
231 --max_number_of_threads %(number)s \
232 --in %(inputFile)s \
233 --out %(outputFiles)s \
234 --gradation %(gradation)s \
235 --max_size %(max_size)s \
236 --min_size %(min_size)s
237   # create force symbolic link as no duplicate multithread output file as MPI output file (as only one process)
238   ln -sf %(outputFiles)s %(outputMultiAsMpi)s
239 """ % args.__dict__ 
240     # args.number, args.inputFile, args.gradation, args.max_size, args.min_size, outputMulti, outputMultiAsMpi, outputs
241   else:
242     msg = "unknown operating system: %s" % NAME_OS
243     log_error(msg)
244     return KO + msg
245
246   result = exec_command(cmd, verbose=True)
247   return result
248
249
250
251 ##########################################################################
252 def launchMpi(args):
253   log_info("launch MPI for %s" % NAME_OS)
254
255   if NAME_OS == 'Linux':
256     args.outputs = os.path.splitext(args.outputFiles)[0] # use args.__dict__
257     args.currentdir = os.path.splitext(args.outputFiles)[0] # use args.__dict__
258     cmd = """
259 set -x
260
261   which mg-tetra_hpc_mpi.exe
262   if [ $? -ne 0 ]  # exit if not found
263   then
264     exit 1
265   fi
266
267   # needed as openmpi environment have to be set, may be as user or root (for cluster) choice
268   which mpirun || module load mpi/openmpi-x86_64 # example for centos7/redhat7
269
270 # compile libmeshgems_mpi.so if needed (i.e. if empty stubs)
271   mg-tetra_hpc_mpi.exe --help
272   if [ $? -ne 0 ] # if empty MPI stubs library, have to compile libmeshgems_mpi.so
273   then
274     # SALOME env var MESHGEMSHOME is something like .../INSTALL/MeshGems
275     MESHGEMSHOME=${MESHGEMSHOME:-/tmp/MeshGems} # default value if unset
276     COMPILDIR=$MESHGEMSHOME/stubs
277     TARGETDIR=$MESHGEMSHOME/lib/Linux_64
278     cd ${COMPILDIR}
279     mpicc meshgems_mpi.c -DMESHGEMS_LINUX_BUILD -I../include -shared -fPIC -o ${TARGETDIR}/libmeshgems_mpi.so
280   fi
281
282 # mpirun mg-tetra_hpc_mpi.exe
283   rm %(outputs)s*   # clean previous case
284   cd %(outputDir)s
285   mpirun --n %(number)s mg-tetra_hpc_mpi.exe \
286 --in %(inputFile)s \
287 --out %(outputFiles)s \
288 --gradation %(gradation)s \
289 --max_size %(max_size)s \
290 --min_size %(min_size)s
291   # ls -alt %(outputs)s*
292 """ % args.__dict__ 
293     # args.number, args.inputFile, args.outputFiles, args.gradation, args.max_size, args.min_size, outputs
294   else:
295     msg = "unknown operating system: %s" % NAME_OS
296     log_error(msg)
297     return KO + msg
298
299   result = exec_command(cmd, verbose=True)
300   return result
301
302
303 ##########################################################################
304 def getParser():
305   parser = AP.ArgumentParser(description='launch tetra_hpc.exe or tetra_hpc_mpi.exe for mesh computation', argument_default=None)
306
307   parser.add_argument(
308     '-v', '--verbose',
309     help='set verbose, for debug',
310     action='store_true',
311   )
312   parser.add_argument(
313     '-t', '--test',
314     help='make test, for script debug',
315     action='store_true',
316   )
317   parser.add_argument(
318     '-n', '--number',
319     help='if multithread: number of threads, else distributed: number of processes MPI',
320     choices=[ArgRange(1, 999999)],
321     type=int,
322     metavar='integer >= 0',
323     default=NB_PROCS # as automatic, no more local number of cpu (multithread or MPI)
324   )
325   parser.add_argument(
326     '-m', '--multithread',
327     help='launch tetra_hpc multithread instead tetra_hpc distributed (MPI)',
328     choices=["no", "yes"],
329     default='no',
330   )
331   parser.add_argument(
332     '-g', '--gradation',
333     help='size ratio adjacent cell, default 0 is 1.05',
334     type=float,
335     choices=[ArgRange(0.0, 3.0)],
336     metavar='float in [0.,3]',
337     default='0'
338   )
339   parser.add_argument(
340     '-si', '--min_size',
341     help='min size cell, default 0 is no constraint',
342     type=float,
343     choices=[ArgRange(0.0, 9e99)],
344     metavar='float >= 0',
345     default='0'
346   )
347   parser.add_argument(
348     '-sa', '--max_size',
349     help='max size cell, default 0 is no constraint',
350     type=float,
351     choices=[ArgRange(0.0, 9e99)],
352     metavar='float >= 0',
353     default='0'
354   )
355   parser.add_argument(
356     '-i', '--inputFile',
357     help='input file name',
358     # nargs='?',
359     metavar='.../inputFile.mesh',
360     default='/tmp/GHS3DPRL.mesh'
361   )
362   parser.add_argument(
363     '-o', '--outputFiles',
364     help='output basename file(s) name',
365     # nargs='?',
366     metavar='.../outputFile.mesh',
367     default='/tmp/GHS3DPRL_out.mesh'
368   )
369   """
370   # example
371   parser.add_argument(
372     '-x', '--xoneargument',
373     nargs='?',
374     metavar='0|1',
375     choices=['0', '1'],
376     help='one argument, for example',
377     default='0'
378   )
379   """
380   # help(parser)
381   return parser
382
383 ##########################################################################
384 # main
385 ##########################################################################
386
387 if __name__ == '__main__':
388   """
389   args is 'Namespace' class, may use it as global to store
390   parameters, data, used arrays and results, and other...
391   """
392   parser = getParser()
393   args = parser.parse_args()
394
395   verbose = args.verbose
396   log_debug("%s arguments are:\n%s" % (__file__, PP.pformat(args.__dict__)))
397
398   if len(sys.argv) == 1:  # no args as --help
399     parser.print_help()
400     sys.exit(KOSYS)
401
402   if args.test is True:
403     test_log('one line message')
404     test_log('first line\nsecond line')
405     sys.exit(OKSYS)
406
407   if args.inputFile == None:
408     log_error("no --inputFile defined in arguments:\n%s" % PP.pformat(args.__dict__))
409     log_info("arguments should be:\n%s" % parser.format_help())
410     # parser.print_help()
411     sys.exit(KOSYS)
412
413   if not os.path.isfile(args.inputFile):
414     log_error("inexisting input file:\n%s" % os.path.realpath(args.inputFile))
415     sys.exit(KOSYS)
416
417   if args.outputFiles == None:
418     tmp, _ = os.path.splitext(args.inputFile)
419     args.outputFiles = tmp + "_out.mesh"
420   
421   tmp, _ = os.path.split(args.outputFiles)
422   args.outputDir = os.path.realpath(tmp)
423
424   log_info("output directory:\n%s" % args.outputDir)
425     
426   force_DISTENE_LICENSE_FILE() # if FORCE_DISTENE_LICENSE_FILE environ variable exists, manually set by user
427
428   DLIM8VAR = os.getenv("DLIM8VAR")
429   DISTENE_LICENSE_FILE = os.getenv("DISTENE_LICENSE_FILE")
430   msg = """\
431 %s assume distene licence file set:
432 DLIM8VAR=%s
433 DISTENE_LICENSE_FILE=%s
434 """ % (__file__, DLIM8VAR, DISTENE_LICENSE_FILE)
435   
436   if None in [DLIM8VAR, DISTENE_LICENSE_FILE]:
437     log_error(msg + "Abandon.")
438     sys.exit(KOSYS)
439   else:
440     log_info(msg)
441
442   if args.multithread == "yes":
443     result = launchMultithread(args)
444   else:
445     result = launchMpi(args)
446
447   sys.exit(okToSys(result, verbose=True))
448