Branch data Line data Source code
1 : : // *****************************************************************************
2 : : /*!
3 : : \file src/Inciter/Partitioner.cpp
4 : : \copyright 2012-2015 J. Bakosi,
5 : : 2016-2018 Los Alamos National Security, LLC.,
6 : : 2019-2021 Triad National Security, LLC.,
7 : : 2022-2025 J. Bakosi
8 : : All rights reserved. See the LICENSE file for details.
9 : : \brief Charm++ chare partitioner nodegroup used to perform mesh
10 : : partitioning
11 : : \details Charm++ chare partitioner nodegroup used to perform mesh read and
12 : : partitioning, one worker per compute node.
13 : : */
14 : : // *****************************************************************************
15 : :
16 : : #include <numeric>
17 : :
18 : : #include "PUPUtil.hpp"
19 : : #include "Partitioner.hpp"
20 : : #include "DerivedData.hpp"
21 : : #include "Reorder.hpp"
22 : : #include "ExodusIIMeshReader.hpp"
23 : : #include "UnsMesh.hpp"
24 : : #include "ContainerUtil.hpp"
25 : : #include "Callback.hpp"
26 : : #include "ZoltanGeom.hpp"
27 : : #include "ZoltanGraph.hpp"
28 : : #include "InciterConfig.hpp"
29 : : #include "PartsReducer.hpp"
30 : : #include "Around.hpp"
31 : :
32 : : namespace inciter {
33 : :
34 : : static CkReduction::reducerType PartsMerger;
35 : : extern ctr::Config g_cfg;
36 : :
37 : : } // inciter::
38 : :
39 : : using inciter::Partitioner;
40 : :
41 : 770 : Partitioner::Partitioner(
42 : : std::size_t meshid,
43 : : const std::string& filename,
44 : : const tk::PartitionerCallback& cbp,
45 : : const tk::RefinerCallback& cbr,
46 : : const tk::SorterCallback& cbs,
47 : : const CProxy_Transporter& host,
48 : : const CProxy_Refiner& refiner,
49 : : const CProxy_Sorter& sorter,
50 : : const tk::CProxy_MeshWriter& meshwriter,
51 : : const std::vector< CProxy_Discretization >& discretization,
52 : : const CProxy_RieCG& riecg,
53 : : const CProxy_LaxCG& laxcg,
54 : : const CProxy_ZalCG& zalcg,
55 : : const CProxy_KozCG& kozcg,
56 : : const CProxy_ChoCG& chocg,
57 : : const CProxy_LohCG& lohcg,
58 : : const tk::CProxy_ConjugateGradients& cgpre,
59 : : const tk::CProxy_ConjugateGradients& cgmom,
60 : : const std::map< int, std::vector< std::size_t > >& bface,
61 : : const std::map< int, std::vector< std::size_t > >& faces,
62 : 770 : const std::map< int, std::vector< std::size_t > >& bnode ) :
63 : 770 : m_meshid( meshid ),
64 : 770 : m_npsup( 0 ),
65 : 770 : m_cbp( cbp ),
66 : 770 : m_cbr( cbr ),
67 : 770 : m_cbs( cbs ),
68 : 770 : m_host( host ),
69 [ + - ]: 770 : m_refiner( refiner ),
70 [ + - ]: 770 : m_sorter( sorter ),
71 [ + - ]: 770 : m_meshwriter( meshwriter ),
72 [ + - ]: 770 : m_discretization( discretization ),
73 [ + - ]: 770 : m_riecg( riecg ),
74 [ + - ]: 770 : m_laxcg( laxcg ),
75 [ + - ]: 770 : m_zalcg( zalcg ),
76 [ + - ]: 770 : m_kozcg( kozcg ),
77 [ + - ]: 770 : m_chocg( chocg ),
78 [ + - ]: 770 : m_lohcg( lohcg ),
79 [ + - ]: 770 : m_cgpre( cgpre ),
80 [ + - ]: 770 : m_cgmom( cgmom ),
81 : 770 : m_ndist( 0 ),
82 : 770 : m_nchare( 0 ),
83 [ + - ]: 770 : m_bface( bface ),
84 [ + - ][ + - ]: 2310 : m_bnode( bnode )
85 : : // *****************************************************************************
86 : : // Constructor
87 : : //! \param[in] meshid Mesh ID
88 : : //! \param[in] filename Input mesh filename to read from
89 : : //! \param[in] cbp Charm++ callbacks for Partitioner
90 : : //! \param[in] cbr Charm++ callbacks for Refiner
91 : : //! \param[in] cbs Charm++ callbacks for Sorter
92 : : //! \param[in] host Host Charm++ proxy we are being called from
93 : : //! \param[in] refiner Mesh refiner proxy
94 : : //! \param[in] sorter Mesh reordering (sorter) proxy
95 : : //! \param[in] meshwriter Mesh writer proxy
96 : : //! \param[in] discretization Discretization proxy for all meshes
97 : : //! \param[in] riecg Discretization scheme
98 : : //! \param[in] laxcg Discretization scheme
99 : : //! \param[in] zalcg Discretization scheme
100 : : //! \param[in] kozcg Discretization scheme
101 : : //! \param[in] chocg Discretization scheme
102 : : //! \param[in] lohcg Discretization scheme
103 : : //! \param[in] cgpre ConjugateGradients Charm++ proxy for pressure solve
104 : : //! \param[in] cgmom ConjugateGradients Charm++ proxy for momentum solve
105 : : //! \param[in] bface File-internal elem ids of side sets (whole mesh)
106 : : //! \param[in] faces Elem-relative face ids of side sets (whole mesh)
107 : : //! \param[in] bnode Node lists of side sets (whole mesh)
108 : : // *****************************************************************************
109 : : {
110 : : // Create mesh reader
111 [ + - ]: 770 : tk::ExodusIIMeshReader mr( filename );
112 : :
113 : : // Read this compute node's chunk of the mesh (graph and coords) from file
114 : 770 : std::vector< std::size_t > triinpoel;
115 [ + - ]: 770 : mr.readMeshPart( m_ginpoel, m_inpoel, triinpoel, m_lid, m_coord,
116 : : CkNumNodes(), CkMyNode() );
117 : :
118 : : // Compute triangle connectivity for side sets, reduce boundary face for side
119 : : // sets to this compute node only and to compute-node-local face ids
120 [ + - ]: 770 : m_triinpoel = mr.triinpoel( m_bface, faces, m_ginpoel, triinpoel );
121 : :
122 : : // Keep those nodes for side sets that reside on this compute node only
123 : 770 : std::map< int, std::vector< std::size_t > > own_bnode;
124 [ + + ]: 2723 : for (const auto& [ setid, nodes ] : m_bnode) {
125 [ + - ]: 1953 : auto& b = own_bnode[ setid ];
126 [ + + ]: 293515 : for (auto n : nodes) {
127 [ + - ]: 291562 : auto i = m_lid.find( n );
128 [ + + ][ + - ]: 291562 : if (i != end(m_lid)) b.push_back( n );
129 : : }
130 [ + + ][ + - ]: 1953 : if (b.empty()) own_bnode.erase( setid );
131 : : }
132 : 770 : m_bnode = std::move(own_bnode);
133 : :
134 : : // Compute unqiue mesh graph if needed
135 : : std::unordered_map< int, std::unordered_map< std::size_t,
136 : 770 : std::unordered_set< std::size_t > > > graph;
137 : 770 : bool multi = g_cfg.get< tag::input >().size() > 1;
138 [ - + ]: 770 : const auto& alg = multi ? g_cfg.get< tag::part_ >()[ m_meshid ]
139 : 770 : : g_cfg.get< tag::part >();
140 [ + + ]: 770 : if ( alg == "phg" ) {
141 : : // Generate global node ids for the mesh on this compute node
142 [ + - ]: 19 : const auto gid = tk::uniquecopy( m_ginpoel );
143 : : // Generate points surrounding points of this sub-graph with local node ids
144 [ + - ][ + - ]: 19 : const auto psup = tk::genPsup( m_inpoel, 4, tk::genEsup(m_inpoel,4) );
145 : : // Get total number of mesh nodes in mesh file
146 [ + - ]: 19 : auto npoin = mr.npoin();
147 : : // Compute the number of nodes (chunksize) a node will build mesh graph for
148 : 19 : auto N = static_cast< std::size_t >( CkNumNodes() );
149 : 19 : auto chunksize = npoin / N;
150 : : // Put sub-graph into a map for aggregation
151 [ + + ]: 57857 : for (std::size_t p=0; p<gid.size(); ++p) {
152 : 57838 : auto bin = gid[p] / chunksize;
153 [ + + ]: 57838 : if (bin >= N) bin = N - 1;
154 [ + - ][ + - ]: 57838 : auto& m = graph[ static_cast< int >( bin ) ][ gid[p] ];
155 [ + - ][ + + ]: 621244 : for (auto i : tk::Around(psup,p)) m.insert( gid[i] );
156 : : }
157 : 19 : }
158 : :
159 : : // Send mesh graph (points surrounding points) in bins to nodes that will
160 : : // aggregate this data in maps for the data in the bin. These bins form a
161 : : // distributed table. Note that we only send data to those nodes that have
162 : : // data to work on. The receiving sides do not know in advance if they receive
163 : : // messages or not. Completion is detected by having the receiver respond back
164 : : // and counting the responses on the sender side, i.e., this node.
165 : 770 : m_npsup = graph.size();
166 [ + + ]: 770 : if (m_npsup == 0) {
167 [ + - ]: 751 : contribute( sizeof(std::size_t), &m_meshid, CkReduction::nop,
168 : 751 : m_cbp.get< tag::queried >() );
169 : : } else {
170 [ + + ]: 76 : for (const auto& [ targetnode, psup ] : graph) {
171 [ + - ][ + - ]: 57 : thisProxy[ targetnode ].query( thisIndex, psup );
172 : : }
173 : : }
174 : 770 : }
175 : :
176 : : void
177 : 804 : Partitioner::registerReducers()
178 : : // *****************************************************************************
179 : : // Configure Charm++ reduction types
180 : : //! \details Since this is a [initnode] routine, see the .ci file, the
181 : : //! Charm++ runtime system executes the routine exactly once on every
182 : : //! logical node early on in the Charm++ init sequence. Must be static as
183 : : //! it is called without an object. See also: Section "Initializations at
184 : : //! Program Startup" at in the Charm++ manual
185 : : //! http://charm.cs.illinois.edu/manuals/html/charm++/manual.html.
186 : : // *****************************************************************************
187 : : {
188 : 804 : PartsMerger = CkReduction::addReducer( tk::mergeParts );
189 : 804 : }
190 : :
191 : : void
192 : 57 : Partitioner::query( int fromnode,
193 : : const std::unordered_map< std::size_t,
194 : : std::unordered_set< std::size_t > >& psup )
195 : : // *****************************************************************************
196 : : // Aggregate mesh graph for mesh nodes owned
197 : : //! \param[in] fromnode Sender node ID
198 : : //! \param[in] psup Points surrounding points data from another compute node
199 : : // *****************************************************************************
200 : : {
201 : : // Aggregate incoming graphs with contributor node ids
202 [ + + ]: 57895 : for (const auto& [g,n] : psup) {
203 [ + - ]: 57838 : auto& t = m_graphnode[g];
204 [ + - ]: 57838 : std::get<0>(t).push_back( fromnode ); // nodes that contribute to g
205 [ + - ]: 57838 : std::get<1>(t).insert( begin(n), end(n) ); // points surrounding g
206 : : }
207 : :
208 : : // Report back to node message received from
209 [ + - ][ + - ]: 57 : thisProxy[ fromnode ].recvquery();
210 : 57 : }
211 : :
212 : : void
213 : 57 : Partitioner::recvquery()
214 : : // *****************************************************************************
215 : : // Receive receipt of list of points surrounding points to query
216 : : // *****************************************************************************
217 : : {
218 [ + + ]: 57 : if (--m_npsup == 0) {
219 : 19 : contribute( sizeof(std::size_t), &m_meshid, CkReduction::nop,
220 : 19 : m_cbp.get< tag::queried >() );
221 : : }
222 : 57 : }
223 : :
224 : : void
225 : 770 : Partitioner::response()
226 : : // *****************************************************************************
227 : : // Respond to graph queries
228 : : // *****************************************************************************
229 : : {
230 : : std::unordered_map< int,
231 : 770 : std::unordered_map< std::size_t, std::unordered_set< std::size_t > > > exp;
232 : :
233 : : // Prepare partial graph to be sent back to requesting compute nodes
234 [ + + ]: 29719 : for (const auto& [g,t] : m_graphnode) {
235 : 28949 : const auto& targetnodes = std::get<0>(t); // requesting compute nodes
236 : 28949 : const auto& psup = std::get<1>(t); // aggregate sub-graph for g
237 : : // send answer to all that queried but send data only to owner
238 [ + - ][ + + ]: 86787 : for (auto n : targetnodes) exp[n];
239 [ + - ]: 28949 : auto owner = *std::min_element( begin(targetnodes), end(targetnodes) );
240 [ + - ][ + - ]: 28949 : exp[owner][g].insert( begin(psup), end(psup) );
[ + - ]
241 : : }
242 : :
243 : : // Send partial mesh node graph to compute nodes that issued a query to us.
244 : : // This data form a distributed table and we only work on a chunk of it. Note
245 : : // that we only send data back to those compute nodes that have queried us
246 : : // that owns the mesh node (with the lowest chare id for a shared node). The
247 : : // receiving sides do not know in advance if they receive messages or not.
248 : : // Completion is detected by having the receiver respond back and counting
249 : : // the responses on the sender side, i.e., this compute node.
250 : 770 : m_npsup = exp.size();
251 [ + + ]: 770 : if (m_npsup == 0) {
252 [ + - ]: 751 : contribute( sizeof(std::size_t), &m_meshid, CkReduction::nop,
253 : 751 : m_cbp.get< tag::responded >() );
254 : : } else {
255 [ + + ]: 76 : for (const auto& [ targetchare, map ] : exp) {
256 [ + - ][ + - ]: 57 : thisProxy[ targetchare ].psup( thisIndex, map );
257 : : }
258 : : }
259 : 770 : }
260 : :
261 : : void
262 : 57 : Partitioner::psup( int fromnode, const std::unordered_map< std::size_t,
263 : : std::unordered_set< std::size_t > >& graph )
264 : : // *****************************************************************************
265 : : // Receive aggregated mesh node graph for our mesh chunk
266 : : //! \param[in] fromnode Sender chare ID
267 : : //! \param[in] graph Aggregate mesh node graph assembled by fromnode
268 : : // *****************************************************************************
269 : : {
270 [ + + ]: 29006 : for (const auto& [g,psup] : graph) {
271 [ + - ]: 28949 : auto& nodes = m_graph[g];
272 [ + - ]: 28949 : nodes.insert( end(nodes), begin(psup), end(psup) );
273 : : }
274 : :
275 : : // Report back to chare message received from
276 [ + - ][ + - ]: 57 : thisProxy[ fromnode ].recvpsup();
277 : 57 : }
278 : :
279 : : void
280 : 57 : Partitioner::recvpsup()
281 : : // *****************************************************************************
282 : : // Receive receipt of mesh nodes graphs
283 : : // *****************************************************************************
284 : : {
285 [ + + ]: 57 : if (--m_npsup == 0)
286 : 19 : contribute( sizeof(std::size_t), &m_meshid, CkReduction::nop,
287 : 19 : m_cbp.get< tag::responded >() );
288 : 57 : }
289 : :
290 : : void
291 : 770 : Partitioner::load()
292 : : // *****************************************************************************
293 : : // Compute total load across distributed mesh
294 : : // *****************************************************************************
295 : : {
296 : : // Sum number of cells across distributed mesh
297 [ + - ]: 770 : std::vector< std::size_t > meshdata{ m_meshid, m_ginpoel.size()/4 };
298 [ + - ]: 770 : contribute( meshdata, CkReduction::sum_ulong, m_cbp.get< tag::load >() );
299 : 770 : }
300 : :
301 : : void
302 : 770 : Partitioner::partition( int nchare )
303 : : // *****************************************************************************
304 : : // Partition the computational mesh into a number of chares
305 : : //! \param[in] nchare Number of parts the mesh will be partitioned into
306 : : //! \details This function calls the mesh partitioner to partition the mesh. The
307 : : //! number of partitions equals the number nchare argument which must be no
308 : : //! lower than the number of compute nodes.
309 : : // *****************************************************************************
310 : : {
311 [ - + ][ - - ]: 770 : Assert( nchare >= CkNumNodes(), "Number of chares must not be lower than the "
[ - - ][ - - ]
312 : : "number of compute nodes" );
313 : :
314 : 770 : m_nchare = nchare;
315 : 770 : bool multi = g_cfg.get< tag::input >().size() > 1;
316 [ - + ]: 770 : const auto& alg = multi ? g_cfg.get< tag::part_ >()[ m_meshid ]
317 : 770 : : g_cfg.get< tag::part >();
318 [ - + ]: 770 : const auto& params = multi ? g_cfg.get< tag::zoltan_params_ >()[ m_meshid ]
319 : 770 : : g_cfg.get< tag::zoltan_params >();
320 : :
321 [ + + ]: 770 : if ( alg == "phg" ) {
322 : :
323 : : // Partition mesh with graph partitioner
324 [ + - ]: 19 : auto chp = graphPartMesh( m_ginpoel, m_graph, params, nchare );
325 : :
326 : : // Aggregate partition assginments
327 [ + - ]: 19 : auto stream = tk::serialize( chp );
328 [ + - ]: 19 : contribute( stream.first, stream.second.get(), PartsMerger,
329 [ + - ][ + - ]: 38 : CkCallback( CkIndex_Partitioner::parts(nullptr), thisProxy ) );
330 : :
331 : 19 : } else {
332 : :
333 : : // Partition mesh with coordinate-based partitioner
334 [ + - ]: 751 : auto che = geomPartMesh( alg.c_str(), params, m_inpoel, m_coord, nchare );
335 : :
336 : : // Distribute partition assignments
337 [ + - ]: 751 : partitioned( std::move(che) );
338 : :
339 : 751 : }
340 : 770 : }
341 : :
342 : : void
343 : 19 : Partitioner::parts( CkReductionMsg* msg )
344 : : // *****************************************************************************
345 : : // Reduction target to aggregate mesh partition assignments
346 : : //! \param[in] msg Serialized aggregated mesh nodes partition assignments
347 : : // *****************************************************************************
348 : : {
349 : : // Deserialize mesh partition assignments
350 : 19 : PUP::fromMem creator( msg->getData() );
351 : 19 : std::unordered_map< std::size_t, std::size_t > parts;
352 [ + - ]: 19 : creator | parts;
353 [ + - ][ + - ]: 19 : delete msg;
354 : :
355 : : // Assign mesh elements based on node assignments
356 : : using std::min;
357 [ + - ]: 19 : std::vector< std::size_t > che( m_ginpoel.size()/4 );
358 [ + + ]: 144529 : for (std::size_t e=0; e<m_ginpoel.size()/4; ++e) {
359 : 144510 : const auto g = m_ginpoel.data() + e*4;
360 [ + - ]: 144510 : std::size_t chp[4] = { tk::cref_find( parts, g[0] ),
361 : 289020 : tk::cref_find( parts, g[1] ),
362 : 289020 : tk::cref_find( parts, g[2] ),
363 [ + - ][ + - ]: 144510 : tk::cref_find( parts, g[3] ) };
[ + - ]
364 : 144510 : che[e] = min( chp[0], min( chp[1], min( chp[2], chp[3] ) ) );
365 : : }
366 : :
367 [ + - ]: 19 : partitioned( std::move(che) );
368 : 19 : }
369 : :
370 : : void
371 : 770 : Partitioner::partitioned( std::vector< std::size_t >&& che )
372 : : // *****************************************************************************
373 : : // Continue after partitioning finished
374 : : //! \param[in] che Chare ids assigned to mesh elements
375 : : // *****************************************************************************
376 : : {
377 [ + + ]: 770 : if ( g_cfg.get< tag::feedback >() ) m_host.pepartitioned();
378 : :
379 : 770 : contribute( sizeof(std::size_t), &m_meshid, CkReduction::nop,
380 : 770 : m_cbp.get< tag::partitioned >() );
381 : :
382 : : // Categorize mesh elements (given by their gobal node IDs) by target chare
383 : : // and distribute to their compute nodes based on mesh partitioning.
384 [ + - ][ + - ]: 770 : distribute( categorize( che ) );
385 : 770 : }
386 : :
387 : : void
388 : 2898 : Partitioner::addMesh(
389 : : int fromnode,
390 : : const std::unordered_map< int, // chare id
391 : : std::tuple<
392 : : std::vector< std::size_t >, // tet connectivity
393 : : tk::UnsMesh::CoordMap, // node coords
394 : : std::unordered_map< int, std::vector< std::size_t > >, // bface conn
395 : : std::unordered_map< int, std::vector< std::size_t > > // bnodes
396 : : > >& chmesh )
397 : : // *****************************************************************************
398 : : // Receive mesh associated to chares we own after refinement
399 : : //! \param[in] fromnode Compute node call coming from
400 : : //! \param[in] chmesh Map associating mesh connectivities to global node ids
401 : : //! and node coordinates for mesh chunks we are assigned by the partitioner
402 : : // *****************************************************************************
403 : : {
404 : : // Store mesh connectivity and global node coordinates categorized by chares.
405 : : // The send side also writes to the data written here, so concat.
406 [ + + ]: 11290 : for (const auto& [ chareid, chunk ] : chmesh) {
407 [ + - ][ - + ]: 8392 : Assert( node(chareid) == CkMyNode(), "Compute node "
[ - - ][ - - ]
[ - - ][ - - ]
408 : : + std::to_string(CkMyNode()) +
409 : : " received a mesh whose chare it does not own" );
410 : : // Store domain element (tetrahedron) connectivity
411 : 8392 : const auto& inpoel = std::get< 0 >( chunk );
412 [ + - ]: 8392 : auto& inp = m_chinpoel[ chareid ]; // will store tetrahedron connectivity
413 [ + - ]: 8392 : inp.insert( end(inp), begin(inpoel), end(inpoel) );
414 : : // Store mesh node coordinates associated to global node IDs
415 : 8392 : const auto& coord = std::get< 1 >( chunk );
416 [ + - ][ - + ]: 8392 : Assert( tk::uniquecopy(inpoel).size() == coord.size(), "Size mismatch" );
[ - - ][ - - ]
[ - - ]
417 [ + - ]: 8392 : auto& chcm = m_chcoordmap[ chareid ]; // will store node coordinates
418 [ + - ]: 8392 : chcm.insert( begin(coord), end(coord) ); // concatenate node coords
419 : : // Store boundary side set id + face ids + face connectivities
420 : 8392 : const auto& bconn = std::get< 2 >( chunk );
421 [ + - ]: 8392 : auto& bface = m_chbface[ chareid ]; // for side set id + boundary face ids
422 [ + - ]: 8392 : auto& t = m_chtriinpoel[ chareid ]; // for boundary face connectivity
423 [ + - ]: 8392 : auto& f = m_nface[ chareid ]; // use counter for chare
424 [ + + ]: 20499 : for (const auto& [ setid, faceids ] : bconn) {
425 [ + - ]: 12107 : auto& b = bface[ setid ];
426 [ + + ]: 104593 : for (std::size_t i=0; i<faceids.size()/3; ++i) {
427 [ + - ]: 92486 : b.push_back( f++ );
428 [ + - ]: 92486 : t.push_back( faceids[i*3+0] );
429 [ + - ]: 92486 : t.push_back( faceids[i*3+1] );
430 [ + - ]: 92486 : t.push_back( faceids[i*3+2] );
431 : : }
432 : : }
433 : : // Store boundary side set id + node lists
434 : 8392 : const auto& bnode = std::get< 3 >( chunk );
435 [ + - ]: 8392 : auto& nodes = m_chbnode[ chareid ]; // for side set id + boundary nodes
436 [ + + ]: 15163 : for (const auto& [ setid, bnodes ] : bnode) {
437 [ + - ]: 6771 : auto& b = nodes[ setid ];
438 [ + - ]: 6771 : b.insert( end(b), begin(bnodes), end(bnodes) );
439 : : }
440 : : }
441 : :
442 [ + - ][ + - ]: 2898 : thisProxy[ fromnode ].recvMesh();
443 : 2898 : }
444 : :
445 : : int
446 : 16784 : Partitioner::node( int id ) const
447 : : // *****************************************************************************
448 : : // Return nodegroup id for chare id
449 : : //! \param[in] id Chare id
450 : : //! \return Nodegroup that creates the chare
451 : : //! \details This is computed based on a simple contiguous linear
452 : : //! distribution of chare ids to compute nodes.
453 : : // *****************************************************************************
454 : : {
455 [ - + ][ - - ]: 16784 : Assert( m_nchare > 0, "Number of chares must be a positive number" );
[ - - ][ - - ]
456 : 16784 : auto p = id / (m_nchare / CkNumNodes());
457 [ + + ]: 16784 : if (p >= CkNumNodes()) p = CkNumNodes()-1;
458 [ - + ][ - - ]: 16784 : Assert( p < CkNumNodes(), "Assigning to nonexistent node" );
[ - - ][ - - ]
459 : 16784 : return p;
460 : : }
461 : :
462 : : void
463 : 2898 : Partitioner::recvMesh()
464 : : // *****************************************************************************
465 : : // Acknowledge received mesh chunk and its nodes after mesh refinement
466 : : // *****************************************************************************
467 : : {
468 [ + + ]: 2898 : if (--m_ndist == 0) {
469 [ + + ]: 668 : if (g_cfg.get< tag::feedback >()) m_host.pedistributed();
470 : 668 : contribute( sizeof(std::size_t), &m_meshid, CkReduction::nop,
471 : 668 : m_cbp.get< tag::distributed >() );
472 : : }
473 : 2898 : }
474 : :
475 : : void
476 : 770 : Partitioner::refine()
477 : : // *****************************************************************************
478 : : // Optionally start refining the mesh
479 : : // *****************************************************************************
480 : : {
481 [ + - ]: 770 : auto dist = distribution( m_nchare );
482 : :
483 : 770 : std::size_t error = 0;
484 [ - + ]: 770 : if (m_chinpoel.size() < static_cast<std::size_t>(dist[1])) {
485 : :
486 : 0 : error = 1;
487 : :
488 : : } else {
489 : :
490 [ + + ]: 3346 : for (int c=0; c<dist[1]; ++c) {
491 : : // compute chare ID
492 : 2576 : auto cid = CkMyNode() * dist[0] + c;
493 : : // create refiner Charm++ chare array element using dynamic insertion
494 [ + - ]: 5152 : m_refiner[ cid ].insert( m_meshid,
495 : 2576 : m_host,
496 : 2576 : m_sorter,
497 : 2576 : m_meshwriter,
498 : 2576 : m_discretization,
499 : 2576 : m_riecg,
500 : 2576 : m_laxcg,
501 : 2576 : m_zalcg,
502 : 2576 : m_kozcg,
503 : 2576 : m_chocg,
504 : 2576 : m_lohcg,
505 : 2576 : m_cgpre,
506 : 2576 : m_cgmom,
507 : 2576 : m_cbr,
508 : 2576 : m_cbs,
509 [ + - ]: 2576 : tk::cref_find(m_chinpoel,cid),
510 [ + - ]: 2576 : tk::cref_find(m_chcoordmap,cid),
511 [ + - ]: 2576 : tk::cref_find(m_chbface,cid),
512 [ + - ]: 2576 : tk::cref_find(m_chtriinpoel,cid),
513 [ + - ][ + - ]: 2576 : tk::cref_find(m_chbnode,cid),
514 : : m_nchare );
515 : : }
516 : :
517 : : }
518 : :
519 : 770 : tk::destroy( m_ginpoel );
520 : 770 : tk::destroy( m_coord );
521 : 770 : tk::destroy( m_inpoel );
522 : 770 : tk::destroy( m_lid );
523 : 770 : tk::destroy( m_nface );
524 : 770 : tk::destroy( m_linnodes );
525 : 770 : tk::destroy( m_chinpoel );
526 : 770 : tk::destroy( m_chcoordmap );
527 : 770 : tk::destroy( m_chbface );
528 : 770 : tk::destroy( m_chtriinpoel );
529 : 770 : tk::destroy( m_chbnode );
530 : 770 : tk::destroy( m_bface );
531 : 770 : tk::destroy( m_triinpoel );
532 : 770 : tk::destroy( m_bnode );
533 : :
534 [ + - ]: 770 : std::vector< std::size_t > meshdata{ m_meshid, error };
535 [ + - ]: 770 : contribute( meshdata, CkReduction::max_ulong, m_cbp.get<tag::refinserted>() );
536 : 770 : }
537 : :
538 : : std::unordered_map< int, Partitioner::MeshData >
539 : 770 : Partitioner::categorize( const std::vector< std::size_t >& target ) const
540 : : // *****************************************************************************
541 : : // Categorize mesh data by target
542 : : //! \param[in] target Target chares of mesh elements, size: number of
543 : : //! elements in the chunk of the mesh graph on this compute node.
544 : : //! \return Vector of global mesh node ids connecting elements owned by each
545 : : //! target chare.
546 : : // *****************************************************************************
547 : : {
548 [ - + ][ - - ]: 770 : Assert( target.size() == m_ginpoel.size()/4, "Size mismatch");
[ - - ][ - - ]
549 : :
550 : : using Face = tk::UnsMesh::Face;
551 : :
552 : : // Build hash map associating side set id to boundary faces
553 : : std::unordered_map< Face, int,
554 : 770 : tk::UnsMesh::Hash<3>, tk::UnsMesh::Eq<3> > faceside;
555 [ + + ]: 4856 : for (const auto& [ setid, faceids ] : m_bface)
556 [ + + ]: 264470 : for (auto f : faceids)
557 : 260384 : faceside[ {{ m_triinpoel[f*3+0],
558 : 260384 : m_triinpoel[f*3+1],
559 [ + - ]: 260384 : m_triinpoel[f*3+2] }} ] = setid;
560 : :
561 : : // Build hash map associating side set ids to boundary nodes
562 : 770 : std::unordered_map< std::size_t, std::unordered_set< int > > nodeside;
563 [ + + ]: 2713 : for (const auto& [ setid, nodes ] : m_bnode)
564 [ + + ]: 196319 : for (auto n : nodes)
565 [ + - ][ + - ]: 194376 : nodeside[ n ].insert( setid );
566 : :
567 : : // Categorize mesh data (tets, node coordinates, and boundary data) by target
568 : : // chare based on which chare the partitioner assigned elements (tets) to
569 : 770 : std::unordered_map< int, MeshData > chmesh;
570 [ + + ]: 1080977 : for (std::size_t e=0; e<target.size(); ++e) {
571 : : // Construct a tetrahedron with global node ids
572 : 1080207 : tk::UnsMesh::Tet t{{ m_ginpoel[e*4+0], m_ginpoel[e*4+1],
573 : 1080207 : m_ginpoel[e*4+2], m_ginpoel[e*4+3] }};
574 : : // Categorize tetrahedron (domain element) connectivity
575 [ + - ]: 1080207 : auto& mesh = chmesh[ static_cast<int>(target[e]) ];
576 : 1080207 : auto& inpoel = std::get< 0 >( mesh );
577 [ + - ]: 1080207 : inpoel.insert( end(inpoel), begin(t), end(t) );
578 : : // Categorize boundary face connectivity
579 : 1080207 : auto& bconn = std::get< 1 >( mesh );
580 : 1080207 : std::array<Face,4> face{{ {{t[0],t[2],t[1]}}, {{t[0],t[1],t[3]}},
581 : 1080207 : {{t[0],t[3],t[2]}}, {{t[1],t[2],t[3]}} }};
582 [ + + ]: 5401035 : for (const auto& f : face) {
583 [ + - ]: 4320828 : auto it = faceside.find( f );
584 [ + + ]: 4320828 : if (it != end(faceside)) {
585 [ + - ]: 260384 : auto& s = bconn[ it->second ];
586 [ + - ]: 260384 : s.insert( end(s), begin(f), end(f) );
587 : : }
588 : : }
589 : : // Categorize boundary node lists
590 : 1080207 : auto& bnode = std::get< 2 >( mesh );
591 [ + + ]: 5401035 : for (const auto& n : t) {
592 [ + - ]: 4320828 : auto it = nodeside.find( n );
593 [ + + ]: 4320828 : if (it != end(nodeside))
594 [ + + ]: 2351395 : for (auto s : it->second)
595 [ + - ][ + - ]: 1208992 : bnode[ s ].push_back( n );
596 : : }
597 : : }
598 : :
599 : : // Make boundary node lists unique per side set
600 [ + + ]: 11411 : for (auto& c : chmesh)
601 [ + + ]: 20007 : for (auto& n : std::get<2>(c.second))
602 [ + - ]: 9366 : tk::unique( n.second );
603 : :
604 : : // Make sure all compute nodes have target chares assigned
605 [ - + ][ - - ]: 770 : Assert( !chmesh.empty(), "No elements have been assigned to a chare" );
[ - - ][ - - ]
606 : :
607 : : // This check should always be done, hence ErrChk and not Assert, as it
608 : : // can result from particular pathological combinations of (1) too large
609 : : // degree of virtualization, (2) too many compute nodes, and/or (3) too small
610 : : // of a mesh and not due to programmer error.
611 [ + + ]: 11411 : for(const auto& c : chmesh)
612 [ - + ][ - - ]: 10641 : ErrChk( !std::get<0>(c.second).empty(),
[ - - ][ - - ]
613 : : "Overdecomposition of the mesh is too large compared to the "
614 : : "number of work units computed based on the degree of "
615 : : "virtualization desired. As a result, there would be at least "
616 : : "one work unit with no mesh elements to work on, i.e., nothing "
617 : : "to do. Solution 1: decrease the virtualization to a lower "
618 : : "value using the command-line argument '-u'. Solution 2: "
619 : : "decrease the number processing elements (PEs and/or compute "
620 : : "nodes) using the charmrun command-line argument '+pN' where N is "
621 : : "the number of PEs (or in SMP-mode in combination with +ppn to "
622 : : "reduce the number of compute nodes), which implicitly increases "
623 : : "the size (and thus decreases the number) of work units.)" );
624 : :
625 : 1540 : return chmesh;
626 : 770 : }
627 : :
628 : : tk::UnsMesh::CoordMap
629 : 10641 : Partitioner::coordmap( const std::vector< std::size_t >& inpoel )
630 : : // *****************************************************************************
631 : : // Extract coordinates associated to global nodes of a mesh chunk
632 : : //! \param[in] inpoel Mesh connectivity
633 : : //! \return Map storing the coordinates of unique nodes associated to global
634 : : //! node IDs in mesh given by inpoel
635 : : // *****************************************************************************
636 : : {
637 [ - + ][ - - ]: 10641 : Assert( inpoel.size() % 4 == 0, "Incomplete mesh connectivity" );
[ - - ][ - - ]
638 : :
639 : 10641 : tk::UnsMesh::CoordMap map;
640 : :
641 [ + - ][ + + ]: 523060 : for (auto g : tk::uniquecopy(inpoel)) {
642 [ + - ]: 512419 : auto i = tk::cref_find( m_lid, g );
643 [ + - ]: 512419 : auto& c = map[g];
644 : 512419 : c[0] = m_coord[0][i];
645 : 512419 : c[1] = m_coord[1][i];
646 : 512419 : c[2] = m_coord[2][i];
647 : 10641 : }
648 : :
649 [ + - ][ - + ]: 10641 : Assert( tk::uniquecopy(inpoel).size() == map.size(), "Size mismatch" );
[ - - ][ - - ]
[ - - ]
650 : :
651 : 10641 : return map;
652 : 0 : }
653 : :
654 : : void
655 : 770 : Partitioner::distribute( std::unordered_map< int, MeshData >&& mesh )
656 : : // *****************************************************************************
657 : : // Distribute mesh to target compute nodes after mesh partitioning
658 : : //! \param[in] mesh Mesh data categorized by target by target chares
659 : : // *****************************************************************************
660 : : {
661 [ + - ]: 770 : auto dist = distribution( m_nchare );
662 : :
663 : : // Extract mesh data whose chares are on ("owned by") this compute node
664 [ + + ]: 3346 : for (int c=0; c<dist[1]; ++c) {
665 : 2576 : auto chid = CkMyNode() * dist[0] + c; // compute owned chare ID
666 [ + - ]: 2576 : const auto it = mesh.find( chid ); // attempt to find its mesh data
667 [ + + ]: 2576 : if (it != end(mesh)) { // if found
668 : : // Store own tetrahedron connectivity
669 : 2249 : const auto& inpoel = std::get<0>( it->second );
670 [ + - ]: 2249 : auto& inp = m_chinpoel[ chid ]; // will store own mesh connectivity
671 [ + - ]: 2249 : inp.insert( end(inp), begin(inpoel), end(inpoel) );
672 : : // Store own node coordinates
673 [ + - ]: 2249 : auto& chcm = m_chcoordmap[ chid ]; // will store own node coordinates
674 [ + - ]: 2249 : auto cm = coordmap( inpoel ); // extract node coordinates
675 [ + - ]: 2249 : chcm.insert( begin(cm), end(cm) ); // concatenate node coords
676 : : // Store own boundary face connectivity
677 : 2249 : const auto& bconn = std::get<1>( it->second );
678 [ + - ]: 2249 : auto& bface = m_chbface[ chid ]; // will store own boundary faces
679 [ + - ]: 2249 : auto& t = m_chtriinpoel[ chid ]; // wil store own boundary face conn
680 [ + - ]: 2249 : auto& f = m_nface[ chid ]; // use counter for chare
681 [ + + ]: 6759 : for (const auto& [ setid, faceids ] : bconn) {
682 [ + - ]: 4510 : auto& b = bface[ setid ];
683 [ + + ]: 172408 : for (std::size_t i=0; i<faceids.size()/3; ++i) {
684 [ + - ]: 167898 : b.push_back( f++ );
685 [ + - ]: 167898 : t.push_back( faceids[i*3+0] );
686 [ + - ]: 167898 : t.push_back( faceids[i*3+1] );
687 [ + - ]: 167898 : t.push_back( faceids[i*3+2] );
688 : : }
689 : : }
690 : : // Store own boundary node lists
691 : 2249 : const auto& bnode = std::get<2>( it->second );
692 [ + - ]: 2249 : auto& nodes = m_chbnode[ chid ]; // will store own boundary nodes
693 [ + + ]: 4844 : for (const auto& [ setid, nodeids ] : bnode) {
694 [ + - ]: 2595 : auto& b = nodes[ setid ];
695 [ + - ]: 2595 : b.insert( end(b), begin(nodeids), end(nodeids) );
696 : : }
697 : : // Remove chare ID and mesh data
698 [ + - ]: 2249 : mesh.erase( it );
699 : 2249 : }
700 [ + - ][ - + ]: 2576 : Assert( mesh.find(chid) == end(mesh), "Not all owned mesh data stored" );
[ - - ][ - - ]
[ - - ]
701 : : }
702 : :
703 : : // Construct export map (associating mesh connectivities with global node
704 : : // indices and node coordinates) for mesh chunks associated to chare IDs
705 : : // owned by chares we do not own.
706 : : std::unordered_map< int, // target compute node
707 : : std::unordered_map< int, // chare ID
708 : : std::tuple<
709 : : // (domain-element) tetrahedron connectivity
710 : : std::vector< std::size_t >,
711 : : // (domain) node IDs & coordinates
712 : : tk::UnsMesh::CoordMap,
713 : : // boundary side set + face connectivity
714 : : std::unordered_map< int, std::vector< std::size_t > >,
715 : : // boundary side set + node list
716 : : std::unordered_map< int, std::vector< std::size_t > >
717 : 770 : > > > exp;
718 : :
719 [ + + ]: 9162 : for (const auto& c : mesh)
720 [ + - ][ + - ]: 8392 : exp[ node(c.first) ][ c.first ] =
[ + - ]
721 [ + - ]: 16784 : std::make_tuple( std::get<0>(c.second),
722 [ + - ]: 16784 : coordmap(std::get<0>(c.second)),
723 : 8392 : std::get<1>(c.second),
724 : 16784 : std::get<2>(c.second) );
725 : :
726 : : // Export chare IDs and mesh we do not own to fellow compute nodes
727 [ + + ]: 770 : if (exp.empty()) {
728 [ + + ][ + - ]: 102 : if (g_cfg.get< tag::feedback >()) m_host.pedistributed();
729 [ + - ]: 102 : contribute( sizeof(std::size_t), &m_meshid, CkReduction::nop,
730 : 102 : m_cbp.get< tag::distributed >() );
731 : : } else {
732 : 668 : m_ndist += exp.size();
733 [ + + ]: 3566 : for (const auto& [ targetnode, chunk ] : exp)
734 [ + - ][ + - ]: 2898 : thisProxy[ targetnode ].addMesh( CkMyNode(), chunk );
735 : : }
736 : 770 : }
737 : :
738 : : std::array< int, 2 >
739 : 1540 : Partitioner::distribution( int npart ) const
740 : : // *****************************************************************************
741 : : // Compute chare (partition) distribution
742 : : //! \param[in] npart Total number of chares (partitions) to distribute
743 : : //! \return Chunksize, i.e., number of chares per all compute nodes except the
744 : : //! last one, and the number of chares for this compute node.
745 : : //! \details Chare ids are distributed to compute nodes in a linear continguous
746 : : //! order with the last compute node taking the remainder if the number of
747 : : //! compute nodes is not divisible by the number chares. For example, if
748 : : //! nchare=7 and nnode=3, the chare distribution is node0: 0 1, node1: 2 3,
749 : : //! and node2: 4 5 6. As a result of this distribution, all compute nodes will
750 : : //! have their chare-categorized element connectivity filled with the global
751 : : //! mesh node IDs associated to the Charm++ chare IDs each compute node owns.
752 : : // *****************************************************************************
753 : : {
754 : 1540 : auto chunksize = npart / CkNumNodes();
755 : 1540 : auto mynchare = chunksize;
756 [ + + ]: 1540 : if (CkMyNode() == CkNumNodes()-1) mynchare += npart % CkNumNodes();
757 : 1540 : return {{ chunksize, mynchare }};
758 : : }
759 : :
760 : : #include "NoWarning/partitioner.def.h"
|