Salome HOME
Adding code for Python parallel mesh with test + removing nbMesherThreads
authorYoann Audouin <yoann.audouin@edf.fr>
Tue, 4 Oct 2022 07:38:12 +0000 (09:38 +0200)
committerYoann Audouin <yoann.audouin@edf.fr>
Tue, 18 Oct 2022 13:02:19 +0000 (15:02 +0200)
idl/SMESH_Mesh.idl
src/SMESH/SMESH_Gen.cxx
src/SMESH/SMESH_Mesh.cxx
src/SMESH/SMESH_Mesh.hxx
src/SMESH_I/SMESH_2smeshpy.cxx
src/SMESH_I/SMESH_Mesh_i.cxx
src/SMESH_SWIG/smeshBuilder.py
test/SMESH_ParallelCompute.py [new file with mode: 0644]
test/netgen_runner.py
test/tests.set

index f420e4ff7fdabebbe0470673973cda00e2d88541..7b86361d83a2604744924452312f18e7dd354ca2 100644 (file)
@@ -905,11 +905,6 @@ module SMESH
     void SetNbThreads(in long nbThreads);
     /*!
 
-     * \brief Set Number of Threads for mesher
-     */
-    void SetMesherNbThreads(in long nbThreads);
-
-
     /*!
      * Get mesh description
      */
index f977bce53378968d12889c85177806cd08f6365d..922a6df95f40039f20d4965a9251f5f6b47f4321 100644 (file)
@@ -279,7 +279,7 @@ bool SMESH_Gen::parallelComputeSubMeshes(
 
   TopAbs_ShapeEnum previousShapeType = TopAbs_VERTEX;
   int nbThreads = aMesh.GetNbThreads();
-  MESSAGE("Compute submeshes with threads: " << nbThreads << " mesher: " << aMesh.GetMesherNbThreads());
+  MESSAGE("Compute submeshes with threads: " << nbThreads);
 
 
   smIt = shapeSM->getDependsOnIterator(includeSelf, !complexShapeFirst);
index 3eb08a85c491cba94eef110101f94a834fef3f60..bd101248cee147f93a3096a31ec3853e76c2a952 100644 (file)
@@ -87,7 +87,7 @@ namespace fs=boost::filesystem;
 #define MAX_MED_GROUP_NAME_LENGTH 80
 
 #ifdef _DEBUG_
-static int MYDEBUG = 1;
+static int MYDEBUG = 0;
 #else
 static int MYDEBUG = 0;
 #endif
index 8c4d0b18be68707fc0f5ed2cc470537b6d981ee6..ed897979f1f448adc2826abee44248081bc40034 100644 (file)
@@ -392,9 +392,6 @@ class SMESH_EXPORT SMESH_Mesh
   int GetNbThreads(){return _NbThreads;};
   void SetNbThreads(int nbThreads){_NbThreads=nbThreads;};
 
-  int GetMesherNbThreads(){return _MesherNbThreads;};
-  void SetMesherNbThreads(int nbThreads){_MesherNbThreads=nbThreads;};
-
   void InitPoolThreads(){_pool = new boost::asio::thread_pool(_NbThreads);};
   void DeletePoolThreads(){delete _pool;};
 
@@ -456,7 +453,6 @@ protected:
   // Mutex for multhitreading write in SMESH_Mesh
   std::mutex _my_lock;
   int _NbThreads=0;
-  int _MesherNbThreads=0;
 
 protected:
   SMESH_Mesh();
index a9fff0a6d6add7fdcb61761d2fcd42316a3c6aeb..4ac51ac995e0e19de4ded48a0b8ed97fd4342f55 100644 (file)
@@ -2213,7 +2213,7 @@ bool _pyMesh::NeedMeshAccess( const Handle(_pyCommand)& theCommand )
         "GetElemFaceNodes", "GetFaceNormal", "FindElementByNodes",
         "IsPoly","IsQuadratic","BaryCenter","GetHypothesisList", "SetAutoColor", "GetAutoColor",
         "Clear", "ConvertToStandalone", "GetMeshOrder", "SetMeshOrder",
-        "SetNbThreads", "SetMesherNbThreads"
+        "SetNbThreads"
         ,"" }; // <- mark of end
     sameMethods.Insert( names );
   }
index 3bd2f7fe74587a973b563c0d2701d30001eb9701..a7abda6dbd11366647ecf8b19a85da36d967f795 100644 (file)
@@ -7046,15 +7046,6 @@ void SMESH_Mesh_i::SetNbThreads(int nbThreads){
   _impl->SetNbThreads(nbThreads);
 }
 
-//=============================================================================
-/*!
- * \brief Set the number of threads for the mesher for a parallel computation
- */
-//=============================================================================
-void SMESH_Mesh_i::SetMesherNbThreads(int nbThreads){
-  _impl->SetMesherNbThreads(nbThreads);
-}
-
 
 //=============================================================================
 /*!
index a06ece58496196a5985b67aa2c0b1e13434255b3..ee22a96bddc01981c1cc90b29503480f1ea896b1 100644 (file)
@@ -462,6 +462,21 @@ class smeshBuilder( SMESH._objref_SMESH_Gen, object ):
             obj,name = name,obj
         return Mesh(self, self.geompyD, obj, name)
 
+    def ParallelMesh(self, obj, param, nbThreads, name=0):
+        """
+        Create a parallel mesh.
+
+        Parameters:
+            obj: geometrical object for meshing
+            name: the name for the new mesh.
+            param: full mesh parameters
+            nbThreads: Number of threads for parallelisation.
+
+        Returns:
+            an instance of class :class:`ParallelMesh`.
+        """
+        return ParallelMesh(self, self.geompyD, obj, param, nbThreads, name)
+
     def RemoveMesh( self, mesh ):
         """
         Delete a mesh
@@ -1863,31 +1878,6 @@ class Mesh(metaclass = MeshMeta):
                 geom = self.geom
         return self.smeshpyD.Evaluate(self.mesh, geom)
 
-    def ParallelCompute(self, nbThreads, mesherNbThreads=1, geom=0, discardModifs=False, refresh=False):
-        """
-        Parallel computation of the mesh and return the status of the computation
-        The mesh must contains have be constructed using create_parallel_mesh
-
-        Parameters:
-                nbThreads: Number of threads to use for a parallel computation
-                geom: geomtrical shape on which mesh data should be computed
-                discardModifs: if True and the mesh has been edited since
-                        a last total re-compute and that may prevent successful partial re-compute,
-                        then the mesh is cleaned before Compute()
-                refresh: if *True*, Object Browser is automatically updated (when running in GUI)
-
-        Returns:
-                True or False
-        """
-        if (nbThreads <= 1):
-            raise ValueError("nbThreads must be strictly greater than 1")
-        if (mesherNbThreads < 1):
-            raise ValueError("nbThreads must be greater than 1")
-
-        self.mesh.SetMesherNbThreads(mesherNbThreads)
-        self.mesh.SetNbThreads(nbThreads)
-        return self.Compute(geom=geom, discardModifs=discardModifs, refresh=refresh)
-
     def Compute(self, geom=0, discardModifs=False, refresh=False):
         """
         Compute the mesh and return the status of the computation
@@ -7511,6 +7501,121 @@ class Mesh(metaclass = MeshMeta):
     pass # end of Mesh class
 
 
+def _copy_netgen_param(dim, local_param, global_param):
+    if dim==1:
+        #TODO: Try to identify why we need to substract 1
+        local_param.NumberOfSegments(int(global_param.GetNbSegPerEdge())-1)
+    elif dim==2:
+        local_param.SetMaxSize(global_param.GetMaxSize())
+        local_param.SetMinSize(global_param.GetMinSize())
+        local_param.SetOptimize(global_param.GetOptimize())
+        local_param.SetFineness(global_param.GetFineness())
+        local_param.SetNbSegPerEdge(global_param.GetNbSegPerEdge())
+        local_param.SetNbSegPerRadius(global_param.GetNbSegPerRadius())
+        local_param.SetGrowthRate(global_param.GetGrowthRate()*0.9)
+        local_param.SetChordalError(global_param.GetChordalError())
+        local_param.SetChordalErrorEnabled(global_param.GetChordalErrorEnabled())
+        local_param.SetUseSurfaceCurvature(global_param.GetUseSurfaceCurvature())
+        local_param.SetUseDelauney(global_param.GetUseDelauney())
+        local_param.SetQuadAllowed(global_param.GetQuadAllowed())
+        local_param.SetWorstElemMeasure(global_param.GetWorstElemMeasure())
+        local_param.SetCheckChartBoundary(global_param.GetCheckChartBoundary())
+        local_param.SetNbThreads(global_param.GetNbThreads())
+    else:
+        local_param.SetMaxSize(global_param.GetMaxSize())
+        local_param.SetMinSize(global_param.GetMinSize())
+        local_param.SetOptimize(global_param.GetOptimize())
+        local_param.SetCheckOverlapping(global_param.GetCheckOverlapping())
+        local_param.SetCheckChartBoundary(global_param.GetCheckChartBoundary())
+        local_param.SetFineness(global_param.GetFineness())
+        local_param.SetNbSegPerEdge(global_param.GetNbSegPerEdge())
+        local_param.SetNbSegPerRadius(global_param.GetNbSegPerRadius())
+        local_param.SetGrowthRate(global_param.GetGrowthRate())
+        local_param.SetNbThreads(global_param.GetNbThreads())
+
+class ParallelMesh(Mesh):
+    """
+    Surcharge on Mesh for parallel computation of a mesh
+    """
+
+    def __init__(self, smeshpyD, geompyD, geom, param, nbThreads, name=0):
+        """
+        Create a parallel mesh.
+
+        Parameters:
+            geom: geometrical object for meshing
+            param: full mesh parameters
+            nbThreads: Number of threads for parallelisation.
+            name: the name for the new mesh.
+
+        Returns:
+            an instance of class :class:`ParallelMesh`.
+        """
+
+        if not isinstance(geom, geomBuilder.GEOM._objref_GEOM_Object):
+            raise ValueError("geom argument must be a geometry")
+
+        if not isinstance(param, NETGENPlugin._objref_NETGENPlugin_Hypothesis):
+            raise ValueError("param must come from NETGENPlugin")
+
+        if nbThreads < 1:
+            raise ValueError("Number of threads must be stricly greater than 1")
+
+        # Splitting geometry into 3D elements and all the 2D/1D into one compound
+        object_solids = geompyD.ExtractShapes(geom, geompyD.ShapeType["SOLID"],
+                                              True)
+
+        solids = []
+        isolid = 0
+        for solid in object_solids:
+            isolid += 1
+            geompyD.addToStudyInFather( geom, solid, 'Solid_{}'.format(isolid) )
+            solids.append(solid)
+
+        faces = []
+        iface = 0
+        for isolid, solid in enumerate(solids):
+            solid_faces = geompyD.ExtractShapes(solid, geompyD.ShapeType["FACE"],
+                                                True)
+            for face in solid_faces:
+                faces.append(face)
+                iface += 1
+                geompyD.addToStudyInFather(solid, face,
+                                           'Face_{}'.format(iface))
+
+        # Creating submesh for edges 1D/2D part
+
+        all_faces = geompyD.MakeCompound(faces)
+        geompyD.addToStudy(all_faces, 'Compound_1')
+        all_faces = geompyD.MakeGlueEdges(all_faces, 1e-07)
+        all_faces = geompyD.MakeGlueFaces(all_faces, 1e-07)
+        geompyD.addToStudy(all_faces, 'global2D')
+
+        super(ParallelMesh, self).__init__(smeshpyD, geompyD, geom, name)
+
+        self.mesh.SetNbThreads(nbThreads)
+
+        self.UseExistingSegments()
+        self.UseExistingFaces()
+
+        algo2d = self.Triangle(geom=all_faces, algo="NETGEN_2D")
+        param2d = algo2d.Parameters()
+
+        _copy_netgen_param(2, param2d, param)
+
+        for solid_id, solid in enumerate(solids):
+            name = "Solid_{}".format(solid_id)
+            self.UseExistingSegments(geom=solid)
+            self.UseExistingFaces(geom=solid)
+            algo3d = self.Tetrahedron(geom=solid, algo="NETGEN_3D_Remote")
+
+            param3d = algo3d.Parameters()
+
+            _copy_netgen_param(3, param3d, param)
+
+    pass # End of ParallelMesh
+
+
 class meshProxy(SMESH._objref_SMESH_Mesh):
     """
     Private class used to compensate change of CORBA API of SMESH_Mesh for backward compatibility
diff --git a/test/SMESH_ParallelCompute.py b/test/SMESH_ParallelCompute.py
new file mode 100644 (file)
index 0000000..eeddd3f
--- /dev/null
@@ -0,0 +1,131 @@
+# contains function to compute a mesh in parallel
+from platform import java_ver
+import sys
+from tkinter import W
+import salome
+
+import time
+
+
+salome.salome_init()
+import salome_notebook
+notebook = salome_notebook.NoteBook()
+
+###
+### GEOM component
+###
+
+import GEOM
+from salome.geom import geomBuilder
+from salome.smesh import smeshBuilder
+import math
+import SALOMEDS
+
+import numpy as np
+
+geompy = geomBuilder.New()
+
+smesh = smeshBuilder.New()
+
+
+def build_seq_mesh(nbox, boxsize, offset):
+    # Create 3D faces
+    boxes = []
+    # First creating all the boxes
+    for i in range(nbox):
+        for j in range(nbox):
+            for k in range(nbox):
+
+                x_orig = i*(boxsize+offset)
+                y_orig = j*(boxsize+offset)
+                z_orig = k*(boxsize+offset)
+
+                tmp_box = geompy.MakeBoxDXDYDZ(boxsize, boxsize, boxsize)
+
+                if not i == j == k == 0:
+                    box = geompy.MakeTranslation(tmp_box, x_orig,
+                                                 y_orig, z_orig)
+                else:
+                    box = tmp_box
+
+                geompy.addToStudy(box, 'box_{}:{}:{}'.format(i, j, k))
+
+                boxes.append(box)
+
+    # Create fuse of all boxes
+    all_boxes = geompy.MakeCompound(boxes)
+    geompy.addToStudy(all_boxes, 'Compound_1')
+
+    # Removing duplicates faces and edges
+    all_boxes = geompy.MakeGlueFaces(all_boxes, 1e-07)
+    geompy.addToStudy(all_boxes, 'Glued_Faces_1')
+
+    all_boxes = geompy.MakeGlueEdges(all_boxes, 1e-07)
+    geompy.addToStudy(all_boxes, 'rubik_cube')
+
+
+    # Building sequetial mesh
+    print("Creating mesh")
+    all_box_mesh = smesh.Mesh(all_boxes, "seq_mesh")
+
+    print("Adding algo")
+    algo3d = all_box_mesh.Tetrahedron(algo=smeshBuilder.NETGEN_1D2D3D)
+
+    netgen_parameters = algo3d.Parameters()
+    netgen_parameters.SetMaxSize(34.641)
+    netgen_parameters.SetMinSize(0.141421)
+    netgen_parameters.SetOptimize(1)
+    netgen_parameters.SetCheckOverlapping(0)
+    netgen_parameters.SetCheckChartBoundary(0)
+    netgen_parameters.SetFineness(5)
+    netgen_parameters.SetNbSegPerEdge(16*(boxsize//100))
+    netgen_parameters.SetNbSegPerRadius(1.5)
+    netgen_parameters.SetGrowthRate(0.15)
+    netgen_parameters.SetChordalError(-1)
+    netgen_parameters.SetChordalErrorEnabled(0)
+    netgen_parameters.SetUseSurfaceCurvature(1)
+    netgen_parameters.SetQuadAllowed(0)
+    netgen_parameters.SetCheckOverlapping(False)
+    netgen_parameters.SetNbThreads(2)
+
+    return all_boxes, all_box_mesh, netgen_parameters
+
+def run_test(nbox=2, boxsize=100):
+    """ Run sequential mesh and parallel version of it
+
+    nbox: NUmber of boxes
+    boxsize: Size of each box
+    """
+    geom, seq_mesh, netgen_parameters = build_seq_mesh(nbox, boxsize, 0)
+
+    par_mesh = smesh.ParallelMesh(geom, netgen_parameters, 6, name="par_mesh")
+
+    start = time.monotonic()
+    is_done = seq_mesh.Compute()
+    assert is_done
+    stop = time.monotonic()
+    time_seq = stop-start
+
+    start = time.monotonic()
+    is_done = par_mesh.Compute()
+    assert is_done
+    stop = time.monotonic()
+    time_par = stop-start
+
+    print("  Tetrahedron: ", seq_mesh.NbTetras(), par_mesh.NbTetras())
+    print("  Triangle: ", seq_mesh.NbTriangles(), par_mesh.NbTriangles())
+    print("  edge: ", seq_mesh.NbEdges(), par_mesh.NbEdges())
+
+    assert  par_mesh.NbTetras() > 0
+    assert  par_mesh.NbTriangles() > 0
+    assert  par_mesh.NbEdges() > 0
+
+    print("Time elapsed (seq, par): ", time_seq, time_par)
+
+def main():
+    nbox = 2
+    boxsize = 100
+    run_test(nbox, boxsize)
+
+main()
+
index a23c7e9ce590816fa5026dfe48ba522f86362481..be8386fcb9da7df4cbaf8b80199b638c91ef1b0f 100644 (file)
@@ -22,7 +22,7 @@ import medcoupling as mc
 
 def create_param_file(param_file):
     """ Create a parameter file for runner """
-    param="""1
+    param = """1
 34.64
 0.14
 16
@@ -43,8 +43,10 @@ def create_param_file(param_file):
 0
 0
 2
+2
 0
 
+0
 0
 0"""
     with open(param_file, "w") as ffile:
@@ -57,69 +59,71 @@ def test_netgen3d():
     box = geompy.MakeBoxDXDYDZ(200, 200, 200)
 
     geompy.ExtractShapes(box, geompy.ShapeType["FACE"], True)
-    Groupe_1 = geompy.CreateGroup(box, geompy.ShapeType["FACE"])
-    geompy.UnionIDs(Groupe_1, [3, 13, 23, 27, 31, 33])
+    groupe_1 = geompy.CreateGroup(box, geompy.ShapeType["FACE"])
+    geompy.UnionIDs(groupe_1, [3, 13, 23, 27, 31, 33])
 
-    # TODO: useful ?
-    [_, _, _, _, _, _, Groupe_1] = geompy.GetExistingSubObjects(box, False)
+    [_, _, _, _, _, _, groupe_1] = geompy.GetExistingSubObjects(box, False)
 
     # Creating 2D mesh
-    NETGEN_2D_Parameters_1 = smesh.CreateHypothesisByAverageLength(
+    netgen_2d_parameters_1 = smesh.CreateHypothesisByAverageLength(
         'NETGEN_Parameters_2D', 'NETGENEngine', 34.641, 0)
-    Mesh2D = smesh.Mesh(Groupe_1, 'Maillage_1')
-    status = Mesh2D.AddHypothesis(Groupe_1, NETGEN_2D_Parameters_1)
-    NETGEN_1D_2D = Mesh2D.Triangle(algo=smeshBuilder.NETGEN_1D2D)
-    isDone = Mesh2D.Compute()
-    smesh.SetName(Mesh2D, 'Maillage_1')
+    mesh_2d = smesh.Mesh(groupe_1, 'Maillage_1')
+    mesh_2d.AddHypothesis(groupe_1, netgen_2d_parameters_1)
+    mesh_2d.Triangle(algo=smeshBuilder.NETGEN_1D2D)
+    is_done = mesh_2d.Compute()
+    assert is_done
+    smesh.SetName(mesh_2d, 'Maillage_1')
 
-    # tmp_dir = tempfile.mkdtemp()
     with tempfile.TemporaryDirectory() as tmp_dir:
-      mesh_file = path.join(tmp_dir, "mesh.med")
-      shape_file = path.join(tmp_dir, "shape.step")
-      param_file = path.join(tmp_dir, "param.txt")
-      output_mesh = path.join(tmp_dir, "mesh3D.med")
-
-      print("Running in folder: ", tmp_dir)
-      create_param_file(param_file)
-
-      Mesh2D.ExportMED(mesh_file, 0, 41, 1, Mesh2D, 1, [], '', -1, 1)
-      geompy.ExportSTEP(box, shape_file, GEOM.LU_METER)
-
-      runner = path.join("${NETGENPLUGIN_ROOT_DIR}",
-                          "bin",
-                          "salome",
-                          "NETGENPlugin_Runner")
-
-      cmd = "{runner} NETGEN3D {mesh_file} {shape_file} "\
-            "{param_file} NONE 2 NONE {output_mesh}"\
-            .format(runner=runner,
-                    mesh_file=mesh_file,
-                    shape_file=shape_file,
-                    param_file=param_file,
-                    output_mesh=output_mesh)
-      print(cmd)
-      subprocess.check_call(cmd, shell=True)
-
-      meshRead = mc.ReadUMeshFromFile (output_mesh, "MESH", 0)
-
-      nbTetras = meshRead.getNumberOfCellsWithType(mc.NORM_TETRA4)
-      nbPoints = meshRead.getNumberOfNodes()
-
-      meshRead = mc.ReadUMeshFromFile (output_mesh, "MESH", -1)
-      nbTriangles = meshRead.getNumberOfCellsWithType(mc.NORM_TRI3)
-
-      meshRead = mc.ReadUMeshFromFile (output_mesh, "MESH", -2)
-      nbSegments = meshRead.getNumberOfCellsWithType(mc.NORM_SEG2)
-
-      print("Nb Tetras:", nbTetras)
-      print("Nb Triangles:", nbTriangles)
-      print("Nb Segments:", nbSegments)
-      print("Nb Points:", nbPoints)
-
-      assert(nbPoints > 0)
-      assert(nbSegments > 0)
-      assert(nbTriangles > 0)
-      assert(nbTetras > 0)
+        mesh_file = path.join(tmp_dir, "mesh.med")
+        shape_file = path.join(tmp_dir, "shape.step")
+        param_file = path.join(tmp_dir, "param.txt")
+        output_mesh = path.join(tmp_dir, "mesh3D.med")
+
+        print("Running in folder: ", tmp_dir)
+        create_param_file(param_file)
+
+        mesh_2d.ExportMED(mesh_file, 0, 41, 1, mesh_2d, 1, [], '', -1, 1)
+        geompy.ExportSTEP(box, shape_file, GEOM.LU_METER)
+
+        runner = path.join("${NETGENPLUGIN_ROOT_DIR}",
+                           "bin",
+                           "salome",
+                           "NETGENPlugin_Runner")
+
+        if sys.platform == 'win32':
+            runner += ".exe"
+
+        cmd = "{runner} NETGEN3D {mesh_file} {shape_file} "\
+              "{param_file} NONE NONE {output_mesh}"\
+              .format(runner=runner,
+                      mesh_file=mesh_file,
+                      shape_file=shape_file,
+                      param_file=param_file,
+                      output_mesh=output_mesh)
+        print(cmd)
+        subprocess.check_call(cmd, shell=True)
+
+        mesh_read = mc.ReadUMeshFromFile(output_mesh, "MESH", 0)
+
+        nb_tetras = mesh_read.getNumberOfCellsWithType(mc.NORM_TETRA4)
+        nb_points = mesh_read.getNumberOfNodes()
+
+        mesh_read = mc.ReadUMeshFromFile(output_mesh, "MESH", -1)
+        nb_triangles = mesh_read.getNumberOfCellsWithType(mc.NORM_TRI3)
+
+        mesh_read = mc.ReadUMeshFromFile(output_mesh, "MESH", -2)
+        nb_segments = mesh_read.getNumberOfCellsWithType(mc.NORM_SEG2)
+
+        print("Nb Tetras:", nb_tetras)
+        print("Nb Triangles:", nb_triangles)
+        print("Nb Segments:", nb_segments)
+        print("Nb Points:", nb_points)
+
+        assert nb_points > 0
+        assert nb_segments > 0
+        assert nb_triangles > 0
+        assert nb_tetras > 0
 
 if __name__ == "__main__":
     test_netgen3d()
index 4664c6a515071a2bbf6e19b55032ae0e7dcd0c46..2c2d901c883854ac200472b21d8f5ef22a64b1d6 100644 (file)
@@ -64,6 +64,7 @@ SET(BAD_TESTS
   SMESH_test4.py
   SMESH_create_dual_mesh_adapt.py
   netgen_runner.py
+  SMESH_ParallelCompute.py
   )
 IF(NOT WIN32)
   LIST(APPEND BAD_TESTS