Arcane  4.1.12.0
Developer documentation
Loading...
Searching...
No Matches
MetisMeshPartitioner.cc
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2026 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com)
4// See the top-level COPYRIGHT file for details.
5// SPDX-License-Identifier: Apache-2.0
6//-----------------------------------------------------------------------------
7/*---------------------------------------------------------------------------*/
8/* MetisMeshPartitioner.cc (C) 2000-2025 */
9/* */
10/* Mesh partitioner using the PARMetis library. */
11/*---------------------------------------------------------------------------*/
12/*---------------------------------------------------------------------------*/
13
14#include "arcane/utils/StringBuilder.h"
15#include "arcane/utils/Iostream.h"
16#include "arcane/utils/PlatformUtils.h"
17#include "arcane/utils/Convert.h"
18#include "arcane/utils/NotImplementedException.h"
19#include "arcane/utils/ArgumentException.h"
20#include "arcane/utils/FloatingPointExceptionSentry.h"
21
22#include "arcane/core/ISubDomain.h"
23#include "arcane/core/IParallelMng.h"
25#include "arcane/core/IMesh.h"
26#include "arcane/core/IMeshSubMeshTransition.h"
27#include "arcane/core/ItemGroup.h"
28#include "arcane/core/Service.h"
29#include "arcane/core/Timer.h"
30#include "arcane/core/FactoryService.h"
31#include "arcane/core/ItemPrinter.h"
32#include "arcane/core/IItemFamily.h"
33#include "arcane/core/MeshVariable.h"
34#include "arcane/core/VariableBuildInfo.h"
35
36#include "arcane/std/MeshPartitionerBase.h"
37#include "arcane/std/MetisMeshPartitioner_axl.h"
38
39#include "arcane_internal_config.h"
40
41// In case we use mpich2 or openmpi to avoid including C++
42#define MPICH_SKIP_MPICXX
43#define OMPI_SKIP_MPICXX
44#include <parmetis.h>
45// #define CEDRIC_LB_2
46
47#if (PARMETIS_MAJOR_VERSION < 4)
48#error "Your version of Parmetis is too old .Version 4.0+ is required"
49#endif
50
51typedef real_t realtype;
52typedef idx_t idxtype;
53
54#include "arcane/std/PartitionConverter.h"
55#include "arcane/std/GraphDistributor.h"
56#include "arcane/std/internal/MetisWrapper.h"
57
58#include <chrono>
59
60/*---------------------------------------------------------------------------*/
61/*---------------------------------------------------------------------------*/
62
63namespace Arcane
64{
65
66using MetisCallStrategy = TypesMetisMeshPartitioner::MetisCallStrategy;
67using MetisEmptyPartitionStrategy = TypesMetisMeshPartitioner::MetisEmptyPartitionStrategy;
68
69/*---------------------------------------------------------------------------*/
70/*---------------------------------------------------------------------------*/
71
75class MetisMeshPartitioner
77{
78 public:
79
80 explicit MetisMeshPartitioner(const ServiceBuildInfo& sbi);
81
82 public:
83
84 void build() override {}
85
86 public:
87
88 void partitionMesh(bool initial_partition) override;
89 void partitionMesh(bool initial_partition, Int32 nb_part) override;
90
91 private:
92
93 IParallelMng* m_parallel_mng = nullptr;
94 Integer m_nb_refine = -1;
95 Integer m_random_seed = 15;
96 bool m_disable_floatingexception = false;
97 void _partitionMesh(bool initial_partition, Int32 nb_part);
98 void _removeEmptyPartsV1(Int32 nb_part, Int32 nb_own_cell, ArrayView<idxtype> metis_part);
99 void _removeEmptyPartsV2(Int32 nb_part, ArrayView<idxtype> metis_part);
100 Int32 _removeEmptyPartsV2Helper(Int32 nb_part, ArrayView<idxtype> metis_part, Int32 algo_iteration);
102 Span<const idxtype> metis_vtkdist,
103 Span<const idxtype> metis_xadj,
104 Span<const idxtype> metis_adjncy,
105 Span<const idxtype> metis_vwgt,
106 Span<const idxtype> metis_ewgt,
107 const String& Name) const;
108};
109
110/*---------------------------------------------------------------------------*/
111/*---------------------------------------------------------------------------*/
112
113MetisMeshPartitioner::
114MetisMeshPartitioner(const ServiceBuildInfo& sbi)
115: ArcaneMetisMeshPartitionerObject(sbi)
116{
117 m_parallel_mng = mesh()->parallelMng();
118 String s = platform::getEnvironmentVariable("ARCANE_DISABLE_METIS_FPE");
119 if (s == "1" || s == "TRUE")
120 m_disable_floatingexception = true;
121}
122
123/*---------------------------------------------------------------------------*/
124/*---------------------------------------------------------------------------*/
125
127partitionMesh(bool initial_partition)
128{
129 Int32 nb_part = m_parallel_mng->commSize();
130 partitionMesh(initial_partition, nb_part);
131}
132
133/*---------------------------------------------------------------------------*/
134/*---------------------------------------------------------------------------*/
135
137partitionMesh(bool initial_partition, Int32 nb_part)
138{
139 // Signals that partitioning might crash, because metis is not always
140 // very reliable on floating point calculations.
141 initial_partition = (subDomain()->commonVariables().globalIteration() <= 2);
142 if (m_disable_floatingexception) {
144 _partitionMesh(initial_partition, nb_part);
145 }
146 else
147 _partitionMesh(initial_partition, nb_part);
148}
149
150/*---------------------------------------------------------------------------*/
151/*---------------------------------------------------------------------------*/
152
154_partitionMesh(bool initial_partition, Int32 nb_part)
155{
156 ISubDomain* sd = subDomain();
157 IParallelMng* pm = m_parallel_mng;
158 Int32 my_rank = pm->commRank();
159 Int32 nb_rank = pm->commSize();
160 IMesh* mesh = this->mesh();
161 bool force_partition = false;
162
163 if (nb_part < nb_rank)
164 ARCANE_THROW(ArgumentException, "partition with nb_part ({0}) < nb_rank ({1})",
165 nb_part, nb_rank);
166
167 if (nb_rank == 1) {
168 info() << "INFO: Unable to test load balancing on a single sub-domain";
169 return;
170 }
171
172 // TODO: understand the IFPEN modifications
173 // always uses full re-partitioning.
174 // info() << "Metis: params " << m_nb_refine << " " << imbalance() << " " << maxImbalance();
175 // info() << "Metis: params " << (m_nb_refine>=10) << " " << (imbalance()>4.0*maxImbalance()) << " " << (imbalance()>1.0);
176 bool force_full_repartition = false;
177 //force_full_repartition = true;
178 force_full_repartition |= (m_nb_refine < 0); // always the first time, for compatibility with the old version
179
180 if (nb_part != nb_rank) { // No "repartitioning" if the number of parts does not match the number of processors.
181 force_full_repartition = true;
182 force_partition = true;
183 }
184
185 info() << "WARNING: compensating the potential lack of 'Metis' options in case of manual instanciation";
186 Integer max_diffusion_count = 10; // recovery of default options from axl
187 Real imbalance_relative_tolerance = 4;
188 float tolerance_target = 1.05f;
189 bool dump_graph = false;
190 MetisCallStrategy call_strategy = MetisCallStrategy::one_processor_per_node;
191 bool in_out_digest = false;
192
193 // Environment variables allowing options to be set when there is no dataset
194
195 String call_strategy_env = platform::getEnvironmentVariable("ARCANE_METIS_CALL_STRATEGY");
196 if (call_strategy_env == "all-processors") {
197 call_strategy = MetisCallStrategy::all_processors;
198 }
199 else if (call_strategy_env == "one-processor-per-node") {
200 call_strategy = MetisCallStrategy::one_processor_per_node;
201 }
202 else if (call_strategy_env == "two-processors-two-nodes") {
203 call_strategy = MetisCallStrategy::two_processors_two_nodes;
204 }
205 else if (call_strategy_env == "two-gathered-processors") {
206 call_strategy = MetisCallStrategy::two_gathered_processors;
207 }
208 else if (call_strategy_env == "two-scattered-processors") {
209 call_strategy = MetisCallStrategy::two_scattered_processors;
210 }
211 else if (!call_strategy_env.null()) {
212 ARCANE_FATAL("Invalid value '{0}' for ARCANE_METIS_CALL_STRATEGY environment variable", call_strategy_env);
213 }
214
215 if (auto v = Convert::Type<Int32>::tryParseFromEnvironment("ARCANE_METIS_INPUT_OUTPUT_DIGEST", true))
216 in_out_digest = (v.value() != 0);
217 if (auto v = Convert::Type<Int32>::tryParseFromEnvironment("ARCANE_METIS_DUMP_GRAPH", true))
218 dump_graph = (v.value() != 0);
219
220 if (options()) {
221 max_diffusion_count = options()->maxDiffusiveCount();
222 imbalance_relative_tolerance = options()->imbalanceRelativeTolerance();
223 tolerance_target = (float)(options()->toleranceTarget());
224 dump_graph = options()->dumpGraph();
225 call_strategy = options()->metisCallStrategy();
226 in_out_digest = options()->inputOutputDigest();
227 }
228
229 if (max_diffusion_count > 0)
230 force_full_repartition |= (m_nb_refine >= max_diffusion_count);
231 force_full_repartition |= (imbalance() > imbalance_relative_tolerance * maxImbalance());
232 force_full_repartition |= (imbalance() > 1.0);
233
234 // initialization for constraint management (except initUidRef)
235 initConstraints(false);
236
237 bool is_shared_memory = pm->isThreadImplementation();
238 // In shared memory mode, for now we force the use of a single
239 // processor per node because we do not know if we have multiple ranks per node.
240 // Notably, in shared memory mode without MPI we only have one node
241 if (is_shared_memory)
242 call_strategy = MetisCallStrategy::one_processor_per_node;
243
244 idxtype nb_weight = nbCellWeight();
245
246 info() << "Load balancing with Metis nb_weight=" << nb_weight << " initial=" << initial_partition
247 << " call_strategy=" << (int)call_strategy
248 << " is_shared_memory?=" << is_shared_memory
249 << " disabling_fpe?=" << m_disable_floatingexception
250 << " sizeof(idxtype)==" << sizeof(idxtype);
251
252 if (nb_weight == 0)
253 initial_partition = true;
254
255 UniqueArray<idxtype> metis_vtkdist(nb_rank + 1);
256 Integer total_nb_cell = 0;
257
258 // Contains the unique numbers of entities in the renumbering
259 // specific to metis
260 VariableCellInteger cell_metis_uid(VariableBuildInfo(mesh, "CellsMetisUid", IVariable::PNoDump));
261
262 UniqueArray<Integer> global_nb_own_cell(nb_rank);
263 CellGroup own_cells = mesh->ownCells();
264 Integer nb_own_cell = nbOwnCellsWithConstraints(); // we take constraints into account
265 pm->allGather(ConstArrayView<Integer>(1, &nb_own_cell), global_nb_own_cell);
266 Int32 nb_empty_part = 0;
267 {
268 //Integer total_first_uid = 0;
269 metis_vtkdist[0] = 0;
270 for (Integer i = 0; i < nb_rank; ++i) {
271 //total_first_uid += global_nb_own_cell[i];
272 Int32 n = global_nb_own_cell[i];
273 if (n == 0)
274 ++nb_empty_part;
275 total_nb_cell += n;
276 metis_vtkdist[i + 1] = static_cast<idxtype>(total_nb_cell);
277 // info() << "METIS VTKDIST " << (i+1) << ' ' << metis_vtkdist[i+1];
278 }
279 }
280 // Does not call Parmetis if there are no cells because otherwise it causes an error.
281 info() << "Total nb_cell=" << total_nb_cell << " nb_empty_partition=" << nb_empty_part;
282 if (total_nb_cell == 0) {
283 info() << "INFO: mesh '" << mesh->name() << " has no cell. No partitioning is needed";
284 freeConstraints();
285 return;
286 }
287
288 // HP: Skip this shortcut because it produces undefined itemsNewOwner variable.
289 /*
290 if (total_nb_cell < nb_rank) { // In this case, it's not worth calling ParMetis.
291 warning() << "There are no subdomain except cells, no reffinement";
292 freeConstraints();
293 return;
294 }
295*/
296
297 // Maximum number of neighboring cells connected to the cells
298 // assuming the cells are connected only by faces
299 // (or edges for a 2D cell in a 3D mesh)
300 // This value is used to pre-allocate memory for the list of neighboring cells
301 Integer nb_max_face_neighbour_cell = 0;
302 {
303 // Renumbers the cells for Metis so that each subdomain
304 // has cells with consecutive numbers
305 Integer mid = static_cast<Integer>(metis_vtkdist[my_rank]);
306 ENUMERATE_ (Cell, i_item, own_cells) {
307 Cell item = *i_item;
308 if (cellUsedWithConstraints(item)) {
309 cell_metis_uid[item] = mid;
310 ++mid;
311 }
312 if (item.hasFlags(ItemFlags::II_HasEdgeFor1DItems))
313 nb_max_face_neighbour_cell += item.nbEdge();
314 else
315 nb_max_face_neighbour_cell += item.nbFace();
316 }
317 cell_metis_uid.synchronize();
318 }
319
320 _initUidRef(cell_metis_uid);
321
322 // memory release
323 cell_metis_uid.setUsed(false);
324
325 SharedArray<idxtype> metis_xadj;
326 metis_xadj.reserve(nb_own_cell + 1);
327
328 SharedArray<idxtype> metis_adjncy;
329 metis_adjncy.reserve(nb_max_face_neighbour_cell);
330
331 // Construction of the connectivity between cells and their neighbors taking constraints into account
332 // (Connectivity is done following the faces)
333 Int64UniqueArray neighbour_cells;
334 UniqueArray<float> edge_weights;
335 edge_weights.resize(0);
336 ENUMERATE_CELL (i_item, own_cells) {
337 Cell item = *i_item;
338
339 if (!cellUsedWithConstraints(item))
340 continue;
341
342 metis_xadj.add(metis_adjncy.size());
343
344 getNeighbourCellsUidWithConstraints(item, neighbour_cells, &edge_weights);
345 for (Integer z = 0; z < neighbour_cells.size(); ++z)
346 metis_adjncy.add(static_cast<idxtype>(neighbour_cells[z]));
347 }
348 metis_xadj.add(metis_adjncy.size());
349
350 idxtype wgtflag = 3; // Vertices and edges
351 //2; // Indicates only weights on vertices
352 idxtype numflags = 0;
353 idxtype nparts = static_cast<int>(nb_part);
354
355 // Weights at the graph vertices (the cells)
356 // if (initial_partition)
357 // nb_weight = 1;
358
359 String s = platform::getEnvironmentVariable("ARCANE_LB_PARAM");
360 if (!s.null() && (s.upper() == "PARTICLES"))
361 nb_weight = 1; // Only one weight in this case !
362
363#if CEDRIC_LB_2
364 nb_weight = 1;
365#endif
366 bool cond = (nb_weight == 1);
367 // Real max_int = 1 << (sizeof(idxtype)*8-1);
368 UniqueArray<realtype> metis_ubvec(CheckedConvert::toInteger(nb_weight));
369 SharedArray<float> cells_weights;
370
371 PartitionConverter<float, idxtype> converter(pm, (Real)IDX_MAX, cond);
372
378 cells_weights = cellsWeightsWithConstraints(CheckedConvert::toInteger(nb_weight));
379 // Allowed imbalance for each constraint
380 metis_ubvec.resize(CheckedConvert::toInteger(nb_weight));
381 metis_ubvec.fill(tolerance_target);
382
383 converter.reset(CheckedConvert::toInteger(nb_weight));
384
385 do {
386 cond = converter.isBalancable<realtype>(cells_weights, metis_ubvec, nb_part);
387 if (!cond) {
388 info() << "We have to increase imbalance :";
389 info() << metis_ubvec;
390 for (auto& tol : metis_ubvec) {
391 tol *= 1.5f;
392 }
393 }
394 } while (!cond);
395
396 ArrayConverter<float, idxtype, PartitionConverter<float, idxtype>> metis_vwgtConvert(cells_weights, converter);
397
398 SharedArray<idxtype> metis_vwgt(metis_vwgtConvert.array().constView());
399
400 const bool do_print_weight = false;
401 if (do_print_weight) {
402 StringBuilder fname;
403 Integer iteration = mesh->subDomain()->commonVariables().globalIteration();
404 fname = "weigth-";
405 fname += pm->commRank();
406 fname += "-";
407 fname += iteration;
408 String f(fname);
409 std::ofstream dumpy(f.localstr());
410 for (int i = 0; i < metis_xadj.size() - 1; ++i) {
411 dumpy << " Weight uid=" << i;
412 for (int j = 0; j < nb_weight; ++j) {
413 dumpy << " w=" << *(metis_vwgt.begin() + i * nb_weight + j)
414 << "(" << cells_weights[i * nb_weight + j] << ")";
415 }
416 dumpy << '\n';
417 }
418 }
419
420 converter.reset(1); // Only one weight for communications !
421
422 converter.computeContrib(edge_weights);
423 ArrayConverter<float, idxtype, PartitionConverter<float, idxtype>> metis_ewgt_convert(edge_weights, converter);
424 SharedArray<idxtype> metis_ewgt((UniqueArray<idxtype>)metis_ewgt_convert.array());
425
426 SharedArray<float> cells_size;
427 realtype itr = 50; // In average lb every 20 iterations ?
428 SharedArray<idxtype> metis_vsize;
429 if (!initial_partition) {
430 cells_size = cellsSizeWithConstraints();
431 converter.computeContrib(cells_size, (Real)itr);
432 metis_vsize.resize(metis_xadj.size() - 1, 1);
433 }
434
435 idxtype metis_options[4];
436 metis_options[0] = 0;
437
438 // By default RandomSeed is fixed
439
440#ifdef CEDRIC_LB_2
441 m_random_seed = Convert::toInteger((Real)MPI_Wtime());
442#endif // CEDRIC_LB_2
443
444 metis_options[0] = 1;
445 metis_options[1] = 0;
446 metis_options[2] = m_random_seed;
447 metis_options[3] = 1; // Initial distribution information is implicit
448 /*
449 * Comment to keep same initial random seed each time !
450 m_random_seed++;
451 */
452
453 idxtype metis_edgecut = 0;
454
455 // TODO: compute correct part number !
456 UniqueArray<idxtype> metis_part;
457
458 std::chrono::high_resolution_clock clock;
459 auto start_time = clock.now();
460
461 GraphDistributor gd(pm);
462
463 // There are currently 2 graph grouping mechanisms: one done by "GraphDistributor" and
464 // one done by the ParMetis wrapper. It is possible to combine the two (two_processors_two_nodes).
465 // Eventually, these 2 mechanisms should merge and only "GraphDistributor" should be kept.
466 //
467 // The redistribution by the wrapper is only done in the following 2 cases:
468 // - we want grouping on 2 processors after grouping by nodes (two_processors_two_nodes)
469 // - we want direct grouping on 2 processors, hoping they are on 2 distinct nodes (two_scattered_processors)
470
471 bool redistribute = true; // redistribution by GraphDistributor
472 bool redistribute_by_wrapper = false; // redistribution by the ParMetis wrapper
473
474 if (call_strategy == MetisCallStrategy::all_processors || call_strategy == MetisCallStrategy::two_scattered_processors) {
475 redistribute = false;
476 }
477
478 if (call_strategy == MetisCallStrategy::two_processors_two_nodes || call_strategy == MetisCallStrategy::two_scattered_processors) {
479 redistribute_by_wrapper = true;
480 }
481 // Indicates if we only allow using a single PE.
482 // historically (before April 2020) this was not allowed because it meant
483 // calling 'ParMetis' on a single PE which was not supported.
484 // Now, we call Metis directly in this case, so there are no
485 // problems. However, for historical reasons, we keep the old behavior
486 // except for two cases:
487 // - if message exchange is in shared memory mode. Since partitioning
488 // in this mode was not supported before, there is
489 // no history to keep. Furthermore, this is essential if we only use
490 // a single computing node because then
491 // - during initial partitioning and if there are empty partitions. This case
492 // did not exist before either because we used 'MeshPartitionerTester' to perform a
493 // first pre-partitioning that guarantees no empty partitions. This allows
494 // to avoid this pre-partitioning.
495
496 bool gd_allow_only_one_rank = false;
497 if (is_shared_memory || (nb_empty_part != 0 && initial_partition))
498 gd_allow_only_one_rank = true;
499
500 // If there is only one non-empty part, we use only one rank. This case
501 // normally occurs only in the case of initial partitioning and if
502 // only one rank (generally rank 0) has cells.
503 if ((nb_empty_part + 1) == nb_rank) {
504 info() << "Initialize GraphDistributor with max rank=1";
505 gd.initWithMaxRank(1);
506 }
507 else if (call_strategy == MetisCallStrategy::two_gathered_processors && (nb_rank > 2)) {
508 // Only the first 2 processors will be used.
509 info() << "Initialize GraphDistributor with max rank=2";
510 gd.initWithMaxRank(2);
511 }
512 else {
513 info() << "Initialize GraphDistributor with one rank per node"
514 << " (allow_one?=" << gd_allow_only_one_rank << ")";
515 gd.initWithOneRankPerNode(gd_allow_only_one_rank);
516 }
517
518 IParallelMng* metis_pm = pm;
519
520 info() << "Using GraphDistributor?=" << redistribute;
521 if (redistribute) {
522 metis_xadj = gd.convert<idxtype>(metis_xadj, &metis_part, true);
523 metis_vwgt = gd.convert<idxtype>(metis_vwgt);
524 metis_adjncy = gd.convert<idxtype>(metis_adjncy);
525 metis_ewgt = gd.convert<idxtype>(metis_ewgt);
526 if (!initial_partition)
527 metis_vsize = gd.convert<idxtype>(metis_vsize);
528 metis_pm = gd.subParallelMng();
529
530 metis_vtkdist.resize(gd.size() + 1);
531 if (gd.contribute()) {
532 UniqueArray<Integer> buffer(gd.size());
533 Integer nbVertices = metis_part.size();
534 gd.subParallelMng()->allGather(ConstArrayView<Integer>(1, &nbVertices), buffer);
535 metis_vtkdist[0] = 0;
536 for (int i = 1; i < metis_vtkdist.size(); ++i) {
537 metis_vtkdist[i] = metis_vtkdist[i - 1] + buffer[i - 1];
538 }
539 }
540 metis_options[3] = PARMETIS_PSR_UNCOUPLED; // Initial distribution information is in metis_part
541 }
542 else {
543
544 metis_part = UniqueArray<idxtype>(nb_own_cell, my_rank);
545 }
546 MPI_Comm metis_mpicomm = static_cast<MPI_Comm>(metis_pm->communicator());
547
548 bool do_call_metis = true;
549 if (redistribute)
550 do_call_metis = gd.contribute();
551
552 if (do_call_metis) {
553 if (dump_graph) {
554 Integer iteration = sd->commonVariables().globalIteration();
555 StringBuilder name("graph-");
556 name += iteration;
557 _writeGraph(metis_pm, metis_vtkdist, metis_xadj, metis_adjncy, metis_vwgt, metis_ewgt, name.toString());
558 }
559
560 // ParMetis >= 4 requires to define tpwgt.
561 const Integer tpwgt_size = CheckedConvert::toInteger(nb_part) * CheckedConvert::toInteger(nb_weight);
562 UniqueArray<realtype> tpwgt(tpwgt_size);
563 float fill_value = (float)(1.0 / (double)nparts);
564 tpwgt.fill(fill_value);
565
566 int retval = METIS_OK;
567 Timer::Action ts(sd, "Metis");
568
569 MetisWrapper wrapper(metis_pm);
570 force_partition |= initial_partition;
571 force_full_repartition |= force_partition;
572 if (force_full_repartition) {
573 m_nb_refine = 0;
574 if (force_partition) {
575 info() << "Metis: use a complete partitioning.";
576 retval = wrapper.callPartKway(in_out_digest,
577 redistribute_by_wrapper,
578 metis_vtkdist.data(),
579 metis_xadj.data(),
580 metis_adjncy.data(),
581 metis_vwgt.data(),
582 metis_ewgt.data(),
583 &wgtflag, &numflags, &nb_weight,
584 &nparts, tpwgt.data(),
585 metis_ubvec.data(),
586 metis_options, &metis_edgecut,
587 metis_part.data());
588 }
589 else {
590 info() << "Metis: use a complete REpartitioning.";
591 retval = wrapper.callAdaptiveRepart(in_out_digest,
592 redistribute_by_wrapper,
593 metis_vtkdist.data(),
594 metis_xadj.data(),
595 metis_adjncy.data(),
596 metis_vwgt.data(),
597 metis_vsize.data(), // Vsize !
598 metis_ewgt.data(),
599 &wgtflag, &numflags, &nb_weight,
600 &nparts, tpwgt.data(),
601 metis_ubvec.data(),
602 &itr, metis_options, &metis_edgecut,
603 metis_part.data());
604 }
605 }
606 else {
607 // TODO: put into MetisWrapper and remove use of .data()
608 // (this must be done by the wrapper)
609 ++m_nb_refine;
610 info() << "Metis: use a diffusive REpartitioning";
611 retval = ParMETIS_V3_RefineKway(metis_vtkdist.data(),
612 metis_xadj.data(),
613 metis_adjncy.data(),
614 metis_vwgt.data(),
615 metis_ewgt.data(),
616 &wgtflag, &numflags, &nb_weight,
617 &nparts, tpwgt.data(),
618 metis_ubvec.data(),
619 metis_options, &metis_edgecut,
620 metis_part.data(),
621 &metis_mpicomm);
622 }
623 if (retval != METIS_OK) {
624 ARCANE_FATAL("Error while computing ParMetis partitioning r='{0}'", retval);
625 }
626 }
627 if (redistribute) {
628 metis_part = gd.convertBack<idxtype>(metis_part, nb_own_cell);
629 }
630
631 auto end_time = clock.now();
632 std::chrono::microseconds duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
633 info() << "Time to partition using parmetis = " << duration.count() << " us ";
634
635 // Strategy to adopt for removing empty partitions.
636 // Note that this must be done before applying any constraints
637 // to ensure that they are respected
638 if (options()) {
639 switch (options()->emptyPartitionStrategy()) {
640 case MetisEmptyPartitionStrategy::DoNothing:
641 break;
642 case MetisEmptyPartitionStrategy::TakeFromBiggestPartitionV1:
643 _removeEmptyPartsV1(nb_part, nb_own_cell, metis_part);
644 break;
645 case MetisEmptyPartitionStrategy::TakeFromBiggestPartitionV2:
646 _removeEmptyPartsV2(nb_part, metis_part);
647 break;
648 }
649 }
650 else
651 _removeEmptyPartsV2(nb_part, metis_part);
652
653 VariableItemInt32& cells_new_owner = mesh->toPrimaryMesh()->itemsNewOwner(IK_Cell);
654 {
655 Integer index = 0;
656 ENUMERATE_CELL (i_item, own_cells) {
657 Cell item = *i_item;
658 if (!cellUsedWithConstraints(item))
659 continue;
660
661 Int32 new_owner = CheckedConvert::toInt32(metis_part[index]);
662 ++index;
663 changeCellOwner(item, cells_new_owner, new_owner);
664 }
665 }
666
667 // freeing temporary arrays
668 freeConstraints();
669
670 cells_new_owner.synchronize();
671
673
674 m_nb_refine = m_parallel_mng->reduce(Parallel::ReduceMax, m_nb_refine);
675}
676
677/*---------------------------------------------------------------------------*/
678/*---------------------------------------------------------------------------*/
679
690_removeEmptyPartsV1(const Int32 nb_part, const Int32 nb_own_cell, ArrayView<idxtype> metis_part)
691{
692 IParallelMng* pm = m_parallel_mng;
693 Int32 my_rank = pm->commRank();
694
695 //The following code insures that no empty part will be created.
696 // TODO: make a better solution, which takes elements from the heaviest part.
697 UniqueArray<Int32> elem_by_part(nb_part, 0);
698 UniqueArray<Int32> min_part(nb_part);
699 UniqueArray<Int32> max_part(nb_part);
700 UniqueArray<Int32> sum_part(nb_part);
701 UniqueArray<Int32> min_roc(nb_part);
702 UniqueArray<Int32> max_proc(nb_part);
703 for (int i = 0; i < nb_own_cell; i++) {
704 elem_by_part[CheckedConvert::toInteger(metis_part[i])]++;
705 }
706 pm->computeMinMaxSum(elem_by_part, min_part, max_part, sum_part, min_roc, max_proc);
707
708 int nb_hole = 0;
709 Int32 max_part_id = -1;
710 Int32 max_part_nbr = -1;
711 // Compute number of empty parts
712 for (int i = 0; i < nb_part; i++) {
713 if (sum_part[i] == 0) {
714 nb_hole++;
715 }
716 if (max_part_nbr < sum_part[i]) {
717 max_part_nbr = sum_part[i];
718 max_part_id = i;
719 }
720 }
721 info() << "Parmetis: number empty parts " << nb_hole;
722
723 // The processor having the largest part of the
724 // largest partition fills the holes
725 if (my_rank == max_proc[max_part_id]) {
726 int offset = 0;
727 for (int i = 0; i < nb_part; i++) {
728 // We only fill if there are still cells
729 if (sum_part[i] == 0 && offset < nb_own_cell) {
730 while (offset < nb_own_cell && metis_part[offset] != max_part_id) {
731 offset++;
732 }
733 // If we exited the while because there are not enough cells
734 if (offset == nb_own_cell)
735 break;
736 // The hole is filled by adding a single cell
737 metis_part[offset] = i;
738 offset++;
739 }
740 }
741 }
742}
743
744/*---------------------------------------------------------------------------*/
745/*---------------------------------------------------------------------------*/
746
755_removeEmptyPartsV2Helper(const Int32 nb_part, ArrayView<idxtype> metis_part, Int32 algo_iteration)
756{
757 IParallelMng* pm = m_parallel_mng;
758 Int32 my_rank = pm->commRank();
759 const Int32 nb_own_cell = metis_part.size();
760 // The following code insures that no empty part will be created.
761 UniqueArray<idxtype> elem_by_part(nb_part, 0);
762 UniqueArray<idxtype> min_part(nb_part);
763 UniqueArray<idxtype> max_part(nb_part);
764 UniqueArray<idxtype> sum_part(nb_part);
765 UniqueArray<Int32> min_rank(nb_part);
766 UniqueArray<Int32> max_rank(nb_part);
767 for (int i = 0; i < nb_own_cell; i++) {
768 elem_by_part[metis_part[i]]++;
769 }
770 pm->computeMinMaxSum(elem_by_part, min_part, max_part, sum_part, min_rank, max_rank);
771
772 // Ranks of empty parts
773 UniqueArray<Int32> empty_part_ranks;
774 int nb_hole = 0;
775 Int32 max_part_id = -1;
776 Int64 max_part_nbr = -1;
777 // Compute number of empty parts
778 Int64 total_nb_cell = 0;
779 for (int i = 0; i < nb_part; i++) {
780 Int64 current_sum_part = sum_part[i];
781 total_nb_cell += current_sum_part;
782 if (current_sum_part == 0) {
783 empty_part_ranks.add(i);
784 nb_hole++;
785 }
786 if (max_part_nbr < current_sum_part) {
787 max_part_nbr = current_sum_part;
788 max_part_id = i;
789 }
790 }
791 if (max_part_id < 0)
792 ARCANE_FATAL("Bad value max_part_id ({0})", max_part_id);
793 info() << "Parmetis: check empty parts: (" << algo_iteration << ") nb_empty_parts=" << nb_hole
794 << " nb_max_part=" << max_part_nbr
795 << " max_part_rank=" << max_part_id
796 << " max_proc_max_part_id=" << max_rank[max_part_id]
797 << " empty_part_ranks=" << empty_part_ranks
798 << " total_nb_cell=" << total_nb_cell;
799
800 if (nb_hole == 0)
801 return 0;
802
803 // The processor having the largest part of the
804 // largest partition fills the holes
805 if (my_rank == max_rank[max_part_id]) {
806 // We ensure that we do not remove all our cells
807 Int32 max_remove_cell = nb_own_cell - 1;
808 int offset = 0;
809 for (int i = 0; i < nb_part; i++) {
810 // We only fill if there are still cells
811 if (sum_part[i] == 0 && offset < nb_own_cell) {
812 while (offset < max_remove_cell && metis_part[offset] != max_part_id) {
813 offset++;
814 }
815 // If we exited the while because there are not enough cells
816 if (offset == max_remove_cell)
817 break;
818 // The hole is filled by adding a single cell
819 metis_part[offset] = i;
820 offset++;
821 }
822 }
823 }
824
825 return nb_hole;
826}
827
828/*---------------------------------------------------------------------------*/
829/*---------------------------------------------------------------------------*/
830
838_removeEmptyPartsV2(const Int32 nb_part, ArrayView<idxtype> metis_part)
839{
840 bool do_continue = true;
841 Int32 last_nb_hole = 0;
842 while (do_continue) {
843 Int32 nb_hole = _removeEmptyPartsV2Helper(nb_part, metis_part, 0);
844 info() << "Parmetis: nb_empty_partition=" << nb_hole << " last_nb_partition=" << last_nb_hole;
845 if (nb_hole == 0)
846 break;
847 // Guaranteed to exit the loop if we always have the same number of holes
848 // as before. This prevents infinite loops.
849 // This also means that we cannot fill the holes and therefore there will
850 // probably be empty partitions
851 if (last_nb_hole > 0 && last_nb_hole <= nb_hole) {
852 pwarning() << "Can not remove all empty partitions. This is probably because you try"
853 << " to cut in too many partitions";
854 break;
855 }
856 last_nb_hole = nb_hole;
857 }
858}
859
860/*---------------------------------------------------------------------------*/
861/*---------------------------------------------------------------------------*/
862
868 Span<const idxtype> metis_vtkdist,
869 Span<const idxtype> metis_xadj,
870 Span<const idxtype> metis_adjncy,
871 Span<const idxtype> metis_vwgt,
872 Span<const idxtype> metis_ewgt,
873 const String& name) const
874{
875 int retval = 0;
876
877 idxtype nvtx = 0;
878 idxtype nwgt = 0;
879 bool have_ewgt = false;
880
881 Int32 commrank = pm->commRank();
882 Int32 commsize = pm->commSize();
883 info() << "COMM_SIZE=" << commsize << " RANK=" << commrank;
884 traceMng()->flush();
885 //MPI_Comm_size(metis_comm, &commsize);
886 //MPI_Comm_rank(metis_comm ,&commrank);
887 // NOTE GG: error handling via METIS_ERROR does not work: it produces a deadlock
888 // because there is an MPI_Allreduce in the error-free part.
889 // NOTE GG: Do not use MPI directly.
890
891 info() << "MetisVtkDist=" << metis_vtkdist;
892 info() << "MetisXAdj =" << metis_xadj;
893 info() << "MetisAdjncy =" << metis_adjncy;
894 info() << "MetisVWgt =" << metis_vwgt;
895 info() << "MetisEWgt =" << metis_ewgt;
896
897#define METIS_ERROR ARCANE_FATAL("_writeGraph")
898
899 StringBuilder filename(name);
900 filename += "_";
901 filename += commrank;
902 std::ofstream file(filename.toString().localstr());
903
904 if (metis_vtkdist.size() != commsize + 1)
905 METIS_ERROR;
906
907 nvtx = metis_vtkdist[commrank + 1] - metis_vtkdist[commrank];
908 if (metis_xadj.size() != nvtx + 1) {
909 std::cerr << "_writeGraph : nvtx+1 = " << nvtx << " != " << metis_xadj.size() << std::endl;
910 METIS_ERROR;
911 }
912
913 if (nvtx != 0)
914 nwgt = metis_vwgt.size() / nvtx;
915 if (nwgt != 0 && metis_vwgt.size() % nwgt != 0)
916 METIS_ERROR;
917
918 have_ewgt = (metis_ewgt.size() != 0);
919 if (have_ewgt && metis_ewgt.size() != metis_adjncy.size())
920 METIS_ERROR;
921
922 if (!file.is_open())
923 METIS_ERROR;
924
925 Int64 localEdges = metis_xadj[nvtx];
926 Int64 globalEdges = pm->reduce(Parallel::ReduceSum, localEdges);
927
928 file << "2" << std::endl;
929 file << commsize << "\t" << commrank << std::endl;
930 file << metis_vtkdist[commsize] << "\t" << globalEdges << std::endl;
931 file << nvtx << "\t" << localEdges << std::endl;
932 file << "0\t" << "0" << have_ewgt << nwgt << std::endl;
933
934 /*
935 Each of these lines begins with the vertex label,
936 if necessary, the vertex load, if necessary, and the vertex degree, followed by the
937 description of the arcs. An arc is defined by the load of the edge, if necessary, and
938 by the label of its other end vertex.
939 */
940
941 //** Lbl Wgt Degree edWgt edLbl ...
942
943 for (idxtype vertnum = 0; vertnum < nvtx; vertnum++) {
944 // file << vertnum + metis_vtkdist[commrank] << " ";
945 for (int dim = 0; dim < nwgt; ++dim)
946 file << metis_vwgt[vertnum * nwgt + dim] << '\t';
947 file << metis_xadj[vertnum + 1] - metis_xadj[vertnum];
948 for (idxtype edgenum = metis_xadj[vertnum];
949 edgenum < metis_xadj[vertnum + 1]; ++edgenum) {
950 if (have_ewgt)
951 file << '\t' << metis_ewgt[edgenum];
952 file << '\t' << metis_adjncy[edgenum];
953 }
954 file << std::endl;
955 }
956 file.close();
957
958 pm->barrier();
959 return retval;
960}
961
962/*---------------------------------------------------------------------------*/
963/*---------------------------------------------------------------------------*/
964
969
970ARCANE_REGISTER_SERVICE_METISMESHPARTITIONER(Metis, MetisMeshPartitioner);
971
972#if ARCANE_DEFAULT_PARTITIONER == METIS_DEFAULT_PARTITIONER
974 ServiceProperty("DefaultPartitioner", ST_SubDomain),
977ARCANE_REGISTER_SERVICE_METISMESHPARTITIONER(DefaultPartitioner, MetisMeshPartitioner);
978#endif
979
980/*---------------------------------------------------------------------------*/
981/*---------------------------------------------------------------------------*/
982
983} // End namespace Arcane
984
985/*---------------------------------------------------------------------------*/
986/*---------------------------------------------------------------------------*/
#define ARCANE_THROW(exception_class,...)
Macro for throwing an exception with formatting.
#define ARCANE_FATAL(...)
Macro throwing a FatalErrorException.
Types and macros for iterating over mesh entities.
#define ENUMERATE_(type, name, group)
Generic enumerator for an entity group.
#define ENUMERATE_CELL(name, group)
Generic enumerator for a cell group.
#define ARCANE_SERVICE_INTERFACE(ainterface)
Macro to declare an interface when registering a service.
Integer size() const
Number of elements in the vector.
ArcaneMetisMeshPartitionerObject(const Arcane::ServiceBuildInfo &sbi)
Constructeur.
CaseOptionsMetisMeshPartitioner * options() const
Options du jeu de données du service.
Conversion of an array from one type to another type.
Modifiable view of an array of type T.
constexpr Integer size() const noexcept
Returns the size of the array.
void fill(ConstReferenceType value)
Fills the array with the value value.
void resize(Int64 s)
Changes the number of elements in the array to s.
iterator begin()
Iterator over the first element of the array.
void add(ConstReferenceType val)
Adds element val to the end of the array.
const T * data() const
Access to the root of the array without any protection.
ConstArrayView< T > constView() const
Constant view of this array.
void reserve(Int64 new_capacity)
Reserves memory for new_capacity elements.
Cell of a mesh.
Definition Item.h:1300
Int32 nbEdge() const
Number of edges of the cell.
Definition Item.h:1412
Int32 nbFace() const
Number of faces of the cell.
Definition Item.h:1397
Int32 globalIteration() const
Current iteration number.
Constant view of an array of type T.
Template class for converting a type.
Class allowing temporary activation/deactivation of exceptions of the processor.
Redistribute graph data to another "communicator".
void initWithOneRankPerNode(bool allow_only_one_rank)
Automatic distribution : one partitioning process per node.
Interface of a mesh partitioner.
Interface of a mesh partitioner.
virtual IParallelMng * parallelMng()=0
Parallelism manager.
Interface of the parallelism manager for a subdomain.
virtual bool isThreadImplementation() const =0
Indicates if the implementation uses threads.
virtual void computeMinMaxSum(char val, char &min_val, char &max_val, char &sum_val, Int32 &min_rank, Int32 &max_rank)=0
Calculates the sum, min, and max of a value in one operation.
virtual Int32 commRank() const =0
Rank of this instance in the communicator.
virtual Int32 commSize() const =0
Number of instances in the communicator.
virtual void allGather(ConstArrayView< char > send_buf, ArrayView< char > recv_buf)=0
Performs an all-gather operation across all processors. This is a collective operation....
virtual Parallel::Communicator communicator() const =0
MPI communicator associated with this manager.
virtual char reduce(eReduceType rt, char v)=0
Performs a reduction of type rt on the real v and returns the value.
virtual void barrier()=0
Performs a barrier.
Interface of the subdomain manager.
Definition ISubDomain.h:75
virtual const CommonVariables & commonVariables() const =0
Information on standard variables.
virtual void flush()=0
Flushes all streams.
@ PNoDump
Indicates that the variable should not be saved.
Definition IVariable.h:62
constexpr bool hasFlags(Int32 flags) const
Returns if the flags are set for the entity.
Definition Item.h:352
Real imbalance() const override
Computation time imbalance.
Real maxImbalance() const override
Maximum allowed imbalance.
virtual void changeOwnersFromCells()
Positions the new owners of nodes, edges and faces based on the cells.
Mesh partitioner using the PARMetis library.
void partitionMesh(bool initial_partition) override
void _removeEmptyPartsV2(Int32 nb_part, ArrayView< idxtype > metis_part)
Fills empty partitions (version 2).
Int32 _removeEmptyPartsV2Helper(Int32 nb_part, ArrayView< idxtype > metis_part, Int32 algo_iteration)
Applies one iteration of the empty partition removal algorithm.
void build() override
Build-level construction of the service.
void _partitionMesh(bool initial_partition, Int32 nb_part)
int _writeGraph(IParallelMng *pm, Span< const idxtype > metis_vtkdist, Span< const idxtype > metis_xadj, Span< const idxtype > metis_adjncy, Span< const idxtype > metis_vwgt, Span< const idxtype > metis_ewgt, const String &Name) const
void _removeEmptyPartsV1(Int32 nb_part, Int32 nb_own_cell, ArrayView< idxtype > metis_part)
Fills empty partitions (version 1).
Wrapper around Parmetis calls.
int callPartKway(const bool print_digest, const bool gather, idx_t *vtxdist, idx_t *xadj, idx_t *adjncy, idx_t *vwgt, idx_t *adjwgt, idx_t *wgtflag, idx_t *numflag, idx_t *ncon, idx_t *nparts, real_t *tpwgts, real_t *ubvec, idx_t *options, idx_t *edgecut, idx_t *part)
Simple wrapper around the ParMetis routine "ParMETIS_V3_PartKway".
int callAdaptiveRepart(const bool print_digest, const bool gather, idx_t *vtxdist, idx_t *xadj, idx_t *adjncy, idx_t *vwgt, idx_t *vsize, idx_t *adjwgt, idx_t *wgtflag, idx_t *numflag, idx_t *ncon, idx_t *nparts, real_t *tpwgts, real_t *ubvec, real_t *ipc2redist, idx_t *options, idx_t *edgecut, idx_t *part)
Simple wrapper around the ParMetis routine "ParMETIS_V3_AdaptiveRepart".
Conversion of an array of floats to an array of integers/longs. \abstract This class manages the scal...
Structure containing the information to create a service.
Service creation properties.
1D vector of data with reference semantics.
constexpr __host__ __device__ SizeType size() const noexcept
Returns the size of the array.
Definition Span.h:327
View of an array of elements of type T.
Definition Span.h:635
Unicode character string constructor.
String toString() const
Returns the constructed character string.
bool null() const
Returns true if the string is null.
Definition String.cc:306
String upper() const
Transforms all characters in the string to uppercase.
Definition String.cc:468
const char * localstr() const
Returns the conversion of the instance into UTF-8 encoding.
Definition String.cc:229
Positions the name of the currently executing action.
Definition Timer.h:119
TraceMessage info() const
Flow for an information message.
ITraceMng * traceMng() const
Trace manager.
TraceMessage pwarning() const
1D data vector with value semantics (STL style).
Parameters necessary for building a variable.
ItemGroupT< Cell > CellGroup
Group of cells.
Definition ItemTypes.h:184
#define ARCANE_REGISTER_SERVICE(aclass, a_service_property,...)
Macro for registering a service.
MeshVariableScalarRefT< Cell, Integer > VariableCellInteger
Quantity at the cell center of integer type.
ItemVariableScalarRefT< Int32 > VariableItemInt32
32-bit integer type quantity
Integer toInteger(Real r)
Converts a Real to Integer.
@ ReduceMax
Maximum of values.
String getEnvironmentVariable(const String &name)
Environment variable named name.
-- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature --
UniqueArray< Int64 > Int64UniqueArray
Dynamic 1D array of 64-bit integers.
Definition UtilsTypes.h:339
std::int64_t Int64
Signed integer type of 64 bits.
Int32 Integer
Type representing an integer.
@ ST_SubDomain
The service is used at the subdomain level.
@ IK_Cell
Cell mesh entity.
double Real
Type representing a real number.
std::int32_t Int32
Signed integer type of 32 bits.