"""
Part of comet/pyhed/misc
"""
# COMET - COupled Magnetic resonance Electrical resistivity Tomography
# Copyright (C) 2019 Nico Skibbe
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import os
import signal
import time
import traceback
from pathlib import Path
[docs]def embeddedMPIRun(scriptname, *scriptargs, **kwargs):
"""
Parameters
----------
scriptargs:
All given arguments will be piped to the mpirun. Kwargs has to be given
in two arguments.
kwargs:
Only for use in this function, kwargs are not piped to the
embeddedMPIRun.
kwargs
------
python_to_call: string ['python' or 'python3']
Programname to be called with mpirun.
number_of_processes: int [12]
Number of processes for mpirun.
"""
from subprocess import Popen
import sys
from comet.pyhed import log
if sys.version_info[:2] < (3, 0):
default_pcall = 'python'
else:
default_pcall = 'python3'
pcall = kwargs.pop('python_to_call', default_pcall)
nop = str(kwargs.pop('number_of_processes', 12))
timeout = kwargs.pop('mpi_timeout', 259200) # 72 h
# tocall = ['mpirun', '-n', nop, pcall, scriptname]
tocall = ['mpirun', '-n', nop, '--bind-to', 'none', pcall, scriptname]
if nop == 1:
log.info('#' * 80)
log.info('Only one process: Avoiding mpirun.')
log.info('#' * 80)
tocall = [pcall, scriptname]
tocall.extend(scriptargs)
log.info('calling {}, args: ({}), kwargs: ({})'
.format(scriptname, scriptargs, kwargs))
p = Popen(tocall, stdout=sys.stdout, stderr=sys.stdout)
timed_out = False
try:
start_waiting = time.time()
while p.poll() is None:
time.sleep(0.5)
if time.time() - start_waiting >= timeout:
timed_out = True
raise Exception('embeddedMPIRun: timeout after {} s'
.format(timeout))
log.debug('debug: p.poll: {}'.format(p.poll()))
except:
# I know it's a bare except... handling mpiruns from within python
# is not exactly easy. Find me another solution and the next comment
# line is yours to fill.
traceback.print_exc(file=sys.stdout)
log.critical('detected Error: shutting down mpi processes...')
p.send_signal(signal.SIGINT)
finally:
log.debug('debug: waiting for process to end')
p.wait()
log.debug('debug: left mpi environment')
returncode = p.returncode
log.debug('debug: return code: {}'.format(returncode))
if timed_out is True:
return -1
# !TODO!:remove hack
log.debug('processes {}successfully ended with returncode: {}'.format(
'' if returncode == 0 else 'un', returncode))
log.debug('#' * 80)
log.debug('#' * 80)
log.debug('#' * 37 + ' HACK ' + '#' * 37)
log.debug('#' * 80)
log.debug('#' * 80)
returncode = 0 # this is the hack!
# end hack
return returncode
[docs]def embeddedMPIRun_bash(scriptname, *scriptargs, **kwargs):
"""
Parameters
----------
scriptargs:
All given arguments will be piped to the mpirun. Kwargs has to be given
in two arguments.
kwargs:
Only for use in this function, kwargs are not piped to the
embeddedMPIRun.
kwargs
------
python_to_call: string ['python' or 'python3']
Programname to be called with mpirun.
number_of_processes: int [12]
Number of processes for mpirun.
"""
from subprocess import Popen
import sys
from comet.pyhed import log
tocall = ['bash', scriptname]
tocall.append(scriptname[:-3] + '.py')
tocall.extend(scriptargs)
log.info('calling {}, args: ({}), kwargs: ({})'
.format(scriptname, scriptargs, kwargs))
p = Popen(tocall, stdout=sys.stdout, stderr=sys.stdout)
timeout = kwargs.pop('mpi_timeout', 259200) # 72 h
timed_out = False
try:
start_waiting = time.time()
while p.poll() is None:
time.sleep(0.5)
if time.time() - start_waiting >= timeout:
timed_out = True
raise Exception('embeddedMPIRun: timeout after {} s'
.format(timeout))
log.debug('debug: p.poll: {}'.format(p.poll()))
except:
# I know it's a bare except... handling mpiruns from within python
# is not exactly easy. Find me another solution and the next comment
# line is yours to fill.
traceback.print_exc(file=sys.stdout)
log.critical('detected Error: shutting down mpi processes...')
p.send_signal(signal.SIGINT)
finally:
log.debug('debug: waiting for process to end')
p.wait()
log.debug('debug: left mpi environment')
returncode = p.returncode
log.debug('debug: return code: {}'.format(returncode))
if timed_out is True:
return -1
# !TODO!:remove hack
log.debug('processes {}successfully ended with returncode: {}'.format(
'' if returncode == 0 else 'un', returncode))
return returncode
[docs]def local_apps(name, *args, **kwargs):
"""
Finds local apps in the comet/pyhed/apps directory by name and call an
embeddedMPIRun and returns the subprocess.call.
"""
from comet import pyhed as ph
local_dir = kwargs.pop('local_dir', None)
if local_dir is None:
local_dir = os.path.abspath(ph.__file__.split('__init__.py')[0])
local_dir += '/apps'
if not name[-3:] == '.py':
name += '.py'
app_name = os.path.join(local_dir, name)
if name == 'app_test.py':
args = ['I\'m here', ', no really I am.']
if os.path.exists(app_name):
r_code = embeddedMPIRun(app_name, *args, **kwargs)
if r_code == -1:
print('retry after time out.')
r_code = embeddedMPIRun(app_name, *args, **kwargs)
return r_code
else:
raise Exception('No local app found for name: "{}"'.format(app_name))
[docs]def local_apps_bash(name, *args, **kwargs):
"""
Finds local apps in the comet/pyhed/apps directory by name and call an
embeddedMPIRun and returns the subprocess.call.
"""
from comet import pyhed as ph
local_dir = kwargs.pop('local_dir', None)
if local_dir is None:
local_dir = os.path.abspath(ph.__file__.split('__init__.py')[0])
local_dir += '/apps'
if not name[-3:] == '.sh':
name += '.sh'
app_name = os.path.join(local_dir, name)
if os.path.exists(app_name):
r_code = embeddedMPIRun_bash(app_name, *args, **kwargs)
if r_code == -1:
print('Timed out.')
r_code = embeddedMPIRun_bash(app_name, *args, **kwargs)
return r_code
else:
raise Exception('No local app found for name: "{}"'.format(app_name))
[docs]def tetgen151(meshname, maxArea='', quality=1.2,
path=None, verbose=False, paraString=None,
preserve_facets=False, addparams='', suppress_tetgen_files=False,
vtk_out=True):
"""TetGen
A Quality Tetrahedral Mesh Generator and 3D Delaunay Triangulator
Version 1.5
May 31, 2014
Copyright (C) 2002 - 2014
What Can TetGen Do?
TetGen generates Delaunay tetrahedralizations, constrained
Delaunay tetrahedralizations, and quality tetrahedral meshes.
Command Line Syntax:
Below is the basic command line syntax of TetGen with a list of short
descriptions. Underscores indicate that numbers may optionally
follow certain switches. Do not leave any space between a switch
and its numeric parameter. 'input_file' contains input data
depending on the switches you supplied which may be a piecewise
linear complex or a list of nodes. File formats and detailed
description of command line switches are found in user's manual.
tetgen [-pYrq_Aa_miO_S_T_XMwcdzfenvgkJBNEFICQVh] input_file
-p Tetrahedralizes a piecewise linear complex (PLC).
-Y Preserves the input surface mesh (does not modify it).
-r Reconstructs a previously generated mesh.
-q Refines mesh (to improve mesh quality).
-R Mesh coarsening (to reduce the mesh elements).
-A Assigns attributes to tetrahedra in different regions.
-a Applies a maximum tetrahedron volume constraint.
-m Applies a mesh sizing function.
-i Inserts a list of additional points.
-O Specifies the level of mesh optimization.
-S Specifies maximum number of added points.
-T Sets a tolerance for coplanar test (default 1e-8).
-X Suppresses use of exact arithmetic.
-M No merge of coplanar facets or very close vertices.
-w Generates weighted Delaunay (regular) triangulation.
-c Retains the convex hull of the PLC.
-d Detects self-intersections of facets of the PLC.
-z Numbers all output items starting from zero.
-f Outputs all faces to .face file.
-e Outputs all edges to .edge file.
-n Outputs tetrahedra neighbors to .neigh file.
-v Outputs Voronoi diagram to files.
-g Outputs mesh to .mesh file for viewing by Medit.
-k Outputs mesh to .vtk file for viewing by Paraview.
-J No jettison of unused vertices from output .node file.
-B Suppresses output of boundary information.
-N Suppresses output of .node file.
-E Suppresses output of .ele file.
-F Suppresses output of .face and .edge file.
-I Suppresses mesh iteration numbers.
-C Checks the consistency of the final mesh.
-Q Quiet: No terminal output except errors.
-V Verbose: Detailed information, more terminal output.
-h Help: A brief instruction for using TetGen.
-o2 quadratic mesh
Examples of How to Use TetGen:
'tetgen object' reads vertices from object.node, and writes their
Delaunay tetrahedralization to object.1.node, object.1.ele
(tetrahedra), and object.1.face (convex hull faces).
'tetgen -p object' reads a PLC from object.poly or object.smesh (and
possibly object.node) and writes its constrained Delaunay
tetrahedralization to object.1.node, object.1.ele, object.1.face,
(boundary faces) and object.1.edge (boundary edges).
'tetgen -pq1.414a.1 object' reads a PLC from object.poly or
object.smesh (and possibly object.node), generates a mesh whose
tetrahedra have radius-edge ratio smaller than 1.414 and have volume
of 0.1 or less, (and writes the mesh to object.1.node, object.1.ele,
object.1.face, and object.1.edge... not anymore)
"""
from comet.pyhed.IO import searchforTetgen
import pathlib
if path is None:
tetpath = searchforTetgen()
if tetpath is not None:
tetpath = pathlib.Path(tetpath)
tetgenToCall = None
standard = ' -pzQA'
if tetpath is None:
tetgenToCall = 'tetgen'
else:
for tet in ['tetgen151.exe', 'tetgen151', 'tetgen.exe', 'tetgen']:
if tetpath.joinpath(tet).exists():
tetgenToCall = tetpath.joinpath(tet).as_posix()
break
if tetgenToCall is None:
raise Exception('no tetgen found under given path: "{}"'
.format(tetpath.as_posix()))
if suppress_tetgen_files:
standard += 'NEF'
else:
standard += 'f'
params = standard + 'q%1.2f' % (quality)
if preserve_facets is True:
params += 'Y'
if maxArea == '':
params += 'a'
else:
params += 'a%f' % (maxArea)
if verbose:
params += 'VV'
if vtk_out:
params += 'k'
# optimization: default: (2/7: -o 2/7)
# 2: [0:10]
# 7: [0:7]
# eff: edge/face flipping
# vs: vertex smoothing
# vid: vertex insertion/deletion
# .../0: NO OPTIMIZATION
# .../1: eff
# .../2: vs
# .../3: vs + eff
# .../4: vid
# .../5: vid + eff
# .../6: vid + vs
# .../7: vid + vs + eff
params += addparams
poly = Path(meshname).absolute().relative_to(Path.cwd()).as_posix()
print('calling: "{}"\n {}.poly {}'.format(tetgenToCall, poly, params))
params += ' '
ret = os.system(tetgenToCall + params + poly + '.poly')
# if verbose is True:
print('tetgen is finished: {}'.format(ret == 0))
return ret
if __name__ == '__main__':
pass
# The End