Zoltan2
Loading...
Searching...
No Matches
Zoltan2_AlgParMA.hpp
Go to the documentation of this file.
1// @HEADER
2// *****************************************************************************
3// Zoltan2: A package of combinatorial algorithms for scientific computing
4//
5// Copyright 2012 NTESS and the Zoltan2 contributors.
6// SPDX-License-Identifier: BSD-3-Clause
7// *****************************************************************************
8// @HEADER
9
10#ifndef _ZOLTAN2_ALGPARMA_HPP_
11#define _ZOLTAN2_ALGPARMA_HPP_
12
13#include <Zoltan2_Algorithm.hpp>
15#include <Zoltan2_Util.hpp>
16#include <Zoltan2_TPLTraits.hpp>
17
21//
22// This design creates an apf mesh to run the ParMA algorithms on. The
23// final solution is determined by changes from beginning to end of the mesh.
24// This approach allows development closer to that of PUMI setup but at the
25// cost of creating an extra mesh representation.
26//
27// Available ParMA algorithms are given by setting the parma_method parameter
28// of the sublist parma_paramaters to one of the following:
29// Vertex - Balances targeting vertex imbalance
30// Element - Balances targeting element imbalance
31// VtxElm - Balances targeting vertex and element imbalance
32// VtxEdgeElm - Balances targeting vertex, edge, and element imbalance
33// Ghost - Balances using ghost element aware diffusion
34// Shape - Optimizes shape of parts by increasing the size of small part boundaries
35// Centroid - Balances using centroid diffusion
37
38#ifndef HAVE_ZOLTAN2_PARMA
39
40// Error handling for when ParMA is requested
41// but Zoltan2 not built with ParMA.
42
43namespace Zoltan2 {
44template <typename Adapter>
45class AlgParMA : public Algorithm<Adapter>
46{
47public:
48 typedef typename Adapter::user_t user_t;
49
50 AlgParMA(const RCP<const Environment> &/* env */,
51 const RCP<const Comm<int> > &/* problemComm */,
52 const RCP<const BaseAdapter<user_t> > &/* adapter */)
53 {
54 throw std::runtime_error(
55 "BUILD ERROR: ParMA requested but not compiled into Zoltan2.\n"
56 "Please set CMake flag Zoltan2_ENABLE_ParMA:BOOL=ON.");
57 }
58};
59}
60
61#endif
62
63#ifdef HAVE_ZOLTAN2_PARMA
64
65
66#include <apf.h>
67#include <gmi_null.h>
68#include <apfMDS.h>
69#include <apfMesh2.h>
70#include <apfNumbering.h>
71#include <PCU.h>
72#include <parma.h>
73#include <apfConvert.h>
74#include <apfShape.h>
75#include <map>
76#include <cassert>
77
78namespace Zoltan2 {
79
80template <typename Adapter>
81class AlgParMA : public Algorithm<Adapter>
82{
83
84private:
85
86 typedef typename Adapter::lno_t lno_t;
87 typedef typename Adapter::gno_t gno_t;
88 typedef typename Adapter::scalar_t scalar_t;
89 typedef typename Adapter::offset_t offset_t;
90 typedef typename Adapter::part_t part_t;
91 typedef typename Adapter::user_t user_t;
92 typedef typename Adapter::userCoord_t userCoord_t;
93
94 const RCP<const Environment> env;
95 const RCP<const Comm<int> > problemComm;
96 const RCP<const MeshAdapter<user_t> > adapter;
97
98 apf::Mesh2* m;
99 apf::Numbering* gids;
100 apf::Numbering* origin_part_ids;
101 std::map<gno_t, lno_t> mapping_elm_gids_index;
102
103 MPI_Comm mpicomm;
104 bool pcu_outside;
105
106 void setMPIComm(const RCP<const Comm<int> > &problemComm__) {
107# ifdef HAVE_ZOLTAN2_MPI
108 mpicomm = Teuchos::getRawMpiComm(*problemComm__);
109# else
110 mpicomm = MPI_COMM_WORLD; // taken from siMPI
111# endif
112 }
113 // provides conversion from an APF entity dimension to a Zoltan2 entity type
114 enum MeshEntityType entityAPFtoZ2(int dimension) const {return static_cast<MeshEntityType>(dimension);}
115
116 //provides a conversion from the Zoltan2 topology type to and APF type
117 // throws an error on topology types not supported by APF
118 enum apf::Mesh::Type topologyZ2toAPF(enum EntityTopologyType ttype) const {
119 if (ttype==POINT)
120 return apf::Mesh::VERTEX;
121 else if (ttype==LINE_SEGMENT)
122 return apf::Mesh::EDGE;
123 else if (ttype==TRIANGLE)
124 return apf::Mesh::TRIANGLE;
125 else if (ttype==QUADRILATERAL)
126 return apf::Mesh::QUAD;
127 else if (ttype==TETRAHEDRON)
128 return apf::Mesh::TET;
129 else if (ttype==HEXAHEDRON)
130 return apf::Mesh::HEX;
131 else if (ttype==PRISM)
132 return apf::Mesh::PRISM;
133 else if (ttype==PYRAMID)
134 return apf::Mesh::PYRAMID;
135 else
136 throw std::runtime_error("APF does not support this topology type");
137
138 }
139
140 //Sets the weights of each entity in dimension 'dim' to those provided by the mesh adapter
141 //sets all weights in the mesh adapter but currently only one is considered by ParMA
142 void setEntWeights(int dim, apf::MeshTag* tag) {
143 MeshEntityType etype = entityAPFtoZ2(dim);
144 for (int i=0;i<m->getTagSize(tag);i++) {
145 apf::MeshIterator* itr = m->begin(dim);
146 apf::MeshEntity* ent;
147 const scalar_t* ws=NULL;
148 int stride;
149 if (i<adapter->getNumWeightsPerOf(etype))
150 adapter->getWeightsViewOf(etype,ws,stride,i);
151 int j=0;
152 while ((ent= m->iterate(itr))) {
153 double w = 1.0;
154 if (ws!=NULL)
155 w = static_cast<double>(ws[j]);
156 m->setDoubleTag(ent,tag,&w);
157 j++;
158 }
159 m->end(itr);
160 }
161 }
162
163 //Helper function to set the weights of each dimension needed by the specific parma algorithm
164 apf::MeshTag* setWeights(bool vtx, bool edge, bool face, bool elm) {
165 int num_ws=1;
166 if (vtx)
167 num_ws = std::max(num_ws,adapter->getNumWeightsPerOf(MESH_VERTEX));
168 if (edge)
169 num_ws = std::max(num_ws,adapter->getNumWeightsPerOf(MESH_EDGE));
170 if (face)
171 num_ws = std::max(num_ws,adapter->getNumWeightsPerOf(MESH_FACE));
172 if (elm)
173 num_ws = std::max(num_ws,adapter->getNumWeightsPerOf(entityAPFtoZ2(m->getDimension())));
174 apf::MeshTag* tag = m->createDoubleTag("parma_weight",num_ws);
175 if (vtx)
176 setEntWeights(0,tag);
177 if (edge)
178 setEntWeights(1,tag);
179 if (face)
180 setEntWeights(2,tag);
181 if (elm) {
182 setEntWeights(m->getDimension(),tag);
183 }
184 return tag;
185 }
186
187
188 //APF Mesh construction helper functions modified and placed here to support arbitrary entity types
189 void constructElements(const gno_t* conn, lno_t nelem, const offset_t* offsets,
190 const EntityTopologyType* tops, apf::GlobalToVert& globalToVert)
191 {
192 apf::ModelEntity* interior = m->findModelEntity(m->getDimension(), 0);
193 for (lno_t i = 0; i < nelem; ++i) {
194 apf::Mesh::Type etype = topologyZ2toAPF(tops[i]);
195 apf::Downward verts;
196 for (offset_t j = offsets[i]; j < offsets[i+1]; ++j)
197 verts[j-offsets[i]] = globalToVert[conn[j]];
198 buildElement(m, interior, etype, verts);
199 }
200 }
201 int getMax(const apf::GlobalToVert& globalToVert)
202 {
203 int max = -1;
204 APF_CONST_ITERATE(apf::GlobalToVert, globalToVert, it)
205 max = std::max(max, it->first);
206 PCU_Max_Ints(&max, 1); // this is type-dependent
207 return max;
208 }
209 void constructResidence(apf::GlobalToVert& globalToVert)
210 {
211 int max = getMax(globalToVert);
212 int total = max + 1;
213 int peers = PCU_Comm_Peers();
214 int quotient = total / peers;
215 int remainder = total % peers;
216 int mySize = quotient;
217 int self = PCU_Comm_Self();
218 if (self == (peers - 1))
219 mySize += remainder;
220 typedef std::vector< std::vector<int> > TmpParts;
221 TmpParts tmpParts(mySize);
222 /* if we have a vertex, send its global id to the
223 broker for that global id */
224 PCU_Comm_Begin();
225 APF_ITERATE(apf::GlobalToVert, globalToVert, it) {
226 int gid = it->first;
227 int to = std::min(peers - 1, gid / quotient);
228 PCU_COMM_PACK(to, gid);
229 }
230 PCU_Comm_Send();
231 int myOffset = self * quotient;
232 /* brokers store all the part ids that sent messages
233 for each global id */
234 while (PCU_Comm_Receive()) {
235 int gid;
236 PCU_COMM_UNPACK(gid);
237 int from = PCU_Comm_Sender();
238 tmpParts.at(gid - myOffset).push_back(from);
239 }
240 /* for each global id, send all associated part ids
241 to all associated parts */
242 PCU_Comm_Begin();
243 for (int i = 0; i < mySize; ++i) {
244 std::vector<int>& parts = tmpParts[i];
245 for (size_t j = 0; j < parts.size(); ++j) {
246 int to = parts[j];
247 int gid = i + myOffset;
248 int nparts = parts.size();
249 PCU_COMM_PACK(to, gid);
250 PCU_COMM_PACK(to, nparts);
251 for (size_t k = 0; k < parts.size(); ++k)
252 PCU_COMM_PACK(to, parts[k]);
253 }
254 }
255 PCU_Comm_Send();
256 /* receiving a global id and associated parts,
257 lookup the vertex and classify it on the partition
258 model entity for that set of parts */
259 while (PCU_Comm_Receive()) {
260 int gid;
261 PCU_COMM_UNPACK(gid);
262 int nparts;
263 PCU_COMM_UNPACK(nparts);
264 apf::Parts residence;
265 for (int i = 0; i < nparts; ++i) {
266 int part;
267 PCU_COMM_UNPACK(part);
268 residence.insert(part);
269 }
270 apf::MeshEntity* vert = globalToVert[gid];
271 m->setResidence(vert, residence);
272 }
273 }
274
275 /* given correct residence from the above algorithm,
276 negotiate remote copies by exchanging (gid,pointer)
277 pairs with parts in the residence of the vertex */
278 void constructRemotes(apf::GlobalToVert& globalToVert)
279 {
280 int self = PCU_Comm_Self();
281 PCU_Comm_Begin();
282 APF_ITERATE(apf::GlobalToVert, globalToVert, it) {
283 int gid = it->first;
284 apf::MeshEntity* vert = it->second;
285 apf::Parts residence;
286 m->getResidence(vert, residence);
287 APF_ITERATE(apf::Parts, residence, rit)
288 if (*rit != self) {
289 PCU_COMM_PACK(*rit, gid);
290 PCU_COMM_PACK(*rit, vert);
291 }
292 }
293 PCU_Comm_Send();
294 while (PCU_Comm_Receive()) {
295 int gid;
296 PCU_COMM_UNPACK(gid);
297 apf::MeshEntity* remote;
298 PCU_COMM_UNPACK(remote);
299 int from = PCU_Comm_Sender();
300 apf::MeshEntity* vert = globalToVert[gid];
301 m->addRemote(vert, from, remote);
302 }
303 }
304
305public:
306
312 AlgParMA(const RCP<const Environment> &env__,
313 const RCP<const Comm<int> > &problemComm__,
314 const RCP<const IdentifierAdapter<user_t> > &adapter__)
315 {
316 throw std::runtime_error("ParMA needs a MeshAdapter but you haven't given it one");
317 }
318
319 AlgParMA(const RCP<const Environment> &env__,
320 const RCP<const Comm<int> > &problemComm__,
321 const RCP<const VectorAdapter<user_t> > &adapter__)
322 {
323 throw std::runtime_error("ParMA needs a MeshAdapter but you haven't given it one");
324 }
325
326 AlgParMA(const RCP<const Environment> &env__,
327 const RCP<const Comm<int> > &problemComm__,
328 const RCP<const GraphAdapter<user_t,userCoord_t> > &adapter__)
329 {
330 throw std::runtime_error("ParMA needs a MeshAdapter but you haven't given it one");
331 }
332
333 AlgParMA(const RCP<const Environment> &env__,
334 const RCP<const Comm<int> > &problemComm__,
335 const RCP<const MatrixAdapter<user_t,userCoord_t> > &adapter__)
336 {
337 throw std::runtime_error("ParMA needs a MeshAdapter but you haven't given it one");
338
339 }
340
341 AlgParMA(const RCP<const Environment> &env__,
342 const RCP<const Comm<int> > &problemComm__,
343 const RCP<const MeshAdapter<user_t> > &adapter__) :
344 env(env__), problemComm(problemComm__), adapter(adapter__)
345 {
346 setMPIComm(problemComm__);
347
348 //Setup PCU communications
349 //If PCU was already initialized outside (EX: for the APFMeshAdapter)
350 // we don't initialize it again.
351 pcu_outside=false;
352 if (!PCU_Comm_Initialized())
353 PCU_Comm_Init();
354 else
355 pcu_outside=true;
356 PCU_Switch_Comm(mpicomm);
357
358 //Find the mesh dimension based on if there are any regions or faces in the part
359 // an all reduce is needed in case one part is empty (Ex: after hypergraph partitioning)
360 int dim;
361 if (adapter->getLocalNumOf(MESH_REGION)>0)
362 dim=3;
363 else if (adapter->getLocalNumOf(MESH_FACE)>0)
364 dim=2;
365 else
366 dim=0;
367 PCU_Max_Ints(&dim,1);
368 if (dim<2)
369 throw std::runtime_error("ParMA neeeds faces or region information");
370
371 //GFD Currently not allowing ParMA to balance non element primary types
372 if (dim!=adapter->getPrimaryEntityType())
373 throw std::runtime_error("ParMA only supports balancing primary type==mesh element");
374
375 //Create empty apf mesh
376 gmi_register_null();
377 gmi_model* g = gmi_load(".null");
378 enum MeshEntityType primary_type = entityAPFtoZ2(dim);
379 m = apf::makeEmptyMdsMesh(g,dim,false);
380
381 //Get entity topology types
382 const EntityTopologyType* tops;
383 try {
384 adapter->getTopologyViewOf(primary_type,tops);
385 }
387
388 //Get element global ids and part ids
389 const gno_t* element_gids;
390 const part_t* part_ids;
391 adapter->getIDsViewOf(primary_type,element_gids);
392 adapter->getPartsView(part_ids);
393 for (size_t i =0;i<adapter->getLocalNumOf(primary_type);i++)
394 mapping_elm_gids_index[element_gids[i]] = i;
395
396 //get vertex global ids
397 const gno_t* vertex_gids;
398 adapter->getIDsViewOf(MESH_VERTEX,vertex_gids);
399
400 //Get vertex coordinates
401 int c_dim = adapter->getDimension();
402 const scalar_t ** vertex_coords = new const scalar_t*[c_dim];
403 int* strides = new int[c_dim];
404 for (int i=0;i<c_dim;i++)
405 adapter->getCoordinatesViewOf(MESH_VERTEX,vertex_coords[i],strides[i],i);
406
407 //Get first adjacencies from elements to vertices
408 if (!adapter->availAdjs(primary_type,MESH_VERTEX))
409 throw "APF needs adjacency information from elements to vertices";
410 const offset_t* offsets;
411 const gno_t* adjacent_vertex_gids;
412 adapter->getAdjsView(primary_type, MESH_VERTEX,offsets,adjacent_vertex_gids);
413
414 //build the apf mesh
415 apf::GlobalToVert vertex_mapping;
416 apf::ModelEntity* interior = m->findModelEntity(m->getDimension(), 0);
417 for (size_t i=0;i<adapter->getLocalNumOf(MESH_VERTEX);i++) {
418 apf::MeshEntity* vtx = m->createVert_(interior);
419 scalar_t temp_coords[3];
420 for (int k=0;k<c_dim&&k<3;k++)
421 temp_coords[k] = vertex_coords[k][i*strides[k]];
422
423 for (int k=c_dim;k<3;k++)
424 temp_coords[k] = 0;
425 apf::Vector3 point(temp_coords[0],temp_coords[1],temp_coords[2]);
426 m->setPoint(vtx,0,point);
427 vertex_mapping[vertex_gids[i]] = vtx;
428 }
429 //Call modified helper functions to build the mesh from element to vertex adjacency
430 constructElements(adjacent_vertex_gids, adapter->getLocalNumOf(primary_type), offsets, tops, vertex_mapping);
431 constructResidence(vertex_mapping);
432 constructRemotes(vertex_mapping);
433 stitchMesh(m);
434 m->acceptChanges();
435
436
437 //Setup numberings of global ids and original part ids
438 // for use after ParMA is run
439 apf::FieldShape* s = apf::getConstant(dim);
440 gids = apf::createNumbering(m,"global_ids",s,1);
441 origin_part_ids = apf::createNumbering(m,"origin",s,1);
442
443 //number the global ids and original part ids
444 apf::MeshIterator* itr = m->begin(dim);
445 apf::MeshEntity* ent;
446 int i = 0;
447 while ((ent = m->iterate(itr))) {
448 apf::number(gids,ent,0,0,element_gids[i]);
449 apf::number(origin_part_ids,ent,0,0,PCU_Comm_Self());
450 i++;
451 }
452 m->end(itr);
453
454 //final setup for apf mesh
455 apf::alignMdsRemotes(m);
456 apf::deriveMdsModel(m);
457 m->acceptChanges();
458 m->verify();
459
460 //cleanup temp storage
461 delete [] vertex_coords;
462 delete [] strides;
463 }
464 void partition(const RCP<PartitioningSolution<Adapter> > &solution);
465
466};
467
469template <typename Adapter>
471 const RCP<PartitioningSolution<Adapter> > &solution
472)
473{
474 //Get parameters
475 std::string alg_name = "VtxElm";
476 double imbalance = 1.1;
477 double step = .5;
478 int ghost_layers=3;
479 int ghost_bridge=m->getDimension()-1;
480
481 //Get the parameters for ParMA
482 const Teuchos::ParameterList &pl = env->getParameters();
483 try {
484 const Teuchos::ParameterList &ppl = pl.sublist("parma_parameters");
485 for (ParameterList::ConstIterator iter = ppl.begin();
486 iter != ppl.end(); iter++) {
487 const std::string &zname = pl.name(iter);
488 if (zname == "parma_method") {
489 std::string zval = pl.entry(iter).getValue(&zval);
490 alg_name = zval;
491 }
492 else if (zname == "step_size") {
493 double zval = pl.entry(iter).getValue(&zval);
494 step = zval;
495 }
496 else if (zname=="ghost_layers" || zname=="ghost_bridge") {
497 int zval = pl.entry(iter).getValue(&zval);
498 if (zname=="ghost_layers")
499 ghost_layers = zval;
500 else
501 ghost_bridge = zval;
502 }
503 }
504 }
505 catch (std::exception &e) {
506 //No parma_parameters sublist found
507 }
508
509 const Teuchos::ParameterEntry *pe2 = pl.getEntryPtr("imbalance_tolerance");
510 if (pe2){
511 imbalance = pe2->getValue<double>(&imbalance);
512 }
513
514 //booleans for which dimensions need weights
515 bool weightVertex,weightEdge,weightFace,weightElement;
516 weightVertex=weightEdge=weightFace=weightElement=false;
517
518 //Build the selected balancer
519 apf::Balancer* balancer;
520 const int verbose = 1;
521 if (alg_name=="Vertex") {
522 balancer = Parma_MakeVtxBalancer(m, step, verbose);
523 weightVertex = true;
524 }
525 else if (alg_name=="Element") {
526 balancer = Parma_MakeElmBalancer(m, step, verbose);
527 weightElement=true;
528 }
529 else if (alg_name=="VtxElm") {
530 balancer = Parma_MakeVtxElmBalancer(m,step,verbose);
531 weightVertex = weightElement=true;
532 }
533 else if (alg_name=="VtxEdgeElm") {
534 balancer = Parma_MakeVtxEdgeElmBalancer(m, step, verbose);
535 weightVertex=weightEdge=weightElement=true;
536 }
537 else if (alg_name=="Ghost") {
538 balancer = Parma_MakeGhostDiffuser(m, ghost_layers, ghost_bridge, step, verbose);
539 weightVertex=weightEdge=weightFace=true;
540 if (3 == m->getDimension()) {
541 weightElement=true;
542 }
543 }
544 else if (alg_name=="Shape") {
545 balancer = Parma_MakeShapeOptimizer(m,step,verbose);
546 weightElement=true;
547 }
548 else if (alg_name=="Centroid") {
549 balancer = Parma_MakeCentroidDiffuser(m,step,verbose);
550 weightElement=true;
551 }
552 else {
553 throw std::runtime_error("No such parma method defined");
554 }
555
556 //build the weights
557 apf::MeshTag* weights = setWeights(weightVertex,weightEdge,weightFace,weightElement);
558
559 //balance the apf mesh
560 balancer->balance(weights, imbalance);
561 delete balancer;
562
563 // Load answer into the solution.
564 int num_local = adapter->getLocalNumOf(adapter->getPrimaryEntityType());
565 ArrayRCP<part_t> partList(new part_t[num_local], 0, num_local, true);
566
567 //Setup for communication
568 PCU_Comm_Begin();
569 apf::MeshEntity* ent;
570 apf::MeshIterator* itr = m->begin(m->getDimension());
571 //Pack information back to each elements original owner
572 while ((ent=m->iterate(itr))) {
573 if (m->isOwned(ent)) {
574 part_t target_part_id = apf::getNumber(origin_part_ids,ent,0,0);
575 gno_t element_gid = apf::getNumber(gids,ent,0,0);
576 PCU_COMM_PACK(target_part_id,element_gid);
577 }
578 }
579 m->end(itr);
580
581 //Send information off
582 PCU_Comm_Send();
583
584 //Unpack information and set new part ids
585 while (PCU_Comm_Receive()) {
586 gno_t global_id;
587 PCU_COMM_UNPACK(global_id);
588 lno_t local_id = mapping_elm_gids_index[global_id];
589 part_t new_part_id = PCU_Comm_Sender();
590 partList[local_id] = new_part_id;
591 }
592 //construct partition solution
593 solution->setParts(partList);
594
595 // Clean up
596 apf::destroyNumbering(gids);
597 apf::destroyNumbering(origin_part_ids);
598 apf::removeTagFromDimension(m, weights, m->getDimension());
599 m->destroyTag(weights);
600 m->destroyNative();
601 apf::destroyMesh(m);
602 //only free PCU if it isn't being used outside
603 if (!pcu_outside)
604 PCU_Comm_Free();
605}
606
607} // namespace Zoltan2
608
609#endif // HAVE_ZOLTAN2_PARMA
610
611#endif
#define Z2_FORWARD_EXCEPTIONS
Forward an exception back through call stack.
Defines the PartitioningSolution class.
Traits class to handle conversions between gno_t/lno_t and TPL data types (e.g., ParMETIS's idx_t,...
A gathering of useful namespace methods.
Adapter::user_t user_t
AlgParMA(const RCP< const Environment > &, const RCP< const Comm< int > > &, const RCP< const BaseAdapter< user_t > > &)
Algorithm defines the base class for all algorithms.
virtual void partition(const RCP< PartitioningSolution< Adapter > > &)
Partitioning method.
Adapter::scalar_t scalar_t
map_t::local_ordinal_type lno_t
map_t::global_ordinal_type gno_t
Created by mbenlioglu on Aug 31, 2020.
EntityTopologyType
Enumerate entity topology types for meshes: points,lines,polygons,triangles,quadrilaterals,...
MeshEntityType
Enumerate entity types for meshes: Regions, Faces, Edges, or Vertices.
static ArrayRCP< ArrayRCP< zscalar_t > > weights
SparseMatrixAdapter_t::part_t part_t