CoMMA 1.3.2
A geometric agglomerator for unstructured meshes
Loading...
Searching...
No Matches
Coarse_Cell_Container.h
Go to the documentation of this file.
1#ifndef COMMA_PROJECT_COARSE_CELL_CONTAINER_H
2#define COMMA_PROJECT_COARSE_CELL_CONTAINER_H
3
4/*
5 * CoMMA
6 *
7 * Copyright © 2024 ONERA
8 *
9 * Authors: Nicolas Lantos, Alberto Remigi, and Riccardo Milani
10 * Contributors: Karim Anemiche
11 *
12 * This Source Code Form is subject to the terms of the Mozilla Public
13 * License, v. 2.0. If a copy of the MPL was not distributed with this
14 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
15 */
16
17#include <algorithm>
18#include <cassert>
19#include <deque>
20#include <map>
21#include <memory>
22#include <optional>
23#include <vector>
24
25#include "CoMMA/Coarse_Cell.h"
26#include "CoMMA/Dual_Graph.h"
27
28namespace comma {
29
37template<
38 typename CoMMAIndexType,
39 typename CoMMAWeightType,
40 typename CoMMAIntType>
42public:
46
48 using CoarseCellPtr = std::shared_ptr<CoarseCellType>;
49
52
60 DualGraphPtr &fc_graph, const CoMMAIntType singular_card_thresh
61 ) :
62 _ccs(),
63 _fc_graph(fc_graph),
64 _cc_counter(0),
65 _fc_2_cc(fc_graph->_number_of_cells, std::nullopt),
66 _is_fc_agglomerated(fc_graph->_number_of_cells, false),
67 _sing_card_thresh(singular_card_thresh),
70 _singular_cc() {}
71
74
76 std::map<CoMMAIndexType, CoarseCellPtr> _ccs;
77
80
82 CoMMAIndexType _cc_counter;
83
86 std::vector<std::optional<CoMMAIndexType>> _fc_2_cc;
87
90 std::vector<bool> _is_fc_agglomerated;
91
93 CoMMAIntType _sing_card_thresh;
94
99 inline CoMMAIndexType get_number_of_fc_agglomerated() const {
100 return (_nb_of_agglomerated_fc);
101 }
102
106 inline CoMMAIndexType get_nb_of_cc() const { return _cc_counter; }
107
116 inline std::set<CoMMAIndexType> get_neighs_cc(
117 const CoMMAIndexType &i_fc, const CoMMAIndexType &i_cc
118 ) const {
119 std::set<CoMMAIndexType> result;
120 for (auto elem = _fc_graph->neighbours_cbegin(i_fc);
121 elem != _fc_graph->neighbours_cend(i_fc);
122 ++elem) {
123 const auto cc = _fc_2_cc[*elem].value();
124 if (cc != i_cc) result.insert(cc);
125 }
126 return result;
127 }
128
129#if 0
130Not used anymore but we leave it for example purposes
131
133 using CustomMapItT = typename std::map<CoMMAIndexType, SubGraphPtr>::iterator;
138 CustomMapItT remove_cc(CustomMapItT elim) {
139 // we delete the element and we obtained the pointer to the next element in
140 // memory
141 CustomMapItT it = _ccs.erase(elim);
142 // update value of the other nodes
143 for (auto i = it; i != _ccs.end(); i++) {
144 for (auto const &i_fc : i->second->_mapping_l_to_g) {
145 _fc_2_cc[i_fc] = (i->first) - 1;
146 }
147 auto node = _ccs.extract(i);
148 if (!node.empty()) {
149 node.key() = (i->first) - 1;
150 _ccs.insert(std::move(node));
151 }
152 }
153 // return pointer to the next element
154 return (it);
155 }
156#endif
157
164 void correct(const CoMMAIntType max_card) {
165 // We use it to understand if we have succeeded in the correction
166 std::set<typename decltype(_singular_cc)::value_type> removed_cc{};
167 for (const auto &old_cc : _singular_cc) {
168 // It might happen that we agglomerate to a singular cell so that the new
169 // cell was singular when it was created but it is not any more
170 auto &cur_cc = _ccs.at(old_cc);
171 if (cur_cc->_cardinality <= _sing_card_thresh) {
172 const auto &fcs = cur_cc->_s_fc;
173 bool should_remove = false;
174 std::unordered_map<CoMMAIndexType, std::set<CoMMAIndexType>>
175 neighs_by_fc{};
176 neighs_by_fc.reserve(cur_cc->_cardinality);
177 for (const auto &i_fc : fcs) {
178 neighs_by_fc.emplace(i_fc, get_neighs_cc(i_fc, old_cc));
179 }
180 if (cur_cc->_cardinality > 1) {
181 // First try: agglomerate the whole coarse cell to another one
182 std::set<CoMMAIndexType> glob_neighs = {};
183 for (const auto &[i_fc, neighs] : neighs_by_fc) {
184 glob_neighs.insert(neighs.begin(), neighs.end());
185 }
186 if (!glob_neighs.empty()) {
187 std::optional<CoMMAIntType> new_compactness = std::nullopt;
188 const auto new_cc = select_best_cc_to_agglomerate_whole(
189 fcs, glob_neighs, max_card, new_compactness
190 );
191 if (new_cc.has_value()) {
192 // first we assign to the fc_2_cc the new cc (later it will be
193 // renumbered considering the deleted cc)
194 for (const auto &i_fc : fcs) {
195 _fc_2_cc[i_fc] = new_cc.value();
196 }
197 _ccs[new_cc.value()]->insert_cells(fcs, new_compactness);
198 should_remove = true;
199 }
200 }
201 }
202 if (!should_remove) {
203 // If here, we could not agglomerate the whole cell, hence we look
204 // fine cell by fine cell
205 for (const auto &[i_fc, neighs] : neighs_by_fc) {
206 if (!neighs.empty()) {
207 std::optional<CoMMAIntType> new_compactness = std::nullopt;
208 const auto new_cc = select_best_cc_to_agglomerate(
209 i_fc, neighs, max_card, new_compactness
210 );
211 if (new_cc.has_value()) {
212 _fc_2_cc[i_fc] = new_cc.value();
213 _ccs[new_cc.value()]->insert_cell(i_fc, new_compactness);
214 should_remove = true;
215 }
216 }
217 // If the cell has no neighbour (this could happen when the
218 // partitioning does not give a connected partition), unfortunately,
219 // there is nothing that we can do. We just skip it
220 }
221 }
222
223 if (should_remove) {
224 _ccs.erase(old_cc);
225 removed_cc.emplace(old_cc);
226 }
227 } // End if still singular
228 } // End loop over singular cells
229
230 // Now update the ID if necessary
231 if (!removed_cc.empty()) {
232 auto new_ID = *(removed_cc.begin());
233 // Starting from the CC just after the first removed singular cell, update
234 // all cells. Looking for new_ID-1 than doing ++ avoid case of consecutive
235 // singular cells. If the first removed cell was cell 0, then start from
236 // the beginning
237 auto it_cc = _ccs.begin();
238 if (new_ID > 0) {
239 it_cc = _ccs.find(new_ID - 1);
240 ++it_cc;
241 }
242 for (; it_cc != _ccs.end(); ++it_cc, ++new_ID) {
243 // Update fine cells
244 for (auto const &i_fc : it_cc->second->_s_fc) {
245 _fc_2_cc[i_fc] = new_ID;
246 }
247 // Update coarse cell ID
248 auto node = _ccs.extract(it_cc);
249 if (!node.empty()) {
250 node.key() = new_ID;
251 _ccs.insert(std::move(node));
252 }
253 }
254 }
255 }
256
271 std::optional<CoMMAIndexType> select_best_cc_to_agglomerate_whole(
272 const std::unordered_set<CoMMAIndexType> &fcs,
273 const std::set<CoMMAIndexType> &neighs,
274 const CoMMAIntType max_card,
275 std::optional<CoMMAIntType> &new_compactness
276 ) const {
277 CoMMAUnused(max_card);
278 std::unordered_map<CoMMAIndexType, CoMMAIntType> card{};
279 std::unordered_map<CoMMAIndexType, CoMMAIntType> compact{};
280 const auto n_neighs = neighs.size();
281 card.reserve(n_neighs);
282 // Since in the end we sort, wouldn't it be better to just use set instead
283 // of deque?
284 std::deque<CoMMAIndexType> argtrue_compact{};
285 // Loop on neighbours to compute their features
286 for (const auto &cc_idx : neighs) {
287 const auto n_cc = _ccs.at(cc_idx);
288 if (n_cc->_is_isotropic) {
289 // NOLINTNEXTLINE(readability-simplify-boolean-expr)
290 if (true /* n_cc->_cardinality < max_card */) {
291 // On second thought, let us consider also cells with max cardinality
292 // since the number of faces could be important to ensure compactness
293 // of the coarse cell
294 card[cc_idx] = n_cc->_cardinality;
295 // Analysing compactness
296 auto tmp_cc = n_cc->_s_fc; // OK copy
297 tmp_cc.insert(fcs.begin(), fcs.end());
298 const auto new_cpt =
299 _fc_graph->compute_min_fc_compactness_inside_a_cc(tmp_cc);
300 compact[cc_idx] = new_cpt;
301 if (new_cpt > n_cc->_compactness) {
302 argtrue_compact.push_back(cc_idx);
303 }
304 } // End compactness and cardinality
305 } // End if isotropic
306 }
307 if (!argtrue_compact.empty()) {
308 // Sort so that, in the end, if nothing worked, we rely on ID numbering
309 sort(argtrue_compact.begin(), argtrue_compact.end());
310 CoMMAIndexType ret_cc{argtrue_compact[0]};
311 CoMMAIntType cur_min{card[ret_cc]};
312 // If more than one, maximize shared faces and/or minimize cardinality
313 for (const auto &idx : argtrue_compact) {
314 const auto cur_card = card[idx];
315 if (cur_card < cur_min) {
316 cur_min = cur_card;
317 ret_cc = idx;
318 }
319 }
320 new_compactness = compact.at(ret_cc);
321 return ret_cc;
322 }
323 return std::nullopt;
324 }
325
339 std::optional<CoMMAIndexType> select_best_cc_to_agglomerate(
340 const CoMMAIndexType fc,
341 const std::set<CoMMAIndexType> &neighs,
342 const CoMMAIntType max_card,
343 std::optional<CoMMAIntType> &new_compactness
344 ) const {
345 CoMMAUnused(max_card);
346 std::unordered_map<CoMMAIndexType, CoMMAIntType> card{};
347 std::unordered_map<CoMMAIndexType, CoMMAIntType> shared_faces{};
348 std::unordered_map<CoMMAIndexType, CoMMAIntType> compact{};
349 const auto n_neighs = neighs.size();
350 card.reserve(n_neighs);
351 shared_faces.reserve(n_neighs);
352 CoMMAIntType min_card = std::numeric_limits<CoMMAIntType>::max();
353 CoMMAIntType max_shared_f{0};
354 // Since in the end we sort, wouldn't it be better to just use set instead
355 // of deque?
356 std::deque<CoMMAIndexType> argmin_card{};
357 std::deque<CoMMAIndexType> argmax_shared_f{};
358 std::deque<CoMMAIndexType> argtrue_compact{};
359 std::deque<CoMMAIndexType> iso_neighs{};
360 // Loop on neighbours to compute their features
361 for (const auto &cc_idx : neighs) {
362 const auto n_cc = _ccs.at(cc_idx);
363 if (n_cc->_is_isotropic) {
364 iso_neighs.push_back(cc_idx);
365 // NOLINTNEXTLINE(readability-simplify-boolean-expr)
366 if (true /* n_cc->_cardinality < max_card */) {
367 // On second thought, let us consider also cells with max cardinality
368 // since the number of faces could be important to ensure compactness
369 // of the coarse cell
370 const auto cur_card = n_cc->_cardinality;
371 card[cc_idx] = cur_card;
372 if (cur_card < min_card) {
373 min_card = cur_card;
374 argmin_card.clear();
375 argmin_card.push_back(cc_idx);
376 } else if (cur_card == min_card) {
377 argmin_card.push_back(cc_idx);
378 }
379 // @TODO: merge computation of shared faces and compactness?
380 const auto cur_sf = get_shared_faces(fc, n_cc);
381 shared_faces[cc_idx] = cur_sf;
382 if (cur_sf > max_shared_f) {
383 max_shared_f = cur_sf;
384 argmax_shared_f.clear();
385 argmax_shared_f.push_back(cc_idx);
386 } else if (cur_sf == max_shared_f) {
387 argmax_shared_f.push_back(cc_idx);
388 }
389 // Analysing compactness
390 auto tmp_cc = n_cc->_s_fc; // OK copy
391 tmp_cc.insert(fc);
392 const auto new_cpt =
393 _fc_graph->compute_min_fc_compactness_inside_a_cc(tmp_cc);
394 compact[cc_idx] = new_cpt;
395 if (new_cpt > n_cc->_compactness) {
396 argtrue_compact.push_back(cc_idx);
397 }
398 } // End compactness and cardinality
399 } // End if isotropic
400 }
401 // Now, it's time to choose the best neighbours. Priority is given to those
402 // which: 1 - Increase the degree of compactness
403 if (!argtrue_compact.empty()) {
404 // Sort so that, in the end, if nothing worked, we rely on ID numbering
405 sort(argtrue_compact.begin(), argtrue_compact.end());
406 CoMMAIndexType ret_cc{argtrue_compact[0]};
407 CoMMAIntType cur_max{shared_faces[ret_cc]};
408 // If more than one, maximize shared faces and/or minimize cardinality
409 for (const auto &idx : argtrue_compact) {
410 const auto cur_shf = shared_faces[idx];
411 if (cur_shf > cur_max) {
412 cur_max = cur_shf;
413 ret_cc = idx;
414 } else if (cur_shf == cur_max && card[idx] < card[ret_cc]) {
415 ret_cc = idx;
416 }
417 }
418 new_compactness = compact.at(ret_cc);
419 return ret_cc;
420 }
421 // 2 - Maximize the number of shared faces
422 if (!argmax_shared_f.empty()) {
423 // Sort so that, in the end, if nothing worked, we rely on ID numbering
424 sort(argmax_shared_f.begin(), argmax_shared_f.end());
425 CoMMAIndexType ret_cc{argmax_shared_f[0]};
426 CoMMAIntType cur_min{card[ret_cc]};
427 // ..but let's see if among all the cells there is one with smaller
428 // cardinality
429 for (const auto &idx : argmax_shared_f) {
430 if (card[idx] < cur_min) {
431 ret_cc = idx;
432 cur_min = card[ret_cc];
433 }
434 }
435 new_compactness = compact.at(ret_cc);
436 return ret_cc;
437 }
438 // 3 - Minimize the cardinality
439 if (!argmin_card.empty()) {
440 // We should never need to come here...
441 // @TODO: I'm not sure what I could consider here to decide which cell to
442 // return. The aspect-ratio maybe? In the mean time, I return the one with
443 // the lowest ID
444 const auto ret_cc =
445 *(min_element(argmin_card.begin(), argmin_card.end()));
446 new_compactness = compact.at(ret_cc);
447 return ret_cc;
448 }
449 // If everything failed, look through the neighbours
450 if (!iso_neighs.empty()) return iso_neighs[0];
451 // otherwise, there is nothing we can do
452 return std::nullopt;
453 }
454
461 inline CoMMAIntType get_shared_faces(
462 const CoMMAIndexType fc, const CoarseCellPtr cc
463 ) const {
464 CoMMAIntType shared_faces{0};
465 for (const auto &i_fc : cc->_s_fc) {
466 shared_faces += count(
467 _fc_graph->neighbours_cbegin(i_fc), _fc_graph->neighbours_cend(i_fc), fc
468 );
469 }
470 return shared_faces;
471 }
472
483 CoMMAIndexType create_cc(
484 const std::unordered_set<CoMMAIndexType> &s_fc,
485 const CoMMAIntType compactness,
486 bool is_anisotropic = false,
487 bool is_creation_delayed = false
488 ) {
489 // Create a course cell from the fine cells and update the fc_2_cc tree.
490 assert((!is_anisotropic) || (!is_creation_delayed));
491 for (const auto &i_fc : s_fc) {
492 assert(!_fc_2_cc[i_fc].has_value());
493 if (!_is_fc_agglomerated[i_fc]) {
494 // Rq: initialise to False pour chaque niveau dans agglomerate(...)
495 _is_fc_agglomerated[i_fc] = true;
497 }
498 }
499 // Anisotropic case
500 bool is_mutable = true;
501 if (is_anisotropic) {
502 // we collect the various cc, where the index in the vector is the i_cc
503 _ccs[_cc_counter] = std::make_shared<CoarseCellType>(
504 _fc_graph, _cc_counter, s_fc, compactness, !is_anisotropic
505 );
506 is_mutable = false;
507 }
508 if (!is_creation_delayed) {
509 // We create the cc right now.
510 // Everything is updated:
511 if (is_mutable) {
512 // the cell can be modified afterwards and is thus defined in dict_cc
513 // and dict_card_cc, dict_compactness_2_cc, dict_cc_to_compactness
514 // Update of dict_cc:
515 //==================
516 // we collect the various cc, where the index in the vector is the i_cc
517 _ccs[_cc_counter] = std::make_shared<CoarseCellType>(
518 _fc_graph, _cc_counter, s_fc, compactness
519 );
520 if (!is_anisotropic
521 && static_cast<decltype(_sing_card_thresh)>(s_fc.size())
523 _singular_cc.emplace_back(_cc_counter);
524 }
525 // Update of _associatedCoarseCellNumber the output of the current
526 // function agglomerate _fc_2_cc is filled with _cc_counter
527 for (const auto &i_fc : s_fc) {
528 // Only if not isCreationDelayed:
529 assert(!_fc_2_cc[i_fc].has_value());
530 _fc_2_cc[i_fc] = _cc_counter;
531 }
532 // Update of the number of CC
533 // ####################################
534 _cc_counter++; // Only if not isCreationDelayed
535 } else {
536 // We do not create the coarse cell yet.
537 // As this coarse cell will be soon deleted, we want its coarse index to
538 // be the greater possible.
539 // Only isFineCellAgglomerated_tmp, number_of_fine_agglomerated_cells_tmp
540 // and dict_DistributionOfCardinalOfCoarseElements are modified!
541 _delayed_cc.emplace_back(s_fc, compactness);
542 }
543 return (_cc_counter - 1);
544 }
545
550 for (const auto &[s_fc, cpt] : _delayed_cc) {
551 create_cc(s_fc, cpt);
552 }
553 _delayed_cc.clear();
554 }
555
556protected:
558 CoMMAIndexType _nb_of_agglomerated_fc = 0;
559
564 std::vector<std::pair<std::unordered_set<CoMMAIndexType>, CoMMAIntType>>
566
569 std::deque<CoMMAIndexType> _singular_cc;
570};
571
572} // end namespace comma
573
574#endif // COMMA_PROJECT_COARSE_CELL_GRAPH_H
#define CoMMAUnused(var)
Convenient function to avoid unused warnings.
Definition: Util.h:38
Class implementing a custom container where the coarse cells are stored.
Definition: Coarse_Cell_Container.h:41
std::map< CoMMAIndexType, CoarseCellPtr > _ccs
Map containing the CC and their ID.
Definition: Coarse_Cell_Container.h:76
std::optional< CoMMAIndexType > select_best_cc_to_agglomerate_whole(const std::unordered_set< CoMMAIndexType > &fcs, const std::set< CoMMAIndexType > &neighs, const CoMMAIntType max_card, std::optional< CoMMAIntType > &new_compactness) const
Choose among the neighbouring coarse cells, the one to which a singular coarse cell should be assigne...
Definition: Coarse_Cell_Container.h:271
std::deque< CoMMAIndexType > _singular_cc
Set of singular coarse cells, that is, composed of only one fine cell.
Definition: Coarse_Cell_Container.h:569
CoMMAIndexType _nb_of_agglomerated_fc
Number of agglomerated fine cells.
Definition: Coarse_Cell_Container.h:558
CoMMAIntType _sing_card_thresh
Minimum cardinality for receiver CC when correcting.
Definition: Coarse_Cell_Container.h:93
std::vector< std::pair< std::unordered_set< CoMMAIndexType >, CoMMAIntType > > _delayed_cc
Vector of the set of fine cells composing the too small coarse cells that will be built at the end of...
Definition: Coarse_Cell_Container.h:565
std::vector< std::optional< CoMMAIndexType > > _fc_2_cc
Output vector identifying to which coarse cell the fine cell belongs.
Definition: Coarse_Cell_Container.h:86
std::shared_ptr< CoarseCellType > CoarseCellPtr
Type for a shared pointer to a Dual_Graph object.
Definition: Coarse_Cell_Container.h:48
typename CoarseCellType::DualGraphPtr DualGraphPtr
Type for a shared pointer to a Dual_Graph object.
Definition: Coarse_Cell_Container.h:51
CoMMAIndexType _cc_counter
Number of coarse cells.
Definition: Coarse_Cell_Container.h:82
std::optional< CoMMAIndexType > select_best_cc_to_agglomerate(const CoMMAIndexType fc, const std::set< CoMMAIndexType > &neighs, const CoMMAIntType max_card, std::optional< CoMMAIntType > &new_compactness) const
Choose among the neighbouring coarse cells, the one to which a fine cell should be assigned to....
Definition: Coarse_Cell_Container.h:339
CoMMAIndexType create_cc(const std::unordered_set< CoMMAIndexType > &s_fc, const CoMMAIntType compactness, bool is_anisotropic=false, bool is_creation_delayed=false)
It creates a coarse cell based on the set of fine cells given as input.
Definition: Coarse_Cell_Container.h:483
CoMMAIndexType get_number_of_fc_agglomerated() const
Helper to get the member variable that defines the number of agglomerated fine cells.
Definition: Coarse_Cell_Container.h:99
std::vector< bool > _is_fc_agglomerated
Vector of boolean telling whether a fine cell has been agglomerated.
Definition: Coarse_Cell_Container.h:90
CoMMAIndexType get_nb_of_cc() const
Helper to get the number of coarse cells.
Definition: Coarse_Cell_Container.h:106
DualGraphPtr _fc_graph
Dual graph representation.
Definition: Coarse_Cell_Container.h:79
CoMMAIntType get_shared_faces(const CoMMAIndexType fc, const CoarseCellPtr cc) const
Compute the number of faces shared between a fine cell and a coarse one.
Definition: Coarse_Cell_Container.h:461
~Coarse_Cell_Container()=default
Destructor.
Coarse_Cell_Container(DualGraphPtr &fc_graph, const CoMMAIntType singular_card_thresh)
Create a Coarse_Cell_Container.
Definition: Coarse_Cell_Container.h:59
std::set< CoMMAIndexType > get_neighs_cc(const CoMMAIndexType &i_fc, const CoMMAIndexType &i_cc) const
Retrieve the indexes of the neighbouring coarse cells to a given fine cell in a coarse cell (excludin...
Definition: Coarse_Cell_Container.h:116
void correct(const CoMMAIntType max_card)
Implementation of the correction. In this version it implements the correction of singular cells (if ...
Definition: Coarse_Cell_Container.h:164
void cc_create_all_delayed_cc()
Creates all the delayed coarse cell. It works only when the delayed cell flag is activated in the agg...
Definition: Coarse_Cell_Container.h:549
Class describing a coarse cell.
Definition: Coarse_Cell.h:37
std::shared_ptr< Dual_Graph< CoMMAIndexType, CoMMAWeightType, CoMMAIntType > > DualGraphPtr
Type for a shared pointer to a Dual_Graph object.
Definition: Coarse_Cell.h:41
Definition: Agglomerator.h:37