From d328a1d7b8ebcd3d3f02ea9996d56d50016be3a4 Mon Sep 17 00:00:00 2001 From: Yoann Audouin Date: Thu, 9 Mar 2023 16:32:55 +0100 Subject: [PATCH] Cleanup of parallel meshing + documentation --- .../examples/creating_parallel_mesh.py | 10 +- doc/examples/tests.set | 1 + doc/gui/images/diagram_parallel_mesh.png | Bin 0 -> 43445 bytes doc/gui/input/about_meshes.rst | 3 + doc/gui/input/parallel_compute.rst | 70 +++++++ doc/gui/input/tui_creating_meshes.rst | 9 + idl/SMESH_Gen.idl | 10 + idl/SMESH_Mesh.idl | 8 + src/SMESH/CMakeLists.txt | 4 + src/SMESH/SMESH_Gen.cxx | 75 ++++---- src/SMESH/SMESH_Gen.hxx | 10 +- src/SMESH/SMESH_Mesh.cxx | 29 --- src/SMESH/SMESH_Mesh.hxx | 59 +++--- src/SMESH/SMESH_ParallelMesh.cxx | 120 ++++++++++++ src/SMESH/SMESH_ParallelMesh.hxx | 83 ++++++++ src/SMESH/SMESH_SequentialMesh.cxx | 69 +++++++ src/SMESH/SMESH_SequentialMesh.hxx | 73 +++++++ src/SMESH/SMESH_subMesh.cxx | 3 +- src/SMESH_I/CMakeLists.txt | 4 +- src/SMESH_I/SMESH_Gen_i.cxx | 48 ++++- src/SMESH_I/SMESH_Gen_i.hxx | 5 +- src/SMESH_I/SMESH_Mesh_i.cxx | 9 + src/SMESH_I/SMESH_Mesh_i.hxx | 4 + src/SMESH_SWIG/smeshBuilder.py | 180 +++++++++++++----- src/StdMeshers/StdMeshers_Prism_3D.cxx | 4 +- src/StdMeshers/StdMeshers_Prism_3D.hxx | 7 +- src/StdMeshers/StdMeshers_Projection_2D.cxx | 11 +- .../StdMeshers_QuadFromMedialAxis_1D2D.cxx | 14 +- .../StdMeshers_RadialQuadrangle_1D2D.cxx | 22 +-- test/tests.set | 1 - 30 files changed, 758 insertions(+), 187 deletions(-) rename test/SMESH_ParallelCompute.py => doc/examples/creating_parallel_mesh.py (90%) create mode 100644 doc/gui/images/diagram_parallel_mesh.png create mode 100644 doc/gui/input/parallel_compute.rst create mode 100644 src/SMESH/SMESH_ParallelMesh.cxx create mode 100644 src/SMESH/SMESH_ParallelMesh.hxx create mode 100644 src/SMESH/SMESH_SequentialMesh.cxx create mode 100644 src/SMESH/SMESH_SequentialMesh.hxx diff --git a/test/SMESH_ParallelCompute.py b/doc/examples/creating_parallel_mesh.py similarity index 90% rename from test/SMESH_ParallelCompute.py rename to doc/examples/creating_parallel_mesh.py index fccec1300..e837fc9d7 100644 --- a/test/SMESH_ParallelCompute.py +++ b/doc/examples/creating_parallel_mesh.py @@ -98,14 +98,22 @@ def run_test(nbox=2, boxsize=100): """ geom, seq_mesh, netgen_parameters = build_seq_mesh(nbox, boxsize, 0) - par_mesh = smesh.ParallelMesh(geom, netgen_parameters, 6, name="par_mesh") + print("Creating Parallel Mesh") + par_mesh = smesh.ParallelMesh(geom, name="par_mesh") + par_mesh.AddGlobalHypothesis(netgen_parameters) + param = par_mesh.GetParallelismSettings() + param.SetNbThreads(6) + assert param.GetNbThreads() == 6, param.GetNbThreads() + + print("Starting sequential compute") start = time.monotonic() is_done = seq_mesh.Compute() assert is_done stop = time.monotonic() time_seq = stop-start + print("Starting parallel compute") start = time.monotonic() is_done = par_mesh.Compute() assert is_done diff --git a/doc/examples/tests.set b/doc/examples/tests.set index df39d9dd9..e051ca01c 100644 --- a/doc/examples/tests.set +++ b/doc/examples/tests.set @@ -129,6 +129,7 @@ SET(BAD_TESTS viewing_meshes_ex01.py radial_prism_3d_algo.py create_dual_mesh.py + creating_parallel_mesh.py ) IF(NOT WIN32) LIST(APPEND BAD_TESTS diff --git a/doc/gui/images/diagram_parallel_mesh.png b/doc/gui/images/diagram_parallel_mesh.png new file mode 100644 index 0000000000000000000000000000000000000000..466b29b97d653f19c011f69288fdf08121a6ec96 GIT binary patch literal 43445 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
!IKeV@mE15%Fj_S>q?+APIoblEe7|IMjbJDMgv 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 literal 0 HcmV?d00001 diff --git a/doc/gui/input/about_meshes.rst b/doc/gui/input/about_meshes.rst index 619bd799f..6350eea4b 100644 --- a/doc/gui/input/about_meshes.rst +++ b/doc/gui/input/about_meshes.rst @@ -71,6 +71,8 @@ Quadratic mesh can be obtained in three ways: * Using :ref:`convert_to_from_quadratic_mesh_page` operation. * Using an appropriate option of some meshing algorithms, which generate elements of several dimensions starting from mesh segments. +A work in progress allow you to compute your mesh in parralle :ref:`Parallel Computing` + **Table of Contents** @@ -86,3 +88,4 @@ Quadratic mesh can be obtained in three ways: copy_mesh.rst create_dual_mesh.rst connectivity.rst + parallel_compute.rst diff --git a/doc/gui/input/parallel_compute.rst b/doc/gui/input/parallel_compute.rst new file mode 100644 index 000000000..fecbf2fea --- /dev/null +++ b/doc/gui/input/parallel_compute.rst @@ -0,0 +1,70 @@ +.. _parallel_compute_page: + +****************** +Parallel Computing +****************** + + +.. warning:: + This functionality is a work in progress. + + It is only available for NETGEN. + + It is only available in TUI. + + +The goal here is to speed up computation by running sub-meshes in parallel +(multi-threading). + +******* +Concept +******* + +.. image:: ../images/diagram_parallel_mesh.png + +In order to parallelise the computation of the mesh we split the geometry into: + + * A 1D+2D compound + * A list of 3D solids + +Then create a sub-mesh for each of those geometry. +And associate Hypothesis to the mesh using a hypothesis on the whole geometry + +We will first compute sequentially the 1D+2D compound with NETGEN_1D2D. + +Then we will compute all the solids in parallel. Having done the 1D+2D first +ensure that all the solids can be computed without any concurrency. + + +****** +How to +****** + +You follow the same principle as the creation of a sequential Mesh. + + +#. First you create the mesh: + .. code-block:: python + + par_mesh = smesh.ParallelMesh(geom, name="par_mesh") + +#. Define the Global Hypothesis that will be split into an hypothesis for the + 1D+2D compound and one for each of the 3D solids: + .. code-block:: python + + NETGEN_3D_Parameters_1 = smesh.CreateHypothesisByAverageLength( 'NETGEN_Parameters', + 'NETGENEngine', 34.641, 0 ) + par_mesh.AddGlobalHypothesis(netgen_parameters) + +#. Set the parameters for the parallelisation: + .. code-block:: python + + param = par_mesh.GetParallelismSettings() + param.SetNbThreads(6) + +#. Compute the mesh: + .. code-block:: python + + mesh.Compute() + +**See Also** a sample script of :ref:`tui_create_parallel_mesh`. diff --git a/doc/gui/input/tui_creating_meshes.rst b/doc/gui/input/tui_creating_meshes.rst index 253fbf260..67e14d358 100644 --- a/doc/gui/input/tui_creating_meshes.rst +++ b/doc/gui/input/tui_creating_meshes.rst @@ -117,3 +117,12 @@ Creating Dual Mesh :download:`Download this script <../../examples/create_dual_mesh.py>` +.. _tui_create_parallel_mesh: + +Creating Parallel Mesh +====================== + +.. literalinclude:: ../../examples/creating_parallel_mesh.py + :language: python + +:download:`Download this script <../../examples/creating_parallel_mesh.py>` diff --git a/idl/SMESH_Gen.idl b/idl/SMESH_Gen.idl index a5b48aaa6..a36e30d3e 100644 --- a/idl/SMESH_Gen.idl +++ b/idl/SMESH_Gen.idl @@ -242,6 +242,16 @@ module SMESH SMESH_Mesh CreateMesh( in GEOM::GEOM_Object theObject ) raises ( SALOME::SALOME_Exception ); + /*! + * Create a Mesh object, given a geometry shape. + * Mesh is created empty (no points, no elements). + * Shape is explored via GEOM_Client to create local copies. + * of TopoDS_Shapes and bind CORBA references of shape & subshapes + * with TopoDS_Shapes + * The mesh is a parallel one + */ + SMESH_Mesh CreateParallelMesh( in GEOM::GEOM_Object theObject ) + raises ( SALOME::SALOME_Exception ); /*! * Create an empty mesh object */ diff --git a/idl/SMESH_Mesh.idl b/idl/SMESH_Mesh.idl index 7b86361d8..88152274e 100644 --- a/idl/SMESH_Mesh.idl +++ b/idl/SMESH_Mesh.idl @@ -904,6 +904,11 @@ module SMESH */ void SetNbThreads(in long nbThreads); /*! + /*! + * \brief Get Number of Threads + */ + long GetNbThreads(); + /*! /*! * Get mesh description @@ -1108,6 +1113,9 @@ module SMESH long GetId(); }; + interface SMESH_SequentialMesh:SMESH_Mesh{}; + interface SMESH_ParallelMesh:SMESH_Mesh{}; + }; #endif diff --git a/src/SMESH/CMakeLists.txt b/src/SMESH/CMakeLists.txt index 88d42d875..ec3761798 100644 --- a/src/SMESH/CMakeLists.txt +++ b/src/SMESH/CMakeLists.txt @@ -77,6 +77,8 @@ SET(_link_LIBRARIES SET(SMESHimpl_HEADERS SMESH_Gen.hxx SMESH_Mesh.hxx + SMESH_SequentialMesh.hxx + SMESH_ParallelMesh.hxx SMESH_subMesh.hxx SMESH_subMeshEventListener.hxx SMESH_Hypothesis.hxx @@ -102,6 +104,8 @@ SET(SMESHimpl_SOURCES memoire.h SMESH_Gen.cxx SMESH_Mesh.cxx + SMESH_SequentialMesh.cxx + SMESH_ParallelMesh.cxx SMESH_subMesh.cxx SMESH_Hypothesis.cxx SMESH_Algo.cxx diff --git a/src/SMESH/SMESH_Gen.cxx b/src/SMESH/SMESH_Gen.cxx index bd8098302..4a020890e 100644 --- a/src/SMESH/SMESH_Gen.cxx +++ b/src/SMESH/SMESH_Gen.cxx @@ -27,9 +27,6 @@ // //#define CHRONODEF // -#ifndef WIN32 -#include -#endif #include "SMESH_Gen.hxx" #include "SMESH_DriverMesh.hxx" @@ -39,6 +36,8 @@ #include "SMESHDS_Document.hxx" #include "SMESH_HypoFilter.hxx" #include "SMESH_Mesh.hxx" +#include "SMESH_SequentialMesh.hxx" +#include "SMESH_ParallelMesh.hxx" #include "SMESH_MesherHelper.hxx" #include "SMESH_subMesh.hxx" @@ -58,6 +57,10 @@ #include +#ifndef WIN32 +#include +#endif + using namespace std; #ifndef WIN32 #include @@ -154,7 +157,8 @@ SMESH_Mesh* SMESH_Gen::CreateMesh(bool theIsEmbeddedMode) Unexpect aCatch(SalomeException); // create a new SMESH_mesh object - SMESH_Mesh *aMesh = new SMESH_Mesh(_localId++, + SMESH_Mesh *aMesh = new SMESH_SequentialMesh( + _localId++, this, theIsEmbeddedMode, _studyContext->myDocument); @@ -163,6 +167,27 @@ SMESH_Mesh* SMESH_Gen::CreateMesh(bool theIsEmbeddedMode) return aMesh; } +//============================================================================= +/*! + * Creates a parallel mesh in a study. + * if (theIsEmbeddedMode) { mesh modification commands are not logged } + */ +//============================================================================= + +SMESH_Mesh* SMESH_Gen::CreateParallelMesh(bool theIsEmbeddedMode) +{ + Unexpect aCatch(SalomeException); + + // create a new SMESH_mesh object + SMESH_Mesh *aMesh = new SMESH_ParallelMesh( + _localId++, + this, + theIsEmbeddedMode, + _studyContext->myDocument); + _studyContext->mapMesh[_localId-1] = aMesh; + + return aMesh; +} //============================================================================= /*! @@ -200,7 +225,7 @@ bool SMESH_Gen::sequentialComputeSubMeshes( continue; // check for preview dimension limitations - if ( aShapesId && GetShapeDim( shapeType ) > (int)aDim ) + if ( aShapesId && SMESH_Gen::GetShapeDim( shapeType ) > (int)aDim ) { // clear compute state not to show previous compute errors // if preview invoked less dimension less than previous @@ -264,6 +289,7 @@ const std::function (int)aDim ) + if ( aShapesId && SMESH_Gen::GetShapeDim( shapeType ) > (int)aDim ) { // clear compute state not to show previous compute errors // if preview invoked less dimension less than previous smToCompute->ComputeStateEngine( SMESH_subMesh::CHECK_COMPUTE_STATE ); continue; } - boost::asio::post(*(aMesh._pool), std::bind(compute_function, smToCompute, computeEvent, + boost::asio::post(*(aMesh.GetPool()), std::bind(compute_function, smToCompute, computeEvent, shapeSM, aShapeOnly, allowedSubShapes, aShapesId)); } @@ -364,7 +384,6 @@ bool SMESH_Gen::parallelComputeSubMeshes( aMesh.wait(); aMesh.GetMeshDS()->Modified(); - aMesh.DeleteTmpFolder(); return ret; #endif @@ -416,22 +435,14 @@ bool SMESH_Gen::Compute(SMESH_Mesh & aMesh, // =============================================== // Mesh all the sub-shapes starting from vertices // =============================================== - if (aMesh.IsParallel()) - ret = parallelComputeSubMeshes( - aMesh, aShape, aDim, - aShapesId, allowedSubShapes, - computeEvent, - includeSelf, - complexShapeFirst, - aShapeOnly); - else - ret = sequentialComputeSubMeshes( - aMesh, aShape, aDim, - aShapesId, allowedSubShapes, - computeEvent, - includeSelf, - complexShapeFirst, - aShapeOnly); + ret = aMesh.ComputeSubMeshes( + this, + aMesh, aShape, aDim, + aShapesId, allowedSubShapes, + computeEvent, + includeSelf, + complexShapeFirst, + aShapeOnly); return ret; } diff --git a/src/SMESH/SMESH_Gen.hxx b/src/SMESH/SMESH_Gen.hxx index 94058184e..40e92ca37 100644 --- a/src/SMESH/SMESH_Gen.hxx +++ b/src/SMESH/SMESH_Gen.hxx @@ -70,6 +70,7 @@ public: ~SMESH_Gen(); SMESH_Mesh* CreateMesh(bool theIsEmbeddedMode); + SMESH_Mesh* CreateParallelMesh(bool theIsEmbeddedMode); enum ComputeFlags { @@ -167,9 +168,7 @@ public: int GetANewId(); -private: - - +public: bool parallelComputeSubMeshes( SMESH_Mesh & aMesh, const TopoDS_Shape & aShape, @@ -191,6 +190,11 @@ private: const bool includeSelf, const bool complexShapeFirst, const bool aShapeOnly); + +private: + + + int _localId; // unique Id of created objects, within SMESH_Gen entity StudyContextStruct* _studyContext; diff --git a/src/SMESH/SMESH_Mesh.cxx b/src/SMESH/SMESH_Mesh.cxx index 82ce58633..ad3d733f0 100644 --- a/src/SMESH/SMESH_Mesh.cxx +++ b/src/SMESH/SMESH_Mesh.cxx @@ -232,8 +232,6 @@ SMESH_Mesh::~SMESH_Mesh() int result=pthread_create(&thread, NULL, deleteMeshDS, (void*)_meshDS); #endif } - if(_pool) - DeletePoolThreads(); } //================================================================================ @@ -2564,30 +2562,3 @@ void SMESH_Mesh::getAncestorsSubMeshes (const TopoDS_Shape& theSubSha // sort submeshes according to stored mesh order SortByMeshOrder( theSubMeshes ); } - - -//============================================================================= -/*! - * \brief Build folder for parallel computation - */ -//============================================================================= -void SMESH_Mesh::CreateTmpFolder() -{ -#ifndef WIN32 - // Temporary folder that will be used by parallel computation - tmp_folder = fs::temp_directory_path()/fs::unique_path(fs::path("SMESH_%%%%-%%%%")); - fs::create_directories(tmp_folder); -#endif -} -// -//============================================================================= -/*! - * \brief Delete temporary folder used for parallel computation - */ -//============================================================================= -void SMESH_Mesh::DeleteTmpFolder() -{ -#ifndef WIN32 - fs::remove_all(tmp_folder); -#endif -} diff --git a/src/SMESH/SMESH_Mesh.hxx b/src/SMESH/SMESH_Mesh.hxx index 9c9606499..c0cbb37b6 100644 --- a/src/SMESH/SMESH_Mesh.hxx +++ b/src/SMESH/SMESH_Mesh.hxx @@ -33,6 +33,7 @@ #include "SMESH_ComputeError.hxx" #include "SMESH_Controls.hxx" #include "SMESH_Hypothesis.hxx" +#include "SMESH_subMesh.hxx" #include "SMDS_Iterator.hxx" #include "Utils_SALOME_Exception.hxx" @@ -72,6 +73,7 @@ class TopoDS_Solid; class DriverMED_W_SMESHDS_Mesh; +typedef std::set TSetOfInt; typedef std::list TListOfInt; typedef std::list TListOfListOfInt; @@ -390,45 +392,32 @@ class SMESH_EXPORT SMESH_Mesh // Parallel computation functions -#ifdef WIN32 - void Lock() {}; - void Unlock() {}; - - int GetNbThreads(){return _NbThreads;}; - void SetNbThreads(long nbThreads){std::cout << "Warning Parallel Meshing is disabled on Windows it will behave as a slower normal compute" << std::endl;_NbThreads=nbThreads;}; - - void InitPoolThreads(){}; - void DeletePoolThreads(){}; - void wait(){} + virtual void Lock(){}; + virtual void Unlock(){}; - bool IsParallel(){return _NbThreads > 0;} -#else - void Lock() {_my_lock.lock();}; - void Unlock() {_my_lock.unlock();}; + virtual int GetNbThreads(){return 0;}; + virtual void SetNbThreads(long nbThreads){(void) nbThreads;}; - int GetNbThreads(){return _NbThreads;}; - void SetNbThreads(long nbThreads){_NbThreads=nbThreads;}; + virtual void InitPoolThreads(){std::cout << "Should not pass here: InitPoolThread" << std::endl;}; + virtual void DeletePoolThreads(){std::cout << "Should not pass here: DeletePoolThread" << std::endl;}; + virtual void wait(){std::cout << "Should not pass here: wait" << std::endl;}; - void InitPoolThreads(){_pool = new boost::asio::thread_pool(_NbThreads);}; - void DeletePoolThreads(){delete _pool;}; + virtual bool IsParallel(){std::cout << "Should not pass here: IsParallel" << std::endl;return false;}; - void wait(){_pool->join(); DeletePoolThreads(); InitPoolThreads(); } - - bool IsParallel(){return _NbThreads > 0;} -#endif - - void CreateTmpFolder(); - void DeleteTmpFolder(); - - // Temporary folder used during parallel Computation -#ifndef WIN32 - boost::filesystem::path tmp_folder; - boost::asio::thread_pool * _pool = nullptr; //thread pool for computation -#else - std::string tmp_folder; - bool _pool = false; -#endif + virtual boost::filesystem::path GetTmpFolder() {return "";}; + virtual boost::asio::thread_pool* GetPool() {return NULL;}; + virtual bool ComputeSubMeshes( + SMESH_Gen* gen, + SMESH_Mesh & aMesh, + const TopoDS_Shape & aShape, + const ::MeshDimension aDim, + TSetOfInt* aShapesId /*=0*/, + TopTools_IndexedMapOfShape* allowedSubShapes, + SMESH_subMesh::compute_event &computeEvent, + const bool includeSelf, + const bool complexShapeFirst, + const bool aShapeOnly){(void) gen;(void) aMesh;(void) aShape;(void) aDim;(void) aShapesId;(void) allowedSubShapes;(void) computeEvent;(void) includeSelf;(void) complexShapeFirst;(void) aShapeOnly;std::cout << "Should not pass here: computesubmesh" << std::endl;return false;}; private: @@ -480,7 +469,7 @@ protected: #ifndef WIN32 boost::mutex _my_lock; #endif - int _NbThreads=0; + int _NbThreads=-1; protected: SMESH_Mesh(); diff --git a/src/SMESH/SMESH_ParallelMesh.cxx b/src/SMESH/SMESH_ParallelMesh.cxx new file mode 100644 index 000000000..4b9f83ce5 --- /dev/null +++ b/src/SMESH/SMESH_ParallelMesh.cxx @@ -0,0 +1,120 @@ +// Copyright (C) 2007-2022 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// File : SMESH_ParallelMesh.cxx +// Author : Yoann AUDOUIN, EDF +// Module : SMESH +// +#include "SMESH_ParallelMesh.hxx" + +#include "SMESH_Gen.hxx" + +#ifdef WIN32 + #include +#endif + +#ifndef WIN32 +#include +namespace fs=boost::filesystem; +#endif + +#ifndef WIN32 +#include +#endif + +#include + +#ifdef _DEBUG_ +static int MYDEBUG = 1; +#else +static int MYDEBUG = 0; +#endif + +SMESH_ParallelMesh::SMESH_ParallelMesh(int theLocalId, + SMESH_Gen* theGen, + bool theIsEmbeddedMode, + SMESHDS_Document* theDocument) :SMESH_Mesh(theLocalId, + theGen, + theIsEmbeddedMode, + theDocument) +{ + MESSAGE("SMESH_ParallelMesh::SMESH_ParallelMesh(int localId)"); + _NbThreads = std::thread::hardware_concurrency(); + CreateTmpFolder(); +}; + +SMESH_ParallelMesh::~SMESH_ParallelMesh() +{ + DeletePoolThreads(); + if(!MYDEBUG) + DeleteTmpFolder(); +}; + + + +//============================================================================= +/*! + * \brief Build folder for parallel computation + */ +//============================================================================= +void SMESH_ParallelMesh::CreateTmpFolder() +{ +#ifndef WIN32 + // Temporary folder that will be used by parallel computation + tmp_folder = fs::temp_directory_path()/fs::unique_path(fs::path("SMESH_%%%%-%%%%")); + fs::create_directories(tmp_folder); +#endif +} +// +//============================================================================= +/*! + * \brief Delete temporary folder used for parallel computation + */ +//============================================================================= +void SMESH_ParallelMesh::DeleteTmpFolder() +{ +#ifndef WIN32 + fs::remove_all(tmp_folder); +#endif +} + +bool SMESH_ParallelMesh::ComputeSubMeshes( + SMESH_Gen* gen, + SMESH_Mesh & aMesh, + const TopoDS_Shape & aShape, + const ::MeshDimension aDim, + TSetOfInt* aShapesId /*=0*/, + TopTools_IndexedMapOfShape* allowedSubShapes, + SMESH_subMesh::compute_event &computeEvent, + const bool includeSelf, + const bool complexShapeFirst, + const bool aShapeOnly) +{ + InitPoolThreads(); + return gen->parallelComputeSubMeshes( + aMesh, aShape, aDim, + aShapesId, allowedSubShapes, + computeEvent, + includeSelf, + complexShapeFirst, + aShapeOnly); +} diff --git a/src/SMESH/SMESH_ParallelMesh.hxx b/src/SMESH/SMESH_ParallelMesh.hxx new file mode 100644 index 000000000..09a520681 --- /dev/null +++ b/src/SMESH/SMESH_ParallelMesh.hxx @@ -0,0 +1,83 @@ +// Copyright (C) 2007-2022 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// File : SMESH_ParallelMesh.hxx +// Author : Yoann AUDOUIN, EDF +// Module : SMESH +// +#ifndef _SMESH_PARALLELMESH_HXX_ +#define _SMESH_PARALLELMESH_HXX_ + +#include "SMESH_Mesh.hxx" + +#include "SMESH_Gen.hxx" +#include "SMESH_subMesh.hxx" + +class SMESH_EXPORT SMESH_ParallelMesh: public SMESH_Mesh +{ + public: + SMESH_ParallelMesh(int theLocalId, + SMESH_Gen* theGen, + bool theIsEmbeddedMode, + SMESHDS_Document* theDocument); + + virtual ~SMESH_ParallelMesh(); + + void Lock() override {_my_lock.lock();}; + void Unlock() override {_my_lock.unlock();}; + + int GetNbThreads() override{return _NbThreads;}; + void SetNbThreads(long nbThreads) override{_NbThreads=nbThreads;}; + + void InitPoolThreads() override {_pool = new boost::asio::thread_pool(_NbThreads);}; + void DeletePoolThreads() override {delete _pool;}; + + void wait() override {_pool->join(); DeletePoolThreads(); InitPoolThreads(); }; + + bool IsParallel() override {return _NbThreads > 0;}; + + void CreateTmpFolder(); + void DeleteTmpFolder(); + + boost::filesystem::path GetTmpFolder() override {return tmp_folder;}; + boost::asio::thread_pool* GetPool() override {return _pool;}; + + bool ComputeSubMeshes( + SMESH_Gen* gen, + SMESH_Mesh & aMesh, + const TopoDS_Shape & aShape, + const ::MeshDimension aDim, + TSetOfInt* aShapesId /*=0*/, + TopTools_IndexedMapOfShape* allowedSubShapes, + SMESH_subMesh::compute_event &computeEvent, + const bool includeSelf, + const bool complexShapeFirst, + const bool aShapeOnly) override; + + protected: + SMESH_ParallelMesh():SMESH_Mesh() {}; + SMESH_ParallelMesh(const SMESH_ParallelMesh& aMesh):SMESH_Mesh(aMesh) {}; + private: + boost::filesystem::path tmp_folder; + boost::asio::thread_pool * _pool = nullptr; //thread pool for computation +}; +#endif diff --git a/src/SMESH/SMESH_SequentialMesh.cxx b/src/SMESH/SMESH_SequentialMesh.cxx new file mode 100644 index 000000000..35023ae50 --- /dev/null +++ b/src/SMESH/SMESH_SequentialMesh.cxx @@ -0,0 +1,69 @@ +// Copyright (C) 2007-2022 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// File : SMESH_SequentialMesh.cxx +// Author : Yoann AUDOUIN, EDF +// Module : SMESH +// +#include "SMESH_SequentialMesh.hxx" + +#include +#include +#include + +#include + +SMESH_SequentialMesh::SMESH_SequentialMesh(int theLocalId, + SMESH_Gen* theGen, + bool theIsEmbeddedMode, + SMESHDS_Document* theDocument) :SMESH_Mesh(theLocalId, + theGen, + theIsEmbeddedMode, + theDocument) +{ + MESSAGE("SMESH_SequentialMesh::SMESH_SequentialMesh(int localId)"); +}; + +SMESH_SequentialMesh::~SMESH_SequentialMesh() +{ +}; + +bool SMESH_SequentialMesh::ComputeSubMeshes( + SMESH_Gen* gen, + SMESH_Mesh & aMesh, + const TopoDS_Shape & aShape, + const ::MeshDimension aDim, + TSetOfInt* aShapesId /*=0*/, + TopTools_IndexedMapOfShape* allowedSubShapes, + SMESH_subMesh::compute_event &computeEvent, + const bool includeSelf, + const bool complexShapeFirst, + const bool aShapeOnly) +{ + return gen->sequentialComputeSubMeshes( + aMesh, aShape, aDim, + aShapesId, allowedSubShapes, + computeEvent, + includeSelf, + complexShapeFirst, + aShapeOnly); +}; diff --git a/src/SMESH/SMESH_SequentialMesh.hxx b/src/SMESH/SMESH_SequentialMesh.hxx new file mode 100644 index 000000000..90cf904ce --- /dev/null +++ b/src/SMESH/SMESH_SequentialMesh.hxx @@ -0,0 +1,73 @@ +// Copyright (C) 2007-2022 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// File : SMESH_SequentialMesh.hxx +// Author : Yoann AUDOUIN, EDF +// Module : SMESH +// +#ifndef _SMESH_SEQUENTIALMESH_HXX_ +#define _SMESH_SEQUENTIALMESH_HXX_ + +#include "SMESH_Mesh.hxx" + +#include "SMESH_Gen.hxx" +#include "SMESH_subMesh.hxx" + +class SMESH_EXPORT SMESH_SequentialMesh: public SMESH_Mesh +{ + public: + SMESH_SequentialMesh(int theLocalId, + SMESH_Gen* theGen, + bool theIsEmbeddedMode, + SMESHDS_Document* theDocument); + + virtual ~SMESH_SequentialMesh(); + + void Lock() override {}; + void Unlock() override {}; + + int GetNbThreads() override {return 0;}; + void SetNbThreads(long nbThreads) {(void) nbThreads;}; + + void InitPoolThreads() override {}; + void DeletePoolThreads() override {}; + void wait() override {}; + + bool IsParallel() override {return false;}; + + bool ComputeSubMeshes ( + SMESH_Gen* gen, + SMESH_Mesh & aMesh, + const TopoDS_Shape & aShape, + const ::MeshDimension aDim, + TSetOfInt* aShapesId /*=0*/, + TopTools_IndexedMapOfShape* allowedSubShapes, + SMESH_subMesh::compute_event &computeEvent, + const bool includeSelf, + const bool complexShapeFirst, + const bool aShapeOnly) override; + + protected: + SMESH_SequentialMesh():SMESH_Mesh() {}; + SMESH_SequentialMesh(const SMESH_SequentialMesh& aMesh):SMESH_Mesh(aMesh) {}; +}; +#endif diff --git a/src/SMESH/SMESH_subMesh.cxx b/src/SMESH/SMESH_subMesh.cxx index 76dfb83e1..bc3aa8792 100644 --- a/src/SMESH/SMESH_subMesh.cxx +++ b/src/SMESH/SMESH_subMesh.cxx @@ -1515,7 +1515,8 @@ bool SMESH_subMesh::ComputeStateEngine(compute_event event) TopoDS_Shape shape = _subShape; algo->setSubMeshesToCompute(this); // check submeshes needed - // In parallel there would be no submesh to check + // When computing in parallel mode we do not have a additional layer of submesh + // The check should not be done in parallel as that check is not thread-safe if (_father->HasShapeToMesh() && !_father->IsParallel()) { bool subComputed = false, subFailed = false; if (!algo->OnlyUnaryInput()) { diff --git a/src/SMESH_I/CMakeLists.txt b/src/SMESH_I/CMakeLists.txt index 218cb915e..f525f022c 100644 --- a/src/SMESH_I/CMakeLists.txt +++ b/src/SMESH_I/CMakeLists.txt @@ -67,7 +67,7 @@ SET(_link_LIBRARIES ${KERNEL_Registry} ${KERNEL_SalomeHDFPersist} ${KERNEL_SalomeLifeCycleCORBA} - ${KERNEL_TOOLSDS} + ${KERNEL_TOOLSDS} ${KERNEL_SalomeGenericObj} ${KERNEL_SalomeIDLKERNEL} ${KERNEL_SALOMELocalTrace} @@ -115,6 +115,8 @@ SET(SMESHEngine_HEADERS SMESH.hxx MG_ADAPT_i.hxx SMESH_Homard_i.hxx + SMESH_SequentialMesh_i.hxx + SMESH_ParallelMesh_i.hxx ) # --- sources --- diff --git a/src/SMESH_I/SMESH_Gen_i.cxx b/src/SMESH_I/SMESH_Gen_i.cxx index 32265c514..0bf885f35 100644 --- a/src/SMESH_I/SMESH_Gen_i.cxx +++ b/src/SMESH_I/SMESH_Gen_i.cxx @@ -107,6 +107,8 @@ #include "SMESH_PythonDump.hxx" #include "SMESH_ControlsDef.hxx" #include +#include +#include // to pass CORBA exception through SMESH_TRY #define SMY_OWN_CATCH catch( SALOME::SALOME_Exception& se ) { throw se; } @@ -560,7 +562,7 @@ SMESH::SMESH_Hypothesis_ptr SMESH_Gen_i::createHypothesis(const char* theHypName */ //============================================================================= -SMESH::SMESH_Mesh_ptr SMESH_Gen_i::createMesh() +SMESH::SMESH_Mesh_ptr SMESH_Gen_i::createMesh(bool parallel /*=false*/) { Unexpect aCatch(SALOME_SalomeException); MESSAGE( "SMESH_Gen_i::createMesh" ); @@ -571,7 +573,11 @@ SMESH::SMESH_Mesh_ptr SMESH_Gen_i::createMesh() SMESH_Mesh_i* meshServant = new SMESH_Mesh_i( GetPOA(), this ); // create a new mesh object MESSAGE("myIsEmbeddedMode " << myIsEmbeddedMode); - meshServant->SetImpl( myGen.CreateMesh( myIsEmbeddedMode )); + if(parallel) { + meshServant->SetImpl( dynamic_cast(myGen.CreateParallelMesh( myIsEmbeddedMode ))); + }else{ + meshServant->SetImpl( dynamic_cast(myGen.CreateMesh( myIsEmbeddedMode ))); + } // activate the CORBA servant of Mesh SMESH::SMESH_Mesh_var mesh = SMESH::SMESH_Mesh::_narrow( meshServant->_this() ); @@ -1198,7 +1204,7 @@ char* SMESH_Gen_i::GetOption(const char* name) SMESH::SMESH_Mesh_ptr SMESH_Gen_i::CreateMesh( GEOM::GEOM_Object_ptr theShapeObject ) { Unexpect aCatch(SALOME_SalomeException); - MESSAGE( "SMESH_Gen_i::CreateMesh" ); + MESSAGE( "SMESH_Gen_i::CreateMesh(GEOM_Object_ptr)" ); // create mesh SMESH::SMESH_Mesh_var mesh = this->createMesh(); // set shape @@ -1221,6 +1227,40 @@ SMESH::SMESH_Mesh_ptr SMESH_Gen_i::CreateMesh( GEOM::GEOM_Object_ptr theShapeObj return mesh._retn(); } +//============================================================================= +/*! + * SMESH_Gen_i::CreateParallelMesh + * + * Create empty parallel mesh on a shape and publish it in the study + */ +//============================================================================= + +SMESH::SMESH_Mesh_ptr SMESH_Gen_i::CreateParallelMesh( GEOM::GEOM_Object_ptr theShapeObject ) +{ + Unexpect aCatch(SALOME_SalomeException); + MESSAGE( "SMESH_Gen_i::CreateParallelMesh" ); + // create mesh + SMESH::SMESH_Mesh_var mesh = this->createMesh(true); + // set shape + SMESH_Mesh_i* meshServant = SMESH::DownCast( mesh ); + ASSERT( meshServant ); + meshServant->SetShape( theShapeObject ); + + // publish mesh in the study + if ( CanPublishInStudy( mesh ) ) { + SALOMEDS::StudyBuilder_var aStudyBuilder = getStudyServant()->NewBuilder(); + aStudyBuilder->NewCommand(); // There is a transaction + SALOMEDS::SObject_wrap aSO = PublishMesh( mesh.in() ); + aStudyBuilder->CommitCommand(); + if ( !aSO->_is_nil() ) { + // Update Python script + TPythonDump(this) << aSO << " = " << this << ".CreateMesh(" << theShapeObject << ")"; + } + } + + return mesh._retn(); +} + //============================================================================= /*! * SMESH_Gen_i::CreateEmptyMesh @@ -1232,7 +1272,7 @@ SMESH::SMESH_Mesh_ptr SMESH_Gen_i::CreateMesh( GEOM::GEOM_Object_ptr theShapeObj SMESH::SMESH_Mesh_ptr SMESH_Gen_i::CreateEmptyMesh() { Unexpect aCatch(SALOME_SalomeException); - MESSAGE( "SMESH_Gen_i::CreateMesh" ); + MESSAGE( "SMESH_Gen_i::CreateEmptyMesh" ); // create mesh SMESH::SMESH_Mesh_var mesh = this->createMesh(); diff --git a/src/SMESH_I/SMESH_Gen_i.hxx b/src/SMESH_I/SMESH_Gen_i.hxx index 822e71f2e..f793d4f1b 100644 --- a/src/SMESH_I/SMESH_Gen_i.hxx +++ b/src/SMESH_I/SMESH_Gen_i.hxx @@ -231,6 +231,9 @@ public: // Create empty mesh on a shape SMESH::SMESH_Mesh_ptr CreateMesh( GEOM::GEOM_Object_ptr theShapeObject ); + // Create empty parallel mesh on a shape + SMESH::SMESH_Mesh_ptr CreateParallelMesh( GEOM::GEOM_Object_ptr theShapeObject ); + // Create empty mesh SMESH::SMESH_Mesh_ptr CreateEmptyMesh(); @@ -631,7 +634,7 @@ private: SMESH::SMESH_Hypothesis_ptr createHypothesis( const char* theHypName, const char* theLibName); // Create empty mesh on shape - SMESH::SMESH_Mesh_ptr createMesh(); + SMESH::SMESH_Mesh_ptr createMesh(bool parallel=false); // Check mesh icon bool isGeomModifIcon( SMESH::SMESH_Mesh_ptr mesh ); diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index 7d4bcb418..d101f14dc 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -7037,6 +7037,15 @@ void SMESH_Mesh_i::SetNbThreads(CORBA::Long nbThreads){ _impl->SetNbThreads(nbThreads); } +//============================================================================= +/*! + * \brief Get the number of threads for a parallel computation + */ +//============================================================================= +CORBA::Long SMESH_Mesh_i::GetNbThreads(){ + return _impl->GetNbThreads(); +} + //============================================================================= /*! diff --git a/src/SMESH_I/SMESH_Mesh_i.hxx b/src/SMESH_I/SMESH_Mesh_i.hxx index 45928272d..d9a82e3c6 100644 --- a/src/SMESH_I/SMESH_Mesh_i.hxx +++ b/src/SMESH_I/SMESH_Mesh_i.hxx @@ -673,7 +673,11 @@ private: SMESH::submesh_array_array& theSubMeshOrder, const bool theIsDump); + /*! + * Parallelims informations + */ void SetNbThreads(CORBA::Long nbThreads); + CORBA::Long GetNbThreads(); /*! * \brief Finds concurrent sub-meshes diff --git a/src/SMESH_SWIG/smeshBuilder.py b/src/SMESH_SWIG/smeshBuilder.py index 972a4507c..8d0a81ac5 100644 --- a/src/SMESH_SWIG/smeshBuilder.py +++ b/src/SMESH_SWIG/smeshBuilder.py @@ -462,20 +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): + def ParallelMesh(self, obj, name=0, split_geom=True): """ 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. + split_geom: If True split the geometry and create the assoicated + sub meshes Returns: an instance of class :class:`ParallelMesh`. """ - return ParallelMesh(self, self.geompyD, obj, param, nbThreads, name) + return ParallelMesh(self, self.geompyD, obj, + split_geom=split_geom, name=name) def RemoveMesh( self, mesh ): """ @@ -1592,7 +1593,7 @@ class Mesh(metaclass = MeshMeta): mesh = 0 editor = 0 - def __init__(self, smeshpyD, geompyD, obj=0, name=0): + def __init__(self, smeshpyD, geompyD, obj=0, name=0, parallel=False): """ Constructor @@ -1625,7 +1626,10 @@ class Mesh(metaclass = MeshMeta): else: geo_name = "%s_%s to mesh"%(self.geom.GetShapeType(), id(self.geom)%100) geompyD.addToStudy( self.geom, geo_name ) - self.SetMesh( self.smeshpyD.CreateMesh(self.geom) ) + if parallel and isinstance(self, ParallelMesh): + self.SetMesh( self.smeshpyD.CreateParallelMesh(self.geom) ) + else: + self.SetMesh( self.smeshpyD.CreateMesh(self.geom) ) elif isinstance(obj, SMESH._objref_SMESH_Mesh): self.SetMesh(obj) @@ -7501,6 +7505,9 @@ class Mesh(metaclass = MeshMeta): def _copy_netgen_param(dim, local_param, global_param): + """ + Create 1D/2D/3D netgen parameters from a NETGEN 1D2D3D parameter + """ if dim==1: #TODO: Try to identify why we need to substract 1 local_param.NumberOfSegments(int(global_param.GetNbSegPerEdge())-1) @@ -7532,19 +7539,95 @@ def _copy_netgen_param(dim, local_param, global_param): local_param.SetGrowthRate(global_param.GetGrowthRate()) local_param.SetNbThreads(global_param.GetNbThreads()) +def _split_geom(geompyD, geom): + """ + Splitting geometry into n solids and a 2D/1D compound + + Parameters: + geompyD: geomBuilder instance + geom: geometrical object for meshing + + """ + # 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) + # If geom is a solid ExtractShapes will return nothin in that case geom is the solids + if isolid == 0: + solids = [geom] + + 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') + + return all_faces, solids + +class ParallelismSettings: + """ + Defines the parameters for the parallelism of ParallelMesh + """ + def __init__(self, mesh): + """ + Construsctor + + Parameters: + mesh: Instance of ParallelMesh + """ + if not(isinstance(mesh, ParallelMesh)): + raise ValueError("mesh should be a ParallelMesh") + + self._mesh = mesh + + def SetNbThreads(self, nbThreads): + """ + Set the number of threads for multithreading + """ + if nbThreads < 1: + raise ValueError("Number of threads must be stricly greater than 1") + + self._mesh.mesh.SetNbThreads(nbThreads) + + def GetNbThreads(self): + """ + Get Number of threads + """ + return self._mesh.mesh.GetNbThreads() + class ParallelMesh(Mesh): """ Surcharge on Mesh for parallel computation of a mesh """ - - def __init__(self, smeshpyD, geompyD, geom, param, nbThreads, name=0): + def __init__(self, smeshpyD, geompyD, geom, split_geom=True, name=0): """ Create a parallel mesh. Parameters: + smeshpyD: instance of smeshBuilder + geompyD: instance of geomBuilder geom: geometrical object for meshing - param: full mesh parameters - nbThreads: Number of threads for parallelisation. + split_geom: If true will divide geometry on solids and 1D/2D + coumpound and create the associated submeshes name: the name for the new mesh. Returns: @@ -7554,63 +7637,56 @@ class ParallelMesh(Mesh): 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") + # Splitting geometry into one geom containing 1D and 2D elements and a + # list of 3D elements + super(ParallelMesh, self).__init__(smeshpyD, geompyD, geom, name, parallel=True) - if nbThreads < 1: - raise ValueError("Number of threads must be stricly greater than 1") + if split_geom: + self._all_faces, self._solids = _split_geom(geompyD, geom) - # Splitting geometry into 3D elements and all the 2D/1D into one compound - object_solids = geompyD.ExtractShapes(geom, geompyD.ShapeType["SOLID"], - True) + self.UseExistingSegments() + self.UseExistingFaces() - solids = [] - isolid = 0 - for solid in object_solids: - isolid += 1 - geompyD.addToStudyInFather( geom, solid, 'Solid_{}'.format(isolid) ) - solids.append(solid) + self._algo2d = self.Triangle(geom=self._all_faces, algo="NETGEN_2D") + self._algo3d = [] - 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)) + for solid_id, solid in enumerate(self._solids): + name = "Solid_{}".format(solid_id) + self.UseExistingSegments(geom=solid) + self.UseExistingFaces(geom=solid) + algo3d = self.Tetrahedron(geom=solid, algo="NETGEN_3D_Remote") + self._algo3d.append(algo3d) - # Creating submesh for edges 1D/2D part + self._param = ParallelismSettings(self) - 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) + def GetParallelismSettings(self): + """ + Return class to set parameters for the parallelism + """ + return self._param - self.mesh.SetNbThreads(nbThreads) + def AddGlobalHypothesis(self, hyp): + """ + Split hypothesis to apply it to all the submeshes: + - the 1D+2D + - each of the 3D solids - self.UseExistingSegments() - self.UseExistingFaces() + Parameters: + hyp: a hypothesis to assign - algo2d = self.Triangle(geom=all_faces, algo="NETGEN_2D") - param2d = algo2d.Parameters() + """ + if not isinstance(hyp, NETGENPlugin._objref_NETGENPlugin_Hypothesis): + raise ValueError("param must come from NETGENPlugin") - _copy_netgen_param(2, param2d, param) + param2d = self._algo2d.Parameters() + _copy_netgen_param(2, param2d, hyp) - 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") + for algo3d in self._algo3d: param3d = algo3d.Parameters() + _copy_netgen_param(3, param3d, hyp) - _copy_netgen_param(3, param3d, param) pass # End of ParallelMesh diff --git a/src/StdMeshers/StdMeshers_Prism_3D.cxx b/src/StdMeshers/StdMeshers_Prism_3D.cxx index 4caad58b4..005be9abb 100644 --- a/src/StdMeshers/StdMeshers_Prism_3D.cxx +++ b/src/StdMeshers/StdMeshers_Prism_3D.cxx @@ -95,7 +95,7 @@ namespace /*! * \brief Auxiliary mesh */ - struct TmpMesh: public SMESH_Mesh + struct TmpMesh: public SMESH_SequentialMesh { TmpMesh() { _isShapeToMesh = (_id = 0); @@ -5075,7 +5075,7 @@ void StdMeshers_PrismAsBlock::THorizontalEdgeAdaptor::dumpNodes(int nbNodes) con { if (!SALOME::VerbosityActivated()) return; - + // Not bedugged code. Last node is sometimes incorrect const TSideFace* side = mySide; double u = 0; diff --git a/src/StdMeshers/StdMeshers_Prism_3D.hxx b/src/StdMeshers/StdMeshers_Prism_3D.hxx index d2bea83f4..9a7398e91 100644 --- a/src/StdMeshers/StdMeshers_Prism_3D.hxx +++ b/src/StdMeshers/StdMeshers_Prism_3D.hxx @@ -33,6 +33,7 @@ #include "SMESH_Block.hxx" #include "SMESH_Comment.hxx" #include "SMESH_Mesh.hxx" +#include "SMESH_SequentialMesh.hxx" #include "SMESH_MesherHelper.hxx" #include "SMESH_TypeDefs.hxx" #include "SMESH_subMesh.hxx" @@ -117,7 +118,7 @@ namespace Prism_3D // =============================================================== /*! - * \brief Tool analyzing and giving access to a prism geometry + * \brief Tool analyzing and giving access to a prism geometry * treating it like a block, i.e. the four side faces are * emulated by division/uniting of missing/excess faces. * It also manage associations between block sub-shapes and a mesh. @@ -200,7 +201,7 @@ class STDMESHERS_EXPORT StdMeshers_PrismAsBlock: public SMESH_Block */ bool GetLayersTransformation(std::vector & trsf, const Prism_3D::TPrismTopo& prism) const; - + /*! * \brief Return pointer to mesh * \retval SMESH_Mesh - mesh @@ -389,7 +390,7 @@ private: SMESH_ComputeErrorPtr myError; // container of 4 side faces - TSideFace* mySide; + TSideFace* mySide; // node columns for each base edge std::vector< TParam2ColumnMap > myParam2ColumnMaps; // to find a column for a node by edge SMESHDS Index diff --git a/src/StdMeshers/StdMeshers_Projection_2D.cxx b/src/StdMeshers/StdMeshers_Projection_2D.cxx index 589112b47..f3e01a338 100644 --- a/src/StdMeshers/StdMeshers_Projection_2D.cxx +++ b/src/StdMeshers/StdMeshers_Projection_2D.cxx @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -324,7 +325,7 @@ namespace { break; } case TopAbs_EDGE: { - + // Get submeshes of sub-vertices const map< int, SMESH_subMesh * >& subSM = sm->DependsOn(); if ( subSM.size() != 2 ) @@ -711,7 +712,7 @@ namespace { while ( elemIt->more() ) // loop on all mesh faces on srcFace { const SMDS_MeshElement* elem = elemIt->next(); - const int nbN = elem->NbCornerNodes(); + const int nbN = elem->NbCornerNodes(); tgtNodes.resize( nbN ); helper->SetElementsOnShape( false ); for ( int i = 0; i < nbN; ++i ) // loop on nodes of the source element @@ -1096,7 +1097,7 @@ namespace { */ //================================================================================ - struct QuadMesh : public SMESH_Mesh + struct QuadMesh : public SMESH_SequentialMesh { ObjectPool< TriaCoordSys > _traiLCSPool; SMESH_ElementSearcher* _elemSearcher; @@ -1428,7 +1429,7 @@ namespace { // const SMDS_MeshElement* elem = elemIt->next(); // TFaceConn& tgtNodes = newFacesVec[ iFaceSrc++ ]; - // const int nbN = elem->NbCornerNodes(); + // const int nbN = elem->NbCornerNodes(); // tgtNodes.resize( nbN ); // for ( int i = 0; i < nbN; ++i ) // loop on nodes of the source element // { @@ -1442,7 +1443,7 @@ namespace { // { // tgtNodeOrXY.first = srcN_tgtN->second; // tgt node exists // } - // else + // else // { // // find XY of src node within the quadrilateral srcFace // if ( !block.ComputeParameters( SMESH_TNodeXYZ( srcNode ), diff --git a/src/StdMeshers/StdMeshers_QuadFromMedialAxis_1D2D.cxx b/src/StdMeshers/StdMeshers_QuadFromMedialAxis_1D2D.cxx index 5cdf93f2b..a14c51377 100644 --- a/src/StdMeshers/StdMeshers_QuadFromMedialAxis_1D2D.cxx +++ b/src/StdMeshers/StdMeshers_QuadFromMedialAxis_1D2D.cxx @@ -30,6 +30,7 @@ #include "SMESH_Gen.hxx" #include "SMESH_MAT2d.hxx" #include "SMESH_Mesh.hxx" +#include "SMESH_SequentialMesh.hxx" #include "SMESH_MeshEditor.hxx" #include "SMESH_MesherHelper.hxx" #include "SMESH_ProxyMesh.hxx" @@ -144,7 +145,7 @@ public: return true; } }; - + //================================================================================ /*! * \brief Constructor sets algo features @@ -203,7 +204,7 @@ bool StdMeshers_QuadFromMedialAxis_1D2D::CheckHypothesis(SMESH_Mesh& aMe namespace { typedef map< const SMDS_MeshNode*, list< const SMDS_MeshNode* > > TMergeMap; - + //================================================================================ /*! * \brief Sinuous face @@ -236,7 +237,7 @@ namespace /*! * \brief Temporary mesh */ - struct TmpMesh : public SMESH_Mesh + struct TmpMesh : public SMESH_SequentialMesh { TmpMesh() { @@ -506,7 +507,7 @@ namespace theSinuEdges[1].clear(); theShortEdges[0].clear(); theShortEdges[1].clear(); - + vector & allEdges = theSinuFace._edges; const size_t nbEdges = allEdges.size(); if ( nbEdges < 4 && theSinuFace._nbWires == 1 ) @@ -841,7 +842,7 @@ namespace // Find 1D algo to mesh branchEdge - + // look for a most local 1D hyp assigned to the FACE int mostSimpleShape = -1, maxShape = TopAbs_EDGE; TopoDS_Edge edge; @@ -1450,7 +1451,7 @@ namespace nIn = nodeParams.rbegin()->second; else nIn = u2n->second; - + // find position of distant nodes in uvsOut and uvsIn size_t iDistOut, iDistIn; for ( iDistOut = 0; iDistOut < uvsOut.size(); ++iDistOut ) @@ -2151,6 +2152,7 @@ bool StdMeshers_QuadFromMedialAxis_1D2D::computeQuads( SMESH_MesherHelper& theHe bool StdMeshers_QuadFromMedialAxis_1D2D::Compute(SMESH_Mesh& theMesh, const TopoDS_Shape& theShape) { + std::cout << "helper_quad " << theMesh.IsParallel() << std::endl; SMESH_MesherHelper helper( theMesh ); helper.SetSubShape( theShape ); diff --git a/src/StdMeshers/StdMeshers_RadialQuadrangle_1D2D.cxx b/src/StdMeshers/StdMeshers_RadialQuadrangle_1D2D.cxx index ff5906d53..a97f274a2 100644 --- a/src/StdMeshers/StdMeshers_RadialQuadrangle_1D2D.cxx +++ b/src/StdMeshers/StdMeshers_RadialQuadrangle_1D2D.cxx @@ -70,7 +70,7 @@ using namespace std; //======================================================================= //function : StdMeshers_RadialQuadrangle_1D2D -//purpose : +//purpose : //======================================================================= StdMeshers_RadialQuadrangle_1D2D::StdMeshers_RadialQuadrangle_1D2D(int hypId, @@ -103,7 +103,7 @@ StdMeshers_RadialQuadrangle_1D2D::~StdMeshers_RadialQuadrangle_1D2D() //======================================================================= //function : CheckHypothesis -//purpose : +//purpose : //======================================================================= bool StdMeshers_RadialQuadrangle_1D2D::CheckHypothesis @@ -111,7 +111,7 @@ bool StdMeshers_RadialQuadrangle_1D2D::CheckHypothesis const TopoDS_Shape& aShape, SMESH_Hypothesis::Hypothesis_Status& aStatus) { - // check aShape + // check aShape myNbLayerHypo = 0; myDistributionHypo = 0; @@ -271,7 +271,7 @@ namespace sideEdges.splice( sideEdges.end(), edges, edges.begin() ); StdMeshers_FaceSidePtr side; - if ( aMesh ) + if ( aMesh ) side = StdMeshers_FaceSide::New( face, sideEdges, aMesh, /*isFwd=*/true, /*skipMedium=*/ true, helper ); sides.push_back( side ); @@ -355,7 +355,7 @@ namespace } } - int iCirc = deviation2sideInd.rbegin()->second; + int iCirc = deviation2sideInd.rbegin()->second; aCircSide = sides[ iCirc ]; aLinSide1 = sides[( iCirc + 1 ) % sides.size() ]; if ( sides.size() > 2 ) @@ -958,7 +958,7 @@ bool StdMeshers_RadialQuadrangle_1D2D::Compute(SMESH_Mesh& aMesh, centerUV = nodes.back().UV(); } // ------------------------------------------------------------------------------------------ - else // nbSides == 3 + else // nbSides == 3 { // one curve must be a part of ellipse and 2 other curves must be segments of line @@ -1082,7 +1082,7 @@ int StdMeshers_RadialQuadrangle_1D2D::computeLayerPositions(StdMeshers_FaceSideP if ( !TNodeDistributor::GetDistributor(*mesh)->Compute( positions, linSide->Edge(0), *curve, f, l, *mesh, hyp1D )) { - if ( myDistributionHypo ) { // bad hyp assigned + if ( myDistributionHypo ) { // bad hyp assigned return error( TNodeDistributor::GetDistributor(*mesh)->GetComputeError() ); } else { @@ -1090,7 +1090,7 @@ int StdMeshers_RadialQuadrangle_1D2D::computeLayerPositions(StdMeshers_FaceSideP } } } - + if ( positions.empty() ) // try to use nb of layers { if ( !nbLayers ) @@ -1132,7 +1132,7 @@ int StdMeshers_RadialQuadrangle_1D2D::computeLayerPositions(StdMeshers_FaceSideP //======================================================================= //function : Evaluate -//purpose : +//purpose : //======================================================================= bool StdMeshers_RadialQuadrangle_1D2D::Evaluate(SMESH_Mesh& aMesh, @@ -1193,7 +1193,7 @@ bool StdMeshers_RadialQuadrangle_1D2D::Evaluate(SMESH_Mesh& aMesh, return false; } // ------------------------------------------------------------------------------------------ - else // nbSides == 3 + else // nbSides == 3 { if ( !computeLayerPositions(( linSide1->Length() > linSide2->Length() ) ? linSide1 : linSide2, layerPositions )) @@ -1217,7 +1217,7 @@ bool StdMeshers_RadialQuadrangle_1D2D::Evaluate(SMESH_Mesh& aMesh, if ( SMDSEntity_Quad_Edge < (int) nbElems.size() ) nbCircSegments += ( nbElems[ SMDSEntity_Edge ] + nbElems[ SMDSEntity_Quad_Edge ]); } - + smIdType nbQuads = nbCircSegments * ( layerPositions.size() - 1 ); smIdType nbTria = nbCircSegments; smIdType nbNodes = ( nbCircSegments - 1 ) * ( layerPositions.size() - 2 ); diff --git a/test/tests.set b/test/tests.set index 0035933fe..b14e7c200 100644 --- a/test/tests.set +++ b/test/tests.set @@ -65,7 +65,6 @@ SET(BAD_TESTS SMESH_create_dual_mesh_adapt.py SMESH_create_dual_mesh_tpipe.py netgen_runner.py - SMESH_ParallelCompute.py ) IF(NOT WIN32) LIST(APPEND BAD_TESTS -- 2.30.2