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 : 750 : 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 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 : 750 : const std::map< int, std::vector< std::size_t > >& bnode ) :
63 : 750 : m_meshid( meshid ),
64 : 750 : m_npsup( 0 ),
65 : 750 : m_cbp( cbp ),
66 : 750 : m_cbr( cbr ),
67 : 750 : m_cbs( cbs ),
68 : : m_host( host ),
69 : : m_refiner( refiner ),
70 : : m_sorter( sorter ),
71 : : m_meshwriter( meshwriter ),
72 : : m_discretization( discretization ),
73 : : m_riecg( riecg ),
74 : : m_laxcg( laxcg ),
75 : : m_zalcg( zalcg ),
76 : : m_kozcg( kozcg ),
77 : : m_chocg( chocg ),
78 : : m_lohcg( lohcg ),
79 : : m_cgpre( cgpre ),
80 : : m_cgmom( cgmom ),
81 : 750 : m_ndist( 0 ),
82 [ + - ]: 750 : m_nchare( 0 ),
83 [ + - ]: 750 : m_bface( bface ),
84 [ + - ][ + - ]: 2250 : 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 base
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 [ + - ]: 750 : tk::ExodusIIMeshReader mr( filename );
112 : :
113 : : // Read this compute node's chunk of the mesh (graph and coords) from file
114 : : std::vector< std::size_t > triinpoel;
115 [ + - ]: 750 : 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 [ + - ][ - - ]: 1500 : 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 : : std::map< int, std::vector< std::size_t > > own_bnode;
124 [ + + ]: 2663 : for (const auto& [ setid, nodes ] : m_bnode) {
125 [ + - ]: 1913 : auto& b = own_bnode[ setid ];
126 [ + + ][ + + ]: 266701 : for (auto n : nodes) {
127 : : auto i = m_lid.find( n );
128 [ + + ][ + - ]: 264788 : if (i != end(m_lid)) b.push_back( n );
129 : : }
130 [ + + ]: 1913 : if (b.empty()) own_bnode.erase( setid );
131 : : }
132 : : 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 : : std::unordered_set< std::size_t > > > graph;
137 [ + + ]: 750 : if ( g_cfg.get< tag::part >() == "phg" ) {
138 : : // Generate global node ids for the mesh on this compute node
139 [ + - ]: 18 : const auto gid = tk::uniquecopy( m_ginpoel );
140 : : // Generate points surrounding points of this sub-graph with local node ids
141 [ + - ][ + - ]: 18 : const auto psup = tk::genPsup( m_inpoel, 4, tk::genEsup(m_inpoel,4) );
[ + - ][ - - ]
142 : : // Get total number of mesh nodes in mesh file
143 : : auto npoin = mr.npoin();
144 : : // Compute the number of nodes (chunksize) a node will build mesh graph for
145 : 18 : auto N = static_cast< std::size_t >( CkNumNodes() );
146 : 18 : auto chunksize = npoin / N;
147 : : // Put sub-graph into a map for aggregation
148 [ + + ]: 56839 : for (std::size_t p=0; p<gid.size(); ++p) {
149 : 56821 : auto bin = gid[p] / chunksize;
150 [ + + ]: 56821 : if (bin >= N) bin = N - 1;
151 [ + - ]: 113642 : auto& m = graph[ static_cast< int >( bin ) ][ gid[p] ];
152 [ + - ][ + + ]: 608471 : for (auto i : tk::Around(psup,p)) m.insert( gid[i] );
153 : : }
154 : 18 : }
155 : :
156 : : // Send mesh graph (points surrounding points) in bins to nodes that will
157 : : // aggregate this data in maps for the data in the bin. These bins form a
158 : : // distributed table. Note that we only send data to those nodes that have
159 : : // data to work on. The receiving sides do not know in advance if they receive
160 : : // messages or not. Completion is detected by having the receiver respond back
161 : : // and counting the responses on the sender side, i.e., this node.
162 : 750 : m_npsup = graph.size();
163 [ + + ]: 750 : if (m_npsup == 0) {
164 [ + - ]: 732 : contribute( sizeof(std::size_t), &m_meshid, CkReduction::nop,
165 : : m_cbp.get< tag::queried >() );
166 : : } else {
167 [ + + ]: 74 : for (const auto& [ targetnode, psup ] : graph) {
168 [ + - ][ + - ]: 168 : thisProxy[ targetnode ].query( thisIndex, psup );
169 : : }
170 : : }
171 : 750 : }
172 : :
173 : : void
174 : 784 : Partitioner::registerReducers()
175 : : // *****************************************************************************
176 : : // Configure Charm++ reduction types
177 : : //! \details Since this is a [initnode] routine, see the .ci file, the
178 : : //! Charm++ runtime system executes the routine exactly once on every
179 : : //! logical node early on in the Charm++ init sequence. Must be static as
180 : : //! it is called without an object. See also: Section "Initializations at
181 : : //! Program Startup" at in the Charm++ manual
182 : : //! http://charm.cs.illinois.edu/manuals/html/charm++/manual.html.
183 : : // *****************************************************************************
184 : : {
185 : 784 : PartsMerger = CkReduction::addReducer( tk::mergeParts );
186 : 784 : }
187 : :
188 : : void
189 : 56 : Partitioner::query( int fromnode,
190 : : const std::unordered_map< std::size_t,
191 : : std::unordered_set< std::size_t > >& psup )
192 : : // *****************************************************************************
193 : : // Aggregate mesh graph for mesh nodes owned
194 : : //! \param[in] fromnode Sender node ID
195 : : //! \param[in] psup Points surrounding points data from another compute node
196 : : // *****************************************************************************
197 : : {
198 : : // Aggregate incoming graphs with contributor node ids
199 [ + + ]: 56877 : for (const auto& [g,n] : psup) {
200 : : auto& t = m_graphnode[g];
201 : 56821 : std::get<0>(t).push_back( fromnode ); // nodes that contribute to g
202 : : std::get<1>(t).insert( begin(n), end(n) ); // points surrounding g
203 : : }
204 : :
205 : : // Report back to node message received from
206 [ + - ]: 112 : thisProxy[ fromnode ].recvquery();
207 : 56 : }
208 : :
209 : : void
210 : 56 : Partitioner::recvquery()
211 : : // *****************************************************************************
212 : : // Receive receipt of list of points surrounding points to query
213 : : // *****************************************************************************
214 : : {
215 [ + + ]: 56 : if (--m_npsup == 0) {
216 : 18 : contribute( sizeof(std::size_t), &m_meshid, CkReduction::nop,
217 : : m_cbp.get< tag::queried >() );
218 : : }
219 : 56 : }
220 : :
221 : : void
222 : 750 : Partitioner::response()
223 : : // *****************************************************************************
224 : : // Respond to graph queries
225 : : // *****************************************************************************
226 : : {
227 : : std::unordered_map< int,
228 : : std::unordered_map< std::size_t, std::unordered_set< std::size_t > > > exp;
229 : :
230 : : // Prepare partial graph to be sent back to requesting compute nodes
231 [ + + ]: 28682 : for (const auto& [g,t] : m_graphnode) {
232 : : const auto& targetnodes = std::get<0>(t); // requesting compute nodes
233 : : const auto& psup = std::get<1>(t); // aggregate sub-graph for g
234 : : // send answer to all that queried but send data only to owner
235 [ + - ][ + + ]: 84753 : for (auto n : targetnodes) exp[n];
236 [ + - ]: 27932 : auto owner = *std::min_element( begin(targetnodes), end(targetnodes) );
237 : : exp[owner][g].insert( begin(psup), end(psup) );
238 : : }
239 : :
240 : : // Send partial mesh node graph to compute nodes that issued a query to us.
241 : : // This data form a distributed table and we only work on a chunk of it. Note
242 : : // that we only send data back to those compute nodes that have queried us
243 : : // that owns the mesh node (with the lowest chare id for a shared node). The
244 : : // receiving sides do not know in advance if they receive messages or not.
245 : : // Completion is detected by having the receiver respond back and counting
246 : : // the responses on the sender side, i.e., this compute node.
247 : 750 : m_npsup = exp.size();
248 [ + + ]: 750 : if (m_npsup == 0) {
249 [ + - ]: 732 : contribute( sizeof(std::size_t), &m_meshid, CkReduction::nop,
250 : : m_cbp.get< tag::responded >() );
251 : : } else {
252 [ + + ]: 74 : for (const auto& [ targetchare, map ] : exp) {
253 [ + - ][ + - ]: 168 : thisProxy[ targetchare ].psup( thisIndex, map );
254 : : }
255 : : }
256 : 750 : }
257 : :
258 : : void
259 : 56 : Partitioner::psup( int fromnode, const std::unordered_map< std::size_t,
260 : : std::unordered_set< std::size_t > >& graph )
261 : : // *****************************************************************************
262 : : // Receive aggregated mesh node graph for our mesh chunk
263 : : //! \param[in] fromnode Sender chare ID
264 : : //! \param[in] graph Aggregate mesh node graph assembled by fromnode
265 : : // *****************************************************************************
266 : : {
267 [ + + ]: 27988 : for (const auto& [g,psup] : graph) {
268 : : auto& nodes = m_graph[g];
269 : 27932 : nodes.insert( end(nodes), begin(psup), end(psup) );
270 : : }
271 : :
272 : : // Report back to chare message received from
273 [ + - ]: 56 : thisProxy[ fromnode ].recvpsup();
274 : 56 : }
275 : :
276 : : void
277 : 56 : Partitioner::recvpsup()
278 : : // *****************************************************************************
279 : : // Receive receipt of mesh nodes graphs
280 : : // *****************************************************************************
281 : : {
282 [ + + ]: 56 : if (--m_npsup == 0)
283 : 18 : contribute( sizeof(std::size_t), &m_meshid, CkReduction::nop,
284 : : m_cbp.get< tag::responded >() );
285 : 56 : }
286 : :
287 : : void
288 : 750 : Partitioner::load()
289 : : // *****************************************************************************
290 : : // Compute total load across distributed mesh
291 : : // *****************************************************************************
292 : : {
293 : : // Sum number of cells across distributed mesh
294 : 750 : std::vector< std::size_t > meshdata{ m_meshid, m_ginpoel.size()/4 };
295 [ + - ]: 750 : contribute( meshdata, CkReduction::sum_ulong, m_cbp.get< tag::load >() );
296 : 750 : }
297 : :
298 : : void
299 : 750 : Partitioner::partition( int nchare )
300 : : // *****************************************************************************
301 : : // Partition the computational mesh into a number of chares
302 : : //! \param[in] nchare Number of parts the mesh will be partitioned into
303 : : //! \details This function calls the mesh partitioner to partition the mesh. The
304 : : //! number of partitions equals the number nchare argument which must be no
305 : : //! lower than the number of compute nodes.
306 : : // *****************************************************************************
307 : : {
308 : : Assert( nchare >= CkNumNodes(), "Number of chares must not be lower than the "
309 : : "number of compute nodes" );
310 : :
311 : 750 : m_nchare = nchare;
312 : : const auto& alg = g_cfg.get< tag::part >();
313 : : const auto& params = g_cfg.get< tag::zoltan_params >();
314 : :
315 [ + + ]: 750 : if ( alg == "phg" ) {
316 : :
317 : : // Partition mesh with graph partitioner
318 : 18 : auto chp = graphPartMesh( m_ginpoel, m_graph, params, nchare );
319 : :
320 : : // Aggregate partition assginments
321 [ + - ]: 18 : auto stream = tk::serialize( chp );
322 [ + - ]: 18 : contribute( stream.first, stream.second.get(), PartsMerger,
323 [ + - ][ + - ]: 36 : CkCallback( CkIndex_Partitioner::parts(nullptr), thisProxy ) );
[ + - ][ - - ]
324 : :
325 : : } else {
326 : :
327 : : // Partition mesh with coordinate-based partitioner
328 : 732 : auto che = geomPartMesh( alg.c_str(), params, m_inpoel, m_coord, nchare );
329 : :
330 : : // Distribute partition assignments
331 [ + - ]: 732 : partitioned( std::move(che) );
332 : :
333 : : }
334 : 750 : }
335 : :
336 : : void
337 [ + - ]: 18 : Partitioner::parts( CkReductionMsg* msg )
338 : : // *****************************************************************************
339 : : // Reduction target to aggregate mesh partition assignments
340 : : //! \param[in] msg Serialized aggregated mesh nodes partition assignments
341 : : // *****************************************************************************
342 : : {
343 : : // Deserialize mesh partition assignments
344 : : PUP::fromMem creator( msg->getData() );
345 : : std::unordered_map< std::size_t, std::size_t > parts;
346 : : creator | parts;
347 : : delete msg;
348 : :
349 : : // Assign mesh elements based on node assignments
350 : : using std::min;
351 [ + - ]: 18 : std::vector< std::size_t > che( m_ginpoel.size()/4 );
352 [ + + ]: 140286 : for (std::size_t e=0; e<m_ginpoel.size()/4; ++e) {
353 [ + + ]: 140268 : const auto g = m_ginpoel.data() + e*4;
354 : 140268 : std::size_t chp[4] = { tk::cref_find( parts, g[0] ),
355 : 140268 : tk::cref_find( parts, g[1] ),
356 : 140268 : tk::cref_find( parts, g[2] ),
357 [ + + ]: 140268 : tk::cref_find( parts, g[3] ) };
358 : 140268 : che[e] = min( chp[0], min( chp[1], min( chp[2], chp[3] ) ) );
359 : : }
360 : :
361 [ + - ]: 18 : partitioned( std::move(che) );
362 : 18 : }
363 : :
364 : : void
365 : 750 : Partitioner::partitioned( std::vector< std::size_t >&& che )
366 : : // *****************************************************************************
367 : : // Continue after partitioning finished
368 : : //! \param[in] che Chare ids assigned to mesh elements
369 : : // *****************************************************************************
370 : : {
371 [ + + ]: 750 : if ( g_cfg.get< tag::feedback >() ) m_host.pepartitioned();
372 : :
373 : 750 : contribute( sizeof(std::size_t), &m_meshid, CkReduction::nop,
374 : : m_cbp.get< tag::partitioned >() );
375 : :
376 : : // Categorize mesh elements (given by their gobal node IDs) by target chare
377 : : // and distribute to their compute nodes based on mesh partitioning.
378 [ + - ]: 750 : distribute( categorize( che ) );
379 : 750 : }
380 : :
381 : : void
382 : 2850 : Partitioner::addMesh(
383 : : int fromnode,
384 : : const std::unordered_map< int, // chare id
385 : : std::tuple<
386 : : std::vector< std::size_t >, // tet connectivity
387 : : tk::UnsMesh::CoordMap, // node coords
388 : : std::unordered_map< int, std::vector< std::size_t > >, // bface conn
389 : : std::unordered_map< int, std::vector< std::size_t > > // bnodes
390 : : > >& chmesh )
391 : : // *****************************************************************************
392 : : // Receive mesh associated to chares we own after refinement
393 : : //! \param[in] fromnode Compute node call coming from
394 : : //! \param[in] chmesh Map associating mesh connectivities to global node ids
395 : : //! and node coordinates for mesh chunks we are assigned by the partitioner
396 : : // *****************************************************************************
397 : : {
398 : : // Store mesh connectivity and global node coordinates categorized by chares.
399 : : // The send side also writes to the data written here, so concat.
400 [ + + ]: 10954 : for (const auto& [ chareid, chunk ] : chmesh) {
401 : : Assert( node(chareid) == CkMyNode(), "Compute node "
402 : : + std::to_string(CkMyNode()) +
403 : : " received a mesh whose chare it does not own" );
404 : : // Store domain element (tetrahedron) connectivity
405 : : const auto& inpoel = std::get< 0 >( chunk );
406 : : auto& inp = m_chinpoel[ chareid ]; // will store tetrahedron connectivity
407 : 8104 : inp.insert( end(inp), begin(inpoel), end(inpoel) );
408 : : // Store mesh node coordinates associated to global node IDs
409 : : const auto& coord = std::get< 1 >( chunk );
410 : : Assert( tk::uniquecopy(inpoel).size() == coord.size(), "Size mismatch" );
411 : : auto& chcm = m_chcoordmap[ chareid ]; // will store node coordinates
412 : : chcm.insert( begin(coord), end(coord) ); // concatenate node coords
413 : : // Store boundary side set id + face ids + face connectivities
414 : : const auto& bconn = std::get< 2 >( chunk );
415 : : auto& bface = m_chbface[ chareid ]; // for side set id + boundary face ids
416 : 8104 : auto& t = m_chtriinpoel[ chareid ]; // for boundary face connectivity
417 : : auto& f = m_nface[ chareid ]; // use counter for chare
418 [ + + ]: 19747 : for (const auto& [ setid, faceids ] : bconn) {
419 : 11643 : auto& b = bface[ setid ];
420 [ + + ]: 96646 : for (std::size_t i=0; i<faceids.size()/3; ++i) {
421 : 85003 : b.push_back( f++ );
422 : 85003 : t.push_back( faceids[i*3+0] );
423 : 85003 : t.push_back( faceids[i*3+1] );
424 : 85003 : t.push_back( faceids[i*3+2] );
425 : : }
426 : : }
427 : : // Store boundary side set id + node lists
428 : : const auto& bnode = std::get< 3 >( chunk );
429 : : auto& nodes = m_chbnode[ chareid ]; // for side set id + boundary nodes
430 [ + + ]: 14382 : for (const auto& [ setid, bnodes ] : bnode) {
431 : 6278 : auto& b = nodes[ setid ];
432 : 6278 : b.insert( end(b), begin(bnodes), end(bnodes) );
433 : : }
434 : : }
435 : :
436 [ + - ]: 2850 : thisProxy[ fromnode ].recvMesh();
437 : 2850 : }
438 : :
439 : : int
440 : 8104 : Partitioner::node( int id ) const
441 : : // *****************************************************************************
442 : : // Return nodegroup id for chare id
443 : : //! \param[in] id Chare id
444 : : //! \return Nodegroup that creates the chare
445 : : //! \details This is computed based on a simple contiguous linear
446 : : //! distribution of chare ids to compute nodes.
447 : : // *****************************************************************************
448 : : {
449 : : Assert( m_nchare > 0, "Number of chares must be a positive number" );
450 : 8104 : auto p = id / (m_nchare / CkNumNodes());
451 [ + + ]: 8104 : if (p >= CkNumNodes()) p = CkNumNodes()-1;
452 : : Assert( p < CkNumNodes(), "Assigning to nonexistent node" );
453 : 8104 : return p;
454 : : }
455 : :
456 : : void
457 : 2850 : Partitioner::recvMesh()
458 : : // *****************************************************************************
459 : : // Acknowledge received mesh chunk and its nodes after mesh refinement
460 : : // *****************************************************************************
461 : : {
462 [ + + ]: 2850 : if (--m_ndist == 0) {
463 [ + + ]: 652 : if (g_cfg.get< tag::feedback >()) m_host.pedistributed();
464 : 652 : contribute( sizeof(std::size_t), &m_meshid, CkReduction::nop,
465 : : m_cbp.get< tag::distributed >() );
466 : : }
467 : 2850 : }
468 : :
469 : : void
470 : 750 : Partitioner::refine()
471 : : // *****************************************************************************
472 : : // Optionally start refining the mesh
473 : : // *****************************************************************************
474 : : {
475 [ + - ]: 750 : auto dist = distribution( m_nchare );
476 : :
477 : : std::size_t error = 0;
478 [ + - ]: 750 : if (m_chinpoel.size() < static_cast<std::size_t>(dist[1])) {
479 : :
480 : : error = 1;
481 : :
482 : : } else {
483 : :
484 [ + + ]: 3226 : for (int c=0; c<dist[1]; ++c) {
485 : : // compute chare ID
486 : 2476 : auto cid = CkMyNode() * dist[0] + c;
487 : : // create refiner Charm++ chare array element using dynamic insertion
488 : 4952 : m_refiner[ cid ].insert( m_meshid,
489 : 2476 : m_host,
490 : 2476 : m_sorter,
491 : 2476 : m_meshwriter,
492 : 2476 : m_discretization,
493 : 2476 : m_riecg,
494 : 2476 : m_laxcg,
495 : 2476 : m_zalcg,
496 : 2476 : m_kozcg,
497 : 2476 : m_chocg,
498 : 2476 : m_lohcg,
499 : 2476 : m_cgpre,
500 : 2476 : m_cgmom,
501 : 2476 : m_cbr,
502 [ + - ]: 2476 : m_cbs,
503 : : tk::cref_find(m_chinpoel,cid),
504 : : tk::cref_find(m_chcoordmap,cid),
505 : : tk::cref_find(m_chbface,cid),
506 : : tk::cref_find(m_chtriinpoel,cid),
507 : : tk::cref_find(m_chbnode,cid),
508 : : m_nchare );
509 : : }
510 : :
511 : : }
512 : :
513 : : tk::destroy( m_ginpoel );
514 : 750 : tk::destroy( m_coord );
515 : : tk::destroy( m_inpoel );
516 : 750 : tk::destroy( m_lid );
517 : 750 : tk::destroy( m_nface );
518 : 750 : tk::destroy( m_linnodes );
519 : 750 : tk::destroy( m_chinpoel );
520 : 750 : tk::destroy( m_chcoordmap );
521 : 750 : tk::destroy( m_chbface );
522 : 750 : tk::destroy( m_chtriinpoel );
523 : 750 : tk::destroy( m_chbnode );
524 : 750 : tk::destroy( m_bface );
525 : : tk::destroy( m_triinpoel );
526 : 750 : tk::destroy( m_bnode );
527 : :
528 : 750 : std::vector< std::size_t > meshdata{ m_meshid, error };
529 [ + - ]: 750 : contribute( meshdata, CkReduction::max_ulong, m_cbp.get<tag::refinserted>() );
530 : 750 : }
531 : :
532 : : std::unordered_map< int, Partitioner::MeshData >
533 : 750 : Partitioner::categorize( const std::vector< std::size_t >& target ) const
534 : : // *****************************************************************************
535 : : // Categorize mesh data by target
536 : : //! \param[in] target Target chares of mesh elements, size: number of
537 : : //! elements in the chunk of the mesh graph on this compute node.
538 : : //! \return Vector of global mesh node ids connecting elements owned by each
539 : : //! target chare.
540 : : // *****************************************************************************
541 : : {
542 : : Assert( target.size() == m_ginpoel.size()/4, "Size mismatch");
543 : :
544 : : using Face = tk::UnsMesh::Face;
545 : :
546 : : // Build hash map associating side set id to boundary faces
547 : : std::unordered_map< Face, int,
548 : : tk::UnsMesh::Hash<3>, tk::UnsMesh::Eq<3> > faceside;
549 [ + + ]: 4800 : for (const auto& [ setid, faceids ] : m_bface)
550 [ + + ]: 246450 : for (auto f : faceids)
551 : 242400 : faceside[ {{ m_triinpoel[f*3+0],
552 [ + - ]: 242400 : m_triinpoel[f*3+1],
553 [ + - ]: 242400 : m_triinpoel[f*3+2] }} ] = setid;
554 : :
555 : : // Build hash map associating side set ids to boundary nodes
556 : : std::unordered_map< std::size_t, std::unordered_set< int > > nodeside;
557 [ + + ]: 2653 : for (const auto& [ setid, nodes ] : m_bnode)
558 [ + - ][ + + ]: 173585 : for (auto n : nodes)
559 : : nodeside[ n ].insert( setid );
560 : :
561 : : // Categorize mesh data (tets, node coordinates, and boundary data) by target
562 : : // chare based on which chare the partitioner assigned elements (tets) to
563 : : std::unordered_map< int, MeshData > chmesh;
564 [ + + ]: 1049928 : for (std::size_t e=0; e<target.size(); ++e) {
565 : : // Construct a tetrahedron with global node ids
566 [ + - ]: 1049178 : tk::UnsMesh::Tet t{{ m_ginpoel[e*4+0], m_ginpoel[e*4+1],
567 : 1049178 : m_ginpoel[e*4+2], m_ginpoel[e*4+3] }};
568 : : // Categorize tetrahedron (domain element) connectivity
569 [ + - ][ + - ]: 1049178 : auto& mesh = chmesh[ static_cast<int>(target[e]) ];
570 : : auto& inpoel = std::get< 0 >( mesh );
571 : : inpoel.insert( end(inpoel), begin(t), end(t) );
572 : : // Categorize boundary face connectivity
573 : : auto& bconn = std::get< 1 >( mesh );
574 : : std::array<Face,4> face{{ {{t[0],t[2],t[1]}}, {{t[0],t[1],t[3]}},
575 : 1049178 : {{t[0],t[3],t[2]}}, {{t[1],t[2],t[3]}} }};
576 [ + + ]: 5245890 : for (const auto& f : face) {
577 : : auto it = faceside.find( f );
578 [ + + ]: 4196712 : if (it != end(faceside)) {
579 [ + - ]: 242400 : auto& s = bconn[ it->second ];
580 : : s.insert( end(s), begin(f), end(f) );
581 : : }
582 : : }
583 : : // Categorize boundary node lists
584 : : auto& bnode = std::get< 2 >( mesh );
585 [ + + ]: 5245890 : for (const auto& n : t) {
586 : : auto it = nodeside.find( n );
587 [ + + ]: 4196712 : if (it != end(nodeside))
588 [ + - ][ + + ]: 2117037 : for (auto s : it->second)
589 [ + - ]: 1087926 : bnode[ s ].push_back( n );
590 : : }
591 : : }
592 : :
593 : : // Make boundary node lists unique per side set
594 [ + + ]: 11003 : for (auto& c : chmesh)
595 [ + + ]: 18951 : for (auto& n : std::get<2>(c.second))
596 [ + - ]: 8698 : tk::unique( n.second );
597 : :
598 : : // Make sure all compute nodes have target chares assigned
599 : : Assert( !chmesh.empty(), "No elements have been assigned to a chare" );
600 : :
601 : : // This check should always be done, hence ErrChk and not Assert, as it
602 : : // can result from particular pathological combinations of (1) too large
603 : : // degree of virtualization, (2) too many compute nodes, and/or (3) too small
604 : : // of a mesh and not due to programmer error.
605 [ + + ]: 11003 : for(const auto& c : chmesh)
606 [ - + ][ - - ]: 10253 : ErrChk( !std::get<0>(c.second).empty(),
[ - - ][ - - ]
[ - - ][ - - ]
[ - - ][ - - ]
607 : : "Overdecomposition of the mesh is too large compared to the "
608 : : "number of work units computed based on the degree of "
609 : : "virtualization desired. As a result, there would be at least "
610 : : "one work unit with no mesh elements to work on, i.e., nothing "
611 : : "to do. Solution 1: decrease the virtualization to a lower "
612 : : "value using the command-line argument '-u'. Solution 2: "
613 : : "decrease the number processing elements (PEs and/or compute "
614 : : "nodes) using the charmrun command-line argument '+pN' where N is "
615 : : "the number of PEs (or in SMP-mode in combination with +ppn to "
616 : : "reduce the number of compute nodes), which implicitly increases "
617 : : "the size (and thus decreases the number) of work units.)" );
618 : :
619 : 750 : return chmesh;
620 : : }
621 : :
622 : : tk::UnsMesh::CoordMap
623 [ + - ]: 10253 : Partitioner::coordmap( const std::vector< std::size_t >& inpoel )
624 : : // *****************************************************************************
625 : : // Extract coordinates associated to global nodes of a mesh chunk
626 : : //! \param[in] inpoel Mesh connectivity
627 : : //! \return Map storing the coordinates of unique nodes associated to global
628 : : //! node IDs in mesh given by inpoel
629 : : // *****************************************************************************
630 : : {
631 : : Assert( inpoel.size() % 4 == 0, "Incomplete mesh connectivity" );
632 : :
633 : : tk::UnsMesh::CoordMap map;
634 : :
635 [ + - ][ + - ]: 498510 : for (auto g : tk::uniquecopy(inpoel)) {
[ + + ]
636 [ + - ]: 488257 : auto i = tk::cref_find( m_lid, g );
637 : : auto& c = map[g];
638 : 488257 : c[0] = m_coord[0][i];
639 : 488257 : c[1] = m_coord[1][i];
640 : 488257 : c[2] = m_coord[2][i];
641 : : }
642 : :
643 : : Assert( tk::uniquecopy(inpoel).size() == map.size(), "Size mismatch" );
644 : :
645 : 10253 : return map;
646 : : }
647 : :
648 : : void
649 : 750 : Partitioner::distribute( std::unordered_map< int, MeshData >&& mesh )
650 : : // *****************************************************************************
651 : : // Distribute mesh to target compute nodes after mesh partitioning
652 : : //! \param[in] mesh Mesh data categorized by target by target chares
653 : : // *****************************************************************************
654 : : {
655 : 750 : auto dist = distribution( m_nchare );
656 : :
657 : : // Extract mesh data whose chares are on ("owned by") this compute node
658 [ + + ]: 3226 : for (int c=0; c<dist[1]; ++c) {
659 [ + + ]: 2476 : auto chid = CkMyNode() * dist[0] + c; // compute owned chare ID
660 : : const auto it = mesh.find( chid ); // attempt to find its mesh data
661 [ + + ]: 2476 : if (it != end(mesh)) { // if found
662 : : // Store own tetrahedron connectivity
663 : : const auto& inpoel = std::get<0>( it->second );
664 : : auto& inp = m_chinpoel[ chid ]; // will store own mesh connectivity
665 [ + - ][ + - ]: 2149 : inp.insert( end(inp), begin(inpoel), end(inpoel) );
666 : : // Store own node coordinates
667 : : auto& chcm = m_chcoordmap[ chid ]; // will store own node coordinates
668 [ + - ]: 2149 : auto cm = coordmap( inpoel ); // extract node coordinates
669 : : chcm.insert( begin(cm), end(cm) ); // concatenate node coords
670 : : // Store own boundary face connectivity
671 : : const auto& bconn = std::get<1>( it->second );
672 : : auto& bface = m_chbface[ chid ]; // will store own boundary faces
673 [ + - ]: 2149 : auto& t = m_chtriinpoel[ chid ]; // wil store own boundary face conn
674 : : auto& f = m_nface[ chid ]; // use counter for chare
675 [ + + ]: 6510 : for (const auto& [ setid, faceids ] : bconn) {
676 [ + - ]: 4361 : auto& b = bface[ setid ];
677 [ + + ]: 161758 : for (std::size_t i=0; i<faceids.size()/3; ++i) {
678 [ + - ]: 157397 : b.push_back( f++ );
679 [ + - ]: 157397 : t.push_back( faceids[i*3+0] );
680 [ + - ]: 157397 : t.push_back( faceids[i*3+1] );
681 [ + - ]: 157397 : t.push_back( faceids[i*3+2] );
682 : : }
683 : : }
684 : : // Store own boundary node lists
685 : : const auto& bnode = std::get<2>( it->second );
686 : : auto& nodes = m_chbnode[ chid ]; // will store own boundary nodes
687 [ + + ]: 4569 : for (const auto& [ setid, nodeids ] : bnode) {
688 [ + - ]: 2420 : auto& b = nodes[ setid ];
689 [ + - ]: 2420 : b.insert( end(b), begin(nodeids), end(nodeids) );
690 : : }
691 : : // Remove chare ID and mesh data
692 : : mesh.erase( it );
693 : : }
694 : : Assert( mesh.find(chid) == end(mesh), "Not all owned mesh data stored" );
695 : : }
696 : :
697 : : // Construct export map (associating mesh connectivities with global node
698 : : // indices and node coordinates) for mesh chunks associated to chare IDs
699 : : // owned by chares we do not own.
700 : : std::unordered_map< int, // target compute node
701 : : std::unordered_map< int, // chare ID
702 : : std::tuple<
703 : : // (domain-element) tetrahedron connectivity
704 : : std::vector< std::size_t >,
705 : : // (domain) node IDs & coordinates
706 : : tk::UnsMesh::CoordMap,
707 : : // boundary side set + face connectivity
708 : : std::unordered_map< int, std::vector< std::size_t > >,
709 : : // boundary side set + node list
710 : : std::unordered_map< int, std::vector< std::size_t > >
711 : : > > > exp;
712 : :
713 [ + + ]: 8854 : for (const auto& c : mesh)
714 [ + - ][ + - ]: 16208 : exp[ node(c.first) ][ c.first ] =
[ + - ]
715 : 8104 : std::make_tuple( std::get<0>(c.second),
716 [ + - ]: 16208 : coordmap(std::get<0>(c.second)),
717 : : std::get<1>(c.second),
718 : : std::get<2>(c.second) );
719 : :
720 : : // Export chare IDs and mesh we do not own to fellow compute nodes
721 [ + + ]: 750 : if (exp.empty()) {
722 [ + + ][ + - ]: 98 : if (g_cfg.get< tag::feedback >()) m_host.pedistributed();
723 [ + - ]: 98 : contribute( sizeof(std::size_t), &m_meshid, CkReduction::nop,
724 : : m_cbp.get< tag::distributed >() );
725 : : } else {
726 : 652 : m_ndist += exp.size();
727 [ + + ]: 3502 : for (const auto& [ targetnode, chunk ] : exp)
728 [ + - ][ + - ]: 8550 : thisProxy[ targetnode ].addMesh( CkMyNode(), chunk );
729 : : }
730 : 750 : }
731 : :
732 : : std::array< int, 2 >
733 : 1500 : Partitioner::distribution( int npart ) const
734 : : // *****************************************************************************
735 : : // Compute chare (partition) distribution
736 : : //! \param[in] npart Total number of chares (partitions) to distribute
737 : : //! \return Chunksize, i.e., number of chares per all compute nodes except the
738 : : //! last one, and the number of chares for this compute node.
739 : : //! \details Chare ids are distributed to compute nodes in a linear continguous
740 : : //! order with the last compute node taking the remainder if the number of
741 : : //! compute nodes is not divisible by the number chares. For example, if
742 : : //! nchare=7 and nnode=3, the chare distribution is node0: 0 1, node1: 2 3,
743 : : //! and node2: 4 5 6. As a result of this distribution, all compute nodes will
744 : : //! have their chare-categorized element connectivity filled with the global
745 : : //! mesh node IDs associated to the Charm++ chare IDs each compute node owns.
746 : : // *****************************************************************************
747 : : {
748 : 1500 : auto chunksize = npart / CkNumNodes();
749 : : auto mynchare = chunksize;
750 [ + + ]: 1500 : if (CkMyNode() == CkNumNodes()-1) mynchare += npart % CkNumNodes();
751 : 1500 : return {{ chunksize, mynchare }};
752 : : }
753 : :
754 : : #include "NoWarning/partitioner.def.h"
|