Arcane  v3.14.10.0
Documentation développeur
Chargement...
Recherche...
Aucune correspondance
MetisMeshPartitioner.cc
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2024 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-2024 */
9/* */
10/* Partitioneur de maillage utilisant la bibliothèque PARMetis. */
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#include "arcane/utils/ValueConvert.h"
22
23#include "arcane/ISubDomain.h"
24#include "arcane/IParallelMng.h"
25#include "arcane/ItemEnumerator.h"
26#include "arcane/IMesh.h"
27#include "arcane/IMeshSubMeshTransition.h"
28#include "arcane/ItemGroup.h"
29#include "arcane/Service.h"
30#include "arcane/Timer.h"
31#include "arcane/FactoryService.h"
32#include "arcane/ItemPrinter.h"
33#include "arcane/IItemFamily.h"
34#include "arcane/MeshVariable.h"
35#include "arcane/VariableBuildInfo.h"
36
37#include "arcane/std/MeshPartitionerBase.h"
38#include "arcane/std/MetisMeshPartitioner_axl.h"
39
40// Au cas où on utilise mpich2 ou openmpi pour éviter d'inclure le C++
41#define MPICH_SKIP_MPICXX
42#define OMPI_SKIP_MPICXX
43#include <parmetis.h>
44// #define CEDRIC_LB_2
45
46#if (PARMETIS_MAJOR_VERSION < 4)
47#error "Your version of Parmetis is too old .Version 4.0+ is required"
48#endif
49
50typedef real_t realtype;
51typedef idx_t idxtype;
52
53#include "arcane/std/PartitionConverter.h"
54#include "arcane/std/GraphDistributor.h"
55#include "arcane/std/internal/MetisWrapper.h"
56
57#include <chrono>
58
59/*---------------------------------------------------------------------------*/
60/*---------------------------------------------------------------------------*/
61
62namespace Arcane
63{
64
65using MetisCallStrategy = TypesMetisMeshPartitioner::MetisCallStrategy;
66using MetisEmptyPartitionStrategy = TypesMetisMeshPartitioner::MetisEmptyPartitionStrategy;
67
68/*---------------------------------------------------------------------------*/
69/*---------------------------------------------------------------------------*/
75{
76 public:
77
79
80 public:
81
82 void build() override {}
83
84 public:
85
86 void partitionMesh(bool initial_partition) override;
87 void partitionMesh(bool initial_partition,Int32 nb_part) override;
88
89
90 private:
91
92 IParallelMng* m_parallel_mng = nullptr;
93 Integer m_nb_refine;
94 Integer m_random_seed;
95 bool m_disable_floatingexception;
106 const String& Name) const;
107};
108
109/*---------------------------------------------------------------------------*/
110/*---------------------------------------------------------------------------*/
111
112MetisMeshPartitioner::
113MetisMeshPartitioner(const ServiceBuildInfo& sbi)
114: ArcaneMetisMeshPartitionerObject(sbi)
115, m_nb_refine(-1)
116, m_random_seed(15)
117, m_disable_floatingexception(false)
118{
119 m_parallel_mng = mesh()->parallelMng();
120 String s = platform::getEnvironmentVariable("ARCANE_DISABLE_METIS_FPE");
121 if (s=="1" || s=="TRUE")
122 m_disable_floatingexception = true;
123}
124
125/*---------------------------------------------------------------------------*/
126/*---------------------------------------------------------------------------*/
127
134
135/*---------------------------------------------------------------------------*/
136/*---------------------------------------------------------------------------*/
137
140{
141 // Signal que le partitionnement peut planter car metis n'est pas toujours
142 // tres fiable.
143 initial_partition = (subDomain()->commonVariables().globalIteration() <= 2);
144 if (m_disable_floatingexception){
147 }
148 else
149 _partitionMesh(initial_partition,nb_part);
150}
151
152/*---------------------------------------------------------------------------*/
153/*---------------------------------------------------------------------------*/
154
157{
158 ISubDomain* sd = subDomain();
159 IParallelMng* pm = m_parallel_mng;
160 Int32 my_rank = pm->commRank();
161 Int32 nb_rank = pm->commSize();
162 IMesh* mesh = this->mesh();
163 bool force_partition = false;
164
165 if (nb_part<nb_rank)
166 ARCANE_THROW(ArgumentException,"partition with nb_part ({0}) < nb_rank ({1})",
167 nb_part,nb_rank);
168
169 if (nb_rank==1){
170 info() << "INFO: Unable to test load balancing on a single sub-domain";
171 return;
172 }
173
174 // TODO : comprendre les modifs de l'IFPEN
175 // utilise toujours repartitionnement complet.
176 // info() << "Metis: params " << m_nb_refine << " " << imbalance() << " " << maxImbalance();
177 // info() << "Metis: params " << (m_nb_refine>=10) << " " << (imbalance()>4.0*maxImbalance()) << " " << (imbalance()>1.0);
178 bool force_full_repartition = false;
179 //force_full_repartition = true;
180 force_full_repartition |= (m_nb_refine < 0); // toujours la première fois, pour compatibilité avec l'ancienne version
181
182 if (nb_part != nb_rank) { // Pas de "repartitionnement" si le nombre de parties ne correspond pas au nombre de processeur.
184 force_partition = true;
185 }
186
187 info() << "WARNING: compensating the potential lack of 'Metis' options in case of manual instanciation";
188 Integer max_diffusion_count = 10; // reprise des options par défaut du axl
190 float tolerance_target = 1.05f;
191 bool dump_graph = false;
192 MetisCallStrategy call_strategy = MetisCallStrategy::one_processor_per_node;
193 bool in_out_digest = false;
194
195 // Variables d'environnement permettant de regler les options lorsqu'il n'y a pas de jeu de donnees
196
197 String call_strategy_env = platform::getEnvironmentVariable("ARCANE_METIS_CALL_STRATEGY");
198 if (call_strategy_env == "all-processors"){
199 call_strategy = MetisCallStrategy::all_processors;
200 } else if (call_strategy_env == "one-processor-per-node") {
201 call_strategy = MetisCallStrategy::one_processor_per_node;
202 } else if (call_strategy_env == "two-processors-two-nodes") {
203 call_strategy = MetisCallStrategy::two_processors_two_nodes;
204 } else if (call_strategy_env == "two-gathered-processors") {
205 call_strategy = MetisCallStrategy::two_gathered_processors;
206 } else if (call_strategy_env == "two-scattered-processors") {
207 call_strategy = MetisCallStrategy::two_scattered_processors;
208 } else if (!call_strategy_env.null()) {
209 ARCANE_FATAL("Invalid value '{0}' for ARCANE_METIS_CALL_STRATEGY environment variable",call_strategy_env);
210 }
211
212 if (auto v = Convert::Type<Int32>::tryParseFromEnvironment("ARCANE_METIS_INPUT_OUTPUT_DIGEST", true))
213 in_out_digest = (v.value()!=0);
214 if (auto v = Convert::Type<Int32>::tryParseFromEnvironment("ARCANE_METIS_DUMP_GRAPH", true))
215 dump_graph = (v.value()!=0);
216
217 if (options()){
218 max_diffusion_count = options()->maxDiffusiveCount();
219 imbalance_relative_tolerance = options()->imbalanceRelativeTolerance();
220 tolerance_target = (float)(options()->toleranceTarget());
221 dump_graph = options()->dumpGraph();
222 call_strategy = options()->metisCallStrategy();
223 in_out_digest = options()->inputOutputDigest();
224 }
225
226 if (max_diffusion_count > 0)
230
231 // initialisations pour la gestion des contraintes (sauf initUidRef)
232 initConstraints(false);
233
235 // En mode mémoire partagé, pour l'instant on force le fait d'utiliser un seul
236 // processeur par noeud car on ne sait pas si on dispose de plusieurs rang par noeud.
237 // Notamment, en mode mémoire partagé sans MPI on n'a obligatoirement qu'un seul noeud
239 call_strategy = MetisCallStrategy::one_processor_per_node;
240
241 idxtype nb_weight = nbCellWeight();
242
243 info() << "Load balancing with Metis nb_weight=" << nb_weight << " initial=" << initial_partition
244 << " call_strategy=" << (int)call_strategy
245 << " is_shared_memory?=" << is_shared_memory
246 << " disabling_fpe?=" << m_disable_floatingexception
247 << " sizeof(idxtype)==" << sizeof(idxtype);
248
249 if (nb_weight==0)
250 initial_partition = true;
251
253 Integer total_nb_cell = 0;
254
255 // Contient les numéros uniques des entités dans la renumérotation
256 // propre à metis
258
260 CellGroup own_cells = mesh->ownCells();
261 Integer nb_own_cell = nbOwnCellsWithConstraints(); // on tient compte des contraintes
263 Int32 nb_empty_part = 0;
264 {
265 //Integer total_first_uid = 0;
266 metis_vtkdist[0] = 0;
267 for( Integer i=0; i<nb_rank; ++i ){
268 //total_first_uid += global_nb_own_cell[i];
269 Int32 n = global_nb_own_cell[i];
270 if (n==0)
272 total_nb_cell += n;
273 metis_vtkdist[i+1] = static_cast<idxtype>(total_nb_cell);
274 // info() << "METIS VTKDIST " << (i+1) << ' ' << metis_vtkdist[i+1];
275 }
276 }
277 // N'appelle pas Parmetis si on n'a pas de mailles car sinon cela provoque une erreur.
278 info() << "Total nb_cell=" << total_nb_cell << " nb_empty_partition=" << nb_empty_part;
279 if (total_nb_cell==0){
280 info() << "INFO: mesh '" << mesh->name() << " has no cell. No partitioning is needed";
281 freeConstraints();
282 return;
283 }
284
285 // HP: Skip this shortcut because it produces undefined itemsNewOwner variable.
286/*
287 if (total_nb_cell < nb_rank) { // Dans ce cas, pas la peine d'appeler ParMetis.
288 warning() << "There are no subdomain except cells, no reffinement";
289 freeConstraints();
290 return;
291 }
292*/
293
294 // Nombre max de mailles voisine connectés aux mailles
295 // en supposant les mailles connectées uniquement par les faces
296 // Cette valeur sert à préallouer la mémoire pour la liste des mailles voisines
297 Integer nb_max_face_neighbour_cell = 0;
298 {
299 // Renumérote les mailles pour Metis pour que chaque sous-domaine
300 // ait des mailles de numéro consécutifs
301 Integer mid = static_cast<Integer>(metis_vtkdist[my_rank]);
303 const Cell& item = *i_item;
304 if (cellUsedWithConstraints(item)){
305 cell_metis_uid[item] = mid;
306 ++mid;
307 }
309 }
310 cell_metis_uid.synchronize();
311 }
312
313 _initUidRef(cell_metis_uid);
314
315 // libération mémoire
316 cell_metis_uid.setUsed(false);
317
319 metis_xadj.reserve(nb_own_cell+1);
320
323
324
325 // Construction de la connectivité entre les cellules et leurs voisines en tenant compte des contraintes
326 // (La connectivité se fait suivant les faces)
329 edge_weights.resize(0);
331 Cell item = *i_item;
332
333 if (!cellUsedWithConstraints(item))
334 continue;
335
336 metis_xadj.add(metis_adjncy.size());
337
338 getNeighbourCellsUidWithConstraints(item, neighbour_cells, &edge_weights);
339 for( Integer z=0; z<neighbour_cells.size(); ++z )
340 metis_adjncy.add(static_cast<idxtype>(neighbour_cells[z]));
341 }
342 metis_xadj.add(metis_adjncy.size());
343
344 idxtype wgtflag = 3; // Sommets et aretes
345//2; // Indique uniquement des poids sur les sommets
346 idxtype numflags = 0;
347 idxtype nparts = static_cast<int>(nb_part);
348
349 // Poids aux sommets du graphe (les mailles)
350// if (initial_partition)
351// nb_weight = 1;
352
353 String s = platform::getEnvironmentVariable("ARCANE_LB_PARAM");
354 if (!s.null() && (s.upper() == "PARTICLES"))
355 nb_weight = 1; // Only one weight in this case !
356
357#if CEDRIC_LB_2
358 nb_weight = 1;
359#endif
360 bool cond = (nb_weight == 1);
361 // Real max_int = 1 << (sizeof(idxtype)*8-1);
364
366
367
373 cells_weights = cellsWeightsWithConstraints(CheckedConvert::toInteger(nb_weight));
374 // Déséquilibre autorisé pour chaque contrainte
377
379
380 do {
381 cond = converter.isBalancable<realtype>(cells_weights, metis_ubvec, nb_part);
382 if (!cond) {
383 info() << "We have to increase imbalance :";
384 info() << metis_ubvec;
385 for (auto& tol: metis_ubvec ) {
386 tol *= 1.5f;
387 }
388 }
389 } while (!cond);
390
392
394
395 const bool do_print_weight = false;
396 if (do_print_weight){
397 std::ofstream dumpy;
399 Integer iteration = mesh->subDomain()->commonVariables().globalIteration();
400 fname = "weigth-";
401 fname += pm->commRank();
402 fname += "-";
403 fname += iteration;
404 String f(fname);
405 dumpy.open(f.localstr());
406 for (int i = 0; i < metis_xadj.size()-1 ; ++i) {
407 dumpy << " Weight uid=" << i;
408 for( int j=0 ; j < nb_weight ; ++j ){
409 dumpy << " w=" << *(metis_vwgt.begin()+i*nb_weight+j)
410 << "(" << cells_weights[i*nb_weight+j] <<")";
411 }
412 dumpy << '\n';
413 }
414 dumpy.close();
415 }
416
417 converter.reset(1); // Only one weight for communications !
418
419 converter.computeContrib(edge_weights);
422
424 realtype itr=50; // In average lb every 20 iterations ?
426 if (!initial_partition) {
427 cells_size = cellsSizeWithConstraints();
428 converter.computeContrib(cells_size, (Real)itr);
429 metis_vsize.resize(metis_xadj.size()-1,1);
430 }
431
432
433 idxtype metis_options[4];
434 metis_options[0] = 0;
435
436 // By default RandomSeed is fixed
437
438#ifdef CEDRIC_LB_2
439 m_random_seed = Convert::toInteger((Real)MPI_Wtime());
440#endif // CEDRIC_LB_2
441
442 metis_options[0] = 1;
443 metis_options[1] = 0;
444 metis_options[2] = m_random_seed;
445 metis_options[3] = 1; // Initial distribution information is implicit
446 /*
447 * Comment to keep same initial random seed each time !
448 m_random_seed++;
449 */
450
451 idxtype metis_edgecut = 0;
452
453 // TODO: compute correct part number !
455
456 std::chrono::high_resolution_clock clock;
457 auto start_time = clock.now();
458
460
461 // Il y a actuellement 2 mecanismes de regroupement du graph : celui fait par "GraphDistributor" et
462 // celui fait par le wrapper ParMetis. Il est possible de combiner les 2 (two_processors_two_nodes).
463 // A terme, ces 2 mecanismes devraient fusionner et il ne faudrait conserver que le "GraphDistributor".
464 //
465 // La redistribution par le wrapper n'est faite que dans les 2 cas suivants :
466 // - on veut un regroupement sur 2 processeurs apres un regroupement par noeuds (two_processors_two_nodes)
467 // - on veut un regroupement direct sur 2 processeurs, en esperant qu'ils soient sur 2 noeuds distincts (two_scattered_processors)
468
469 bool redistribute = true; // redistribution par GraphDistributor
470 bool redistribute_by_wrapper = false; // redistribution par le wrapper ParMetis
471
472 if (call_strategy == MetisCallStrategy::all_processors || call_strategy == MetisCallStrategy::two_scattered_processors) {
473 redistribute = false;
474 }
475
476 if (call_strategy == MetisCallStrategy::two_processors_two_nodes || call_strategy == MetisCallStrategy::two_scattered_processors) {
478 }
479 // Indique si on n'autorise de n'utiliser qu'un seul PE.
480 // historiquement (avant avril 2020) cela n'était pas autorisé car cela revenait
481 // à appeler 'ParMetis' sur un seul PE ce qui n'était pas supporté.
482 // Maintenant, on appelle directement Metis dans ce cas donc cela ne pose pas de
483 // problèmes. Cependant pour des raisons historiques on garde l'ancien comportement
484 // sauf pour deux cas:
485 // - si l'échange de messages est en mode mémoire partagé. Comme le partitionnement
486 // dans ce mode n'était pas supporté avant, il n'y a
487 // pas d'historique à conserver. De plus cela est indispensable si on n'utilise
488 // qu'un seul noeud de calcul car alors
489 // - lors du partionnement initial et s'il y a des partitions vides. Ce cas n'existait
490 // pas avant non plus car on utilisait 'MeshPartitionerTester' pour faire un
491 // premier pré-partitionnement qui garantit aucune partition vide. Cela permet
492 // d'éviter ce pré-partitionnement.
493
494 bool gd_allow_only_one_rank = false;
497
498 // S'il n'y a qu'une seule partie non-vide on n'utilise qu'un seul rang. Ce cas
499 // intervient normalement uniquement en cas de partitionnement initial et si
500 // un seul rang (en général le rang 0) a des mailles.
501 if ((nb_empty_part+1)==nb_rank){
502 info() << "Initialize GraphDistributor with max rank=1";
503 gd.initWithMaxRank(1);
504 }
505 else if (call_strategy == MetisCallStrategy::two_gathered_processors && (nb_rank > 2)) {
506 // Seuls les 2 premiers processeurs seront utilisés.
507 info() << "Initialize GraphDistributor with max rank=2";
508 gd.initWithMaxRank(2);
509 }
510 else {
511 info() << "Initialize GraphDistributor with one rank per node"
512 << " (allow_one?=" << gd_allow_only_one_rank << ")";
513 gd.initWithOneRankPerNode(gd_allow_only_one_rank);
514 }
515
517
518 info() << "Using GraphDistributor?=" << redistribute;
519 if (redistribute){
520 metis_xadj = gd.convert<idxtype>(metis_xadj, &metis_part, true);
521 metis_vwgt = gd.convert<idxtype>(metis_vwgt);
522 metis_adjncy = gd.convert<idxtype>(metis_adjncy);
523 metis_ewgt = gd.convert<idxtype>(metis_ewgt);
525 metis_vsize = gd.convert<idxtype>(metis_vsize);
526 metis_pm = gd.subParallelMng();
527
528 metis_vtkdist.resize(gd.size()+1);
529 if (gd.contribute()) {
530 UniqueArray<Integer> buffer(gd.size());
531 Integer nbVertices = metis_part.size();
532 gd.subParallelMng()->allGather(ConstArrayView<Integer>(1,&nbVertices),buffer);
533 metis_vtkdist[0] = 0;
534 for (int i = 1 ; i < metis_vtkdist.size() ; ++i) {
535 metis_vtkdist[i] = metis_vtkdist[i-1] + buffer[i-1];
536 }
537 }
538 metis_options[3] = PARMETIS_PSR_UNCOUPLED ; // Initial distribution information is in metis_part
539 }
540 else{
541
543 }
544 MPI_Comm metis_mpicomm = static_cast<MPI_Comm>(metis_pm->communicator());
545
546 bool do_call_metis = true;
547 if (redistribute)
548 do_call_metis = gd.contribute();
549
550 if (do_call_metis) {
551 if (dump_graph) {
552 Integer iteration = sd->commonVariables().globalIteration();
553 StringBuilder name("graph-");
554 name += iteration;
556 }
557
558 // ParMetis >= 4 requires to define tpwgt.
561 float fill_value = (float)(1.0/(double)nparts);
562 tpwgt.fill(fill_value);
563
564 int retval = METIS_OK;
565 Timer::Action ts(sd,"Metis");
566
571 m_nb_refine = 0;
572 if (force_partition) {
573 info() << "Metis: use a complete partitioning.";
574 retval = wrapper.callPartKway(in_out_digest,
576 metis_vtkdist.data(),
577 metis_xadj.data(),
578 metis_adjncy.data(),
579 metis_vwgt.data(),
580 metis_ewgt.data(),
582 &nparts, tpwgt.data(),
583 metis_ubvec.data(),
585 metis_part.data());
586 }
587 else {
588 info() << "Metis: use a complete REpartitioning.";
589 retval = wrapper.callAdaptiveRepart(in_out_digest,
591 metis_vtkdist.data(),
592 metis_xadj.data(),
593 metis_adjncy.data(),
594 metis_vwgt.data(),
595 metis_vsize.data(), // Vsize !
596 metis_ewgt.data(),
598 &nparts,tpwgt.data(),
599 metis_ubvec.data(),
601 metis_part.data());
602 }
603 }
604 else{
605 // TODO: mettre dans MetisWrapper et supprimer utilisation de .data()
606 // (cela doit être faire par le wrapper)
607 ++m_nb_refine;
608 info() << "Metis: use a diffusive REpartitioning";
610 metis_xadj.data(),
611 metis_adjncy.data(),
612 metis_vwgt.data(),
613 metis_ewgt.data(),
615 &nparts,tpwgt.data(),
616 metis_ubvec.data(),
618 metis_part.data(),
620 }
621 if (retval != METIS_OK) {
622 ARCANE_FATAL("Error while computing ParMetis partitioning r='{0}'",retval);
623 }
624 }
625 if (redistribute){
626 metis_part = gd.convertBack<idxtype>(metis_part, nb_own_cell);
627 }
628
629 auto end_time = clock.now();
630 std::chrono::microseconds duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
631 info() << "Time to partition using parmetis = " << duration.count() << " us ";
632
633 // Stratégie à adopter pour supprimer les partitions vides.
634 // A noter qu'il faut faire cela avant d'appliquer les éventuelles contraintes
635 // pour garantir que ces dernières seront bien respectées
636 if (options()){
637 switch(options()->emptyPartitionStrategy()){
638 case MetisEmptyPartitionStrategy::DoNothing:
639 break;
640 case MetisEmptyPartitionStrategy::TakeFromBiggestPartitionV1:
642 break;
643 case MetisEmptyPartitionStrategy::TakeFromBiggestPartitionV2:
645 break;
646 }
647 }
648 else
650
651 VariableItemInt32& cells_new_owner = mesh->toPrimaryMesh()->itemsNewOwner(IK_Cell);
652 {
653 Integer index = 0;
655 Cell item = *i_item;
656 if (!cellUsedWithConstraints(item))
657 continue;
658
660 ++index;
661 changeCellOwner(item, cells_new_owner, new_owner);
662 }
663 }
664
665 // libération des tableaux temporaires
666 freeConstraints();
667
668 cells_new_owner.synchronize();
669
671
672 m_nb_refine = m_parallel_mng->reduce(Parallel::ReduceMax, m_nb_refine);
673}
674
675/*---------------------------------------------------------------------------*/
676/*---------------------------------------------------------------------------*/
689{
690 IParallelMng* pm = m_parallel_mng;
691 Int32 my_rank = pm->commRank();
692
693 //The following code insures that no empty part will be created.
694 // TODO: faire une meilleure solution, qui prend des elements sur la partie la plus lourde.
701 for (int i =0 ; i < nb_own_cell ; i++) {
703 }
705
706 int nb_hole=0;
707 Int32 max_part_id = -1;
708 Int32 max_part_nbr = -1;
709 // Compute number of empty parts
710 for(int i = 0; i < nb_part ; i++) {
711 if (sum_part[i] == 0) {
712 nb_hole ++;
713 }
714 if(max_part_nbr < sum_part[i]) {
716 max_part_id = i;
717 }
718 }
719 info() << "Parmetis: number empty parts " << nb_hole;
720
721 // Le processeur ayant la plus grosse partie de la plus
722 // grosse partition comble les trous
724 int offset = 0;
725 for(int i = 0; i < nb_part ; i++) {
726 // On ne comble que s'il reste des mailles
727 if (sum_part[i] == 0 && offset < nb_own_cell) {
728 while(offset < nb_own_cell && metis_part[offset] != max_part_id) {
729 offset++;
730 }
731 // Si on est sorti du while car pas assez de mailles
732 if(offset == nb_own_cell)
733 break;
734 // Le trou est comblé par ajout d'une seule maille
735 metis_part[offset] = i;
736 offset++;
737 }
738 }
739 }
740}
741
742/*---------------------------------------------------------------------------*/
743/*---------------------------------------------------------------------------*/
753{
754 IParallelMng* pm = m_parallel_mng;
755 Int32 my_rank = pm->commRank();
756 const Int32 nb_own_cell = metis_part.size();
757 // The following code insures that no empty part will be created.
764 for (int i =0 ; i < nb_own_cell ; i++) {
766 }
768
769 // Rang des parties vides
771 int nb_hole = 0;
772 Int32 max_part_id = -1;
773 Int64 max_part_nbr = -1;
774 // Compute number of empty parts
775 Int64 total_nb_cell = 0;
776 for(int i = 0; i < nb_part ; i++) {
777 Int64 current_sum_part = sum_part[i];
779 if (current_sum_part == 0) {
780 empty_part_ranks.add(i);
781 nb_hole ++;
782 }
785 max_part_id = i;
786 }
787 }
788 if (max_part_id<0)
789 ARCANE_FATAL("Bad value max_part_id ({0})",max_part_id);
790 info() << "Parmetis: check empty parts: (" << algo_iteration << ") nb_empty_parts=" << nb_hole
791 << " nb_max_part=" << max_part_nbr
792 << " max_part_rank=" << max_part_id
793 << " max_proc_max_part_id=" << max_rank[max_part_id]
794 << " empty_part_ranks=" << empty_part_ranks
795 << " total_nb_cell=" << total_nb_cell;
796
797 if (nb_hole==0)
798 return 0;
799
800 // Le processeur ayant la plus grosse partie de la plus
801 // grosse partition comble les trous
802 if (my_rank == max_rank[max_part_id]) {
803 // On garantit qu'on n'enlèvera pas toutes nos mailles
804 Int32 max_remove_cell = nb_own_cell - 1;
805 int offset = 0;
806 for(int i = 0; i < nb_part ; i++) {
807 // On ne comble que s'il reste des mailles
808 if (sum_part[i] == 0 && offset < nb_own_cell) {
809 while (offset < max_remove_cell && metis_part[offset] != max_part_id) {
810 offset++;
811 }
812 // Si on est sorti du while car pas assez de mailles
813 if (offset == max_remove_cell)
814 break;
815 // Le trou est comblé par ajout d'une seule maille
816 metis_part[offset] = i;
817 offset++;
818 }
819 }
820 }
821
822 return nb_hole;
823}
824
825/*---------------------------------------------------------------------------*/
826/*---------------------------------------------------------------------------*/
835{
836 bool do_continue = true;
837 Int32 last_nb_hole = 0;
838 while (do_continue){
840 info() << "Parmetis: nb_empty_partition=" << nb_hole << " last_nb_partition=" << last_nb_hole;
841 if (nb_hole==0)
842 break;
843 // Garanti qu'on sort de la boucle si on a toujours le même nombre de trous
844 // qu'avant. Cela permet d'éviter les boucles infinies.
845 // Cela signifie aussi qu'on ne peut pas combler les trous et donc il y
846 // aura surement des partitions vides
848 pwarning() << "Can not remove all empty partitions. This is probably because you try"
849 << " to cut in too many partitions";
850 break;
851 }
853 }
854}
855
856/*---------------------------------------------------------------------------*/
857/*---------------------------------------------------------------------------*/
858
869 const String& name) const
870{
871 int retval = 0;
872
873 idxtype nvtx = 0;
874 idxtype nwgt = 0;
875 bool have_ewgt = false;
876
877 Int32 commrank = pm->commRank();
878 Int32 commsize = pm->commSize();
879 info() << "COMM_SIZE=" << commsize << " RANK=" << commrank;
880 traceMng()->flush();
881 //MPI_Comm_size(metis_comm, &commsize);
882 //MPI_Comm_rank(metis_comm ,&commrank);
883 // NOTE GG: la gestion des erreurs via METIS_ERROR ne fonctionne pas: cela produit un blocage
884 // car il y a un MPI_Allreduce dans la partie sans erreur.
885 // NOTE GG: Ne pas utiliser MPI directement.
886
887#define METIS_ERROR ARCANE_FATAL("_writeGraph")
888
889 StringBuilder filename(name);
890 filename += "_";
891 filename += commrank;
892 std::ofstream file(filename.toString().localstr());
893
894 if (metis_vtkdist.size() != commsize + 1)
895 METIS_ERROR;
896
898 if (metis_xadj.size() != nvtx + 1) {
899 std::cerr << "_writeGraph : nvtx+1 = " << nvtx << " != " << metis_xadj.size() << std::endl;
900 METIS_ERROR;
901 }
902
903 if (nvtx)
904 nwgt = metis_vwgt.size()/nvtx;
905 if (nwgt != 0 && metis_vwgt.size() % nwgt != 0)
906 METIS_ERROR;
907
908 have_ewgt = (metis_ewgt.size() != 0) ;
909 if (have_ewgt && metis_ewgt.size() != metis_adjncy.size())
910 METIS_ERROR;
911
912 if (!file.is_open())
913 METIS_ERROR;
914
915 Int64 localEdges = metis_xadj[nvtx];
916 Int64 globalEdges = pm->reduce(Parallel::ReduceSum,localEdges);
917
918 file << "2" << std::endl;
919 file << commsize << "\t" << commrank << std::endl;
920 file << metis_vtkdist[commsize] << "\t" << globalEdges << std::endl;
921 file << nvtx << "\t" << localEdges << std::endl;
922 file << "0\t" << "0" << have_ewgt << nwgt << std::endl;
923
924 /*
925 Each of these lines begins with the vertex label,
926 if necessary, the vertex load, if necessary, and the vertex degree, followed by the
927 description of the arcs. An arc is defined by the load of the edge, if necessary, and
928 by the label of its other end vertex.
929 */
930
931 //** Lbl Wgt Degree edWgt edLbl ...
932
933 for (idxtype vertnum = 0 ; vertnum < nvtx ; vertnum++) {
934 // file << vertnum + metis_vtkdist[commrank] << " ";
935 for (int dim = 0 ; dim < nwgt ; ++ dim)
936 file << metis_vwgt[vertnum*nwgt+dim] << '\t';
937 file << metis_xadj[vertnum + 1] - metis_xadj[vertnum];
938 for (idxtype edgenum = metis_xadj[vertnum] ;
939 edgenum < metis_xadj[vertnum + 1] ; ++edgenum) {
940 if (have_ewgt)
941 file << '\t' << metis_ewgt[edgenum];
942 file << '\t' << metis_adjncy[edgenum];
943 }
944 file << std::endl;
945 }
946 file.close();
947
948 pm->barrier();
949 return retval;
950}
951
952
953/*---------------------------------------------------------------------------*/
954/*---------------------------------------------------------------------------*/
955
960
961ARCANE_REGISTER_SERVICE_METISMESHPARTITIONER(Metis,MetisMeshPartitioner);
962
963#if ARCANE_DEFAULT_PARTITIONER == METIS_DEFAULT_PARTITIONER
965 ServiceProperty("DefaultPartitioner",ST_SubDomain),
968ARCANE_REGISTER_SERVICE_METISMESHPARTITIONER(DefaultPartitioner,MetisMeshPartitioner);
969#endif
970
971/*---------------------------------------------------------------------------*/
972/*---------------------------------------------------------------------------*/
973
974} // End namespace Arcane
975
976/*---------------------------------------------------------------------------*/
977/*---------------------------------------------------------------------------*/
#define ARCANE_THROW(exception_class,...)
Macro pour envoyer une exception avec formattage.
#define ARCANE_FATAL(...)
Macro envoyant une exception FatalErrorException.
#define ENUMERATE_CELL(name, group)
Enumérateur générique d'un groupe de mailles.
#define ARCANE_SERVICE_INTERFACE(ainterface)
Macro pour déclarer une interface lors de l'enregistrement d'un service.
Generation de la classe de base du Service.
CaseOptionsMetisMeshPartitioner * options() const
Options du jeu de données du service.
Maille d'un maillage.
Definition Item.h:1178
Int32 nbFace() const
Nombre de faces de la maille.
Definition Item.h:1252
Classe permettant d'activer/désactiver temporairement les exceptions flottantes du processeur.
Redistribute graph data to another "communicator".
virtual String name() const =0
Nom du maillage.
virtual CellGroup ownCells()=0
Groupe de toutes les mailles propres au domaine.
Interface d'un partitionneur de maillage.
Interface d'un partitionneur de maillage.
virtual IParallelMng * parallelMng()=0
Gestionnaire de parallèlisme.
virtual ISubDomain * subDomain()=0
Sous-domaine associé
virtual IPrimaryMesh * toPrimaryMesh()=0
Retourne l'instance sous la forme d'un IPrimaryMesh.
Interface du gestionnaire de parallélisme pour un sous-domaine.
virtual bool isThreadImplementation() const =0
Indique si l'implémentation utilise les threads.
virtual void computeMinMaxSum(char val, char &min_val, char &max_val, char &sum_val, Int32 &min_rank, Int32 &max_rank)=0
Calcule en une opération la somme, le min, le max d'une valeur.
virtual Int32 commRank() const =0
Rang de cette instance dans le communicateur.
virtual Int32 commSize() const =0
Nombre d'instance dans le communicateur.
virtual void allGather(ConstArrayView< char > send_buf, ArrayView< char > recv_buf)=0
Effectue un regroupement sur tous les processeurs. Il s'agit d'une opération collective....
virtual char reduce(eReduceType rt, char v)=0
Effectue la réduction de type rt sur le réel v et retourne la valeur.
virtual void barrier()=0
Effectue une barière.
Interface du gestionnaire d'un sous-domaine.
Definition ISubDomain.h:74
virtual const CommonVariables & commonVariables() const =0
Informations sur les variables standards.
@ PNoDump
Indique que la variable ne doit pas être sauvegardée.
Definition IVariable.h:72
Lecteur des fichiers de maillage via la bibliothèque LIMA.
Definition Lima.cc:120
IMesh * mesh() const
Maillage associé au partitionneur.
virtual Real imbalance() const
Déséquilibre de temps de calcul.
virtual Real maxImbalance() const
Déséquilibre maximal autorisé
virtual void changeOwnersFromCells()
Positionne les nouveaux propriétaires des noeuds, arêtes et faces à partir des mailles.
Partitioneur de maillage utilisant la bibliothèque PARMetis.
void partitionMesh(bool initial_partition) override
void _removeEmptyPartsV2(Int32 nb_part, ArrayView< idxtype > metis_part)
Comble les partitions vides (version 2).
Int32 _removeEmptyPartsV2Helper(Int32 nb_part, ArrayView< idxtype > metis_part, Int32 algo_iteration)
Applique une itération de l'algorithme de suppression des partitions vides.
void build() override
Construction de niveau build du 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(const Int32 nb_part, const Int32 nb_own_cell, ArrayView< idxtype > metis_part)
Comble les partitions vides (version 1).
Wrapper autour des appels de Parmetis.
Structure contenant les informations pour créer un service.
Propriétés de création d'un service.
Postionne le nom de l'action en cours d'exécution.
Definition Timer.h:110
Paramètres nécessaires à la construction d'une variable.
Exception lorsqu'un argument est invalide.
virtual void flush()=0
Flush tous les flots.
Constructeur de chaîne de caractère unicode.
String toString() const
Retourne la chaîne de caractères construite.
Chaîne de caractères unicode.
String upper() const
Transforme tous les caractères de la chaîne en majuscules.
Definition String.cc:466
const char * localstr() const
Retourne la conversion de l'instance dans l'encodage UTF-8.
Definition String.cc:227
bool null() const
Retourne true si la chaîne est nulle.
Definition String.cc:304
TraceMessage pwarning() const
ITraceMng * traceMng() const
Gestionnaire de trace.
TraceMessage info() const
Flot pour un message d'information.
Vecteur 1D de données avec sémantique par valeur (style STL).
#define ARCANE_REGISTER_SERVICE(aclass, a_service_property,...)
Macro pour enregistrer un service.
Integer toInteger(Real r)
Converti un Int64 en un Integer.
Int32 toInt32(Int64 v)
Converti un Int64 en un Int32.
Integer toInteger(Real r)
Converti un Real en Integer.
Definition Convert.h:53
-*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
@ ST_SubDomain
Le service s'utilise au niveau du sous-domaine.
@ IK_Cell
Entité de maillage de genre maille.