Documentation 6b54b78464
Loading...
Searching...
No Matches
graph_toolbox.hpp
Go to the documentation of this file.
1/*!
2 * \file graph_toolbox.hpp
3 * \brief Functions and classes to build/represent sparse graphs or sparse patterns.
4 * \author P. Gomes
5 * \version 8.5.0 "Harrier"
6 *
7 * SU2 Project Website: https://su2code.github.io
8 *
9 * The SU2 Project is maintained by the SU2 Foundation
10 * (http://su2foundation.org)
11 *
12 * Copyright 2012-2026, SU2 Contributors (cf. AUTHORS.md)
13 *
14 * SU2 is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU Lesser General Public
16 * License as published by the Free Software Foundation; either
17 * version 2.1 of the License, or (at your option) any later version.
18 *
19 * SU2 is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 * Lesser General Public License for more details.
23 *
24 * You should have received a copy of the GNU Lesser General Public
25 * License along with SU2. If not, see <http://www.gnu.org/licenses/>.
26 */
27
28#pragma once
29
30#include "../code_config.hpp"
31#include "../containers/C2DContainer.hpp"
32#include "../parallelization/omp_structure.hpp"
33
34#include <set>
35#include <vector>
36#include <limits>
37#include <cassert>
38#include <algorithm>
39#include <numeric>
40
41/// \addtogroup Graph
42/// @{
43
44/*!
45 * \enum ConnectivityType
46 * \brief In FVM points are connected by the edges (faces) of the grid.
47 * In FEM, two points are connected if they have an element in common.
48 */
50
51/*!
52 * \class CCompressedSparsePattern
53 * \brief A simple class to store adjacency information in a
54 * compressed format suitable for sparse matrix operations.
55 * If built for row-major storage the inner indices are column indices
56 * and the pattern should be used as (row,icol), otherwise as (col,irow).
57 */
58template <typename Index_t>
60 static_assert(std::is_integral<Index_t>::value);
61
62 private:
63 su2vector<Index_t> m_outerPtr; /*!< \brief Start positions of the inner indices for each outer index. */
64 su2vector<Index_t> m_innerIdx; /*!< \brief Inner indices of the non zero entries. */
65 su2vector<Index_t> m_diagPtr; /*!< \brief Position of the diagonal entry. */
66 su2vector<Index_t> m_innerIdxTransp; /*!< \brief Position of the transpose non zero entries, requires symmetry. */
67
68 public:
69 using IndexType = Index_t;
70
71 /*!
72 * \brief Type to allow range for loops over inner indices.
73 */
74 struct CInnerIter {
75 const IndexType* const m_first = nullptr;
76 const IndexType* const m_last = nullptr;
77 CInnerIter(const IndexType* first, const IndexType* last) : m_first(first), m_last(last) {}
78 const IndexType* begin() const { return m_first; }
79 const IndexType* end() const { return m_last; }
80 };
81
82 /*!
83 * \brief Default construction.
84 */
86
87 /*!
88 * \brief Construct with default inner indices.
89 * \param[in] outerPtrBegin - Start of outer pointers.
90 * \param[in] outerPtrEnd - End of outer pointers.
91 * \param[in] defaultInnerIdx - Default value for inner indices.
92 */
93 template <class Iterator>
94 CCompressedSparsePattern(Iterator outerPtrBegin, Iterator outerPtrEnd, Index_t defaultInnerIdx) {
95 const auto size = outerPtrEnd - outerPtrBegin;
96 m_outerPtr.resize(size);
97 Index_t k = 0;
98 for (auto it = outerPtrBegin; it != outerPtrEnd; ++it) m_outerPtr(k++) = *it;
99
100 m_innerIdx.resize(m_outerPtr(size - 1)) = defaultInnerIdx;
101 }
102
103 /*!
104 * \brief Construct from rvalue refs.
105 * \note This is the most efficient constructor as no data copy occurs.
106 * \param[in] outerPtr - Outer index pointers.
107 * \param[in] innerIdx - Inner indices.
108 */
110 : m_outerPtr(outerPtr), m_innerIdx(innerIdx) {
111 /*--- perform a basic sanity check ---*/
112 assert(m_innerIdx.size() == m_outerPtr(m_outerPtr.size() - 1));
113 }
114
115 /*!
116 * \brief Construct from vector-like objects of any type with
117 * methods "size()" and "data()" (returning a pointer).
118 * \param[in] outerPtr - Outer index pointers.
119 * \param[in] innerIdx - Inner indices.
120 */
121 template <class T>
123 m_outerPtr.resize(outerPtr.size());
124 for (Index_t i = 0; i < outerPtr.size(); ++i) m_outerPtr(i) = outerPtr.data()[i];
125
126 m_innerIdx.resize(innerIdx.size());
127 for (Index_t i = 0; i < innerIdx.size(); ++i) m_innerIdx(i) = innerIdx.data()[i];
128
129 /*--- perform a basic sanity check ---*/
130 assert(m_innerIdx.size() == m_outerPtr(m_outerPtr.size() - 1));
131 }
132
133 /*!
134 * \brief Build from a "list of lists" type object
135 * \param[in] lil - An object with operator [][] and method "size" e.g. vector<vector<?> >.
136 */
137 template <class T>
139 m_outerPtr.resize(lil.size() + 1);
140 m_outerPtr(0) = 0;
141 for (Index_t i = 1; i < Index_t(m_outerPtr.size()); ++i) m_outerPtr(i) = m_outerPtr(i - 1) + lil[i - 1].size();
142
143 m_innerIdx.resize(m_outerPtr(lil.size()));
144 Index_t k = 0;
145 for (Index_t i = 0; i < Index_t(lil.size()); ++i)
146 for (Index_t j = 0; j < Index_t(lil[i].size()); ++j) m_innerIdx(k++) = lil[i][j];
147 }
148
149 /*!
150 * \brief Build a list of pointers to the diagonal entries of the pattern.
151 */
153 if (!m_diagPtr.empty()) return;
154
155 m_diagPtr.resize(getOuterSize());
156
158 for (Index_t k = 0; k < getOuterSize(); ++k) m_diagPtr(k) = findInnerIdx(k, k);
160 }
161
162 /*!
163 * \brief Build a list of pointers to the transpose entries of the pattern, requires symmetry.
164 */
166 if (!m_innerIdxTransp.empty()) return;
167
168 m_innerIdxTransp.resize(getNumNonZeros());
169
171 for (Index_t i = 0; i < getOuterSize(); ++i) {
172 for (Index_t k = m_outerPtr(i); k < m_outerPtr(i + 1); ++k) {
173 auto j = m_innerIdx(k);
174 m_innerIdxTransp(k) = findInnerIdx(j, i);
175 assert(m_innerIdxTransp(k) != m_innerIdx.size() && "The pattern is not symmetric.");
176 }
177 }
179 }
180
181 /*!
182 * \return True if the pattern is empty, i.e. has not been built yet.
183 */
184 inline bool empty() const { return m_outerPtr.empty() || m_innerIdx.empty(); }
185
186 /*!
187 * \return Number of rows/columns.
188 */
189 inline Index_t getOuterSize() const { return m_outerPtr.size() - 1; }
190
191 /*!
192 * \return Number of non zero entries.
193 */
194 inline Index_t getNumNonZeros() const { return m_innerIdx.size(); }
195
196 /*!
197 * \param[in] iOuterIdx - Outer index.
198 * \return Number of inner indices associated with the outer index.
199 */
200 inline Index_t getNumNonZeros(Index_t iOuterIdx) const { return m_outerPtr(iOuterIdx + 1) - m_outerPtr(iOuterIdx); }
201
202 /*!
203 * \param[in] iOuterIdx - Outer index.
204 * \param[in] iNonZero - Relative position of the inner index.
205 * \return The index of the i'th inner index associated with the outer index.
206 */
207 inline Index_t getInnerIdx(Index_t iOuterIdx, Index_t iNonZero) const {
208 assert(iNonZero >= 0 && iNonZero < getNumNonZeros(iOuterIdx));
209 return m_innerIdx(m_outerPtr(iOuterIdx) + iNonZero);
210 }
211
212 /*!
213 * \param[in] iOuterIdx - Outer index.
214 * \param[in] iNonZero - Relative position of the inner index.
215 * \return The index of the i'th inner index associated with the outer index.
216 */
217 inline Index_t& getInnerIdx(Index_t iOuterIdx, Index_t iNonZero) {
218 assert(iNonZero >= 0 && iNonZero < getNumNonZeros(iOuterIdx));
219 return m_innerIdx(m_outerPtr(iOuterIdx) + iNonZero);
220 }
221
222 /*!
223 * \param[in] iOuterIdx - Outer index.
224 * \return Iterator to inner dimension to use in range for loops.
225 */
226 inline CInnerIter getInnerIter(Index_t iOuterIdx) const {
227 return CInnerIter(m_innerIdx.data() + m_outerPtr(iOuterIdx), m_innerIdx.data() + m_outerPtr(iOuterIdx + 1));
228 }
229
230 /*!
231 * \param[in] iOuterIdx - Outer index (row/col).
232 * \param[in] iInnerIdx - Inner index (col/row).
233 * \return Absolute position of non zero entry (iOuterIdx,iInnerIdx),
234 * or NNZ if position does not belong to the pattern.
235 */
236 inline Index_t findInnerIdx(Index_t iOuterIdx, Index_t iInnerIdx) const {
237 for (Index_t k = m_outerPtr(iOuterIdx); k < m_outerPtr(iOuterIdx + 1); ++k)
238 if (m_innerIdx(k) == iInnerIdx) return k;
239 return m_innerIdx.size();
240 }
241
242 /*!
243 * \param[in] iOuterIdx - Outer index (row/col).
244 * \param[in] iInnerIdx - Inner index (col/row).
245 * \return True if (iOuterIdx,iInnerIdx) exists, i.e. is non zero.
246 */
247 inline bool isNonZero(Index_t iOuterIdx, Index_t iInnerIdx) const {
248 return findInnerIdx(iOuterIdx, iInnerIdx) < m_innerIdx.size();
249 }
250
251 /*!
252 * \param[in] iOuterIdx - Outer index (row/col).
253 * \param[in] iInnerIdx - Inner index (col/row).
254 * \return Absolute position of non zero entry (iOuterIdx,iInnerIdx).
255 * \note This method is only safe if the entry exists.
256 */
257 inline Index_t quickFindInnerIdx(Index_t iOuterIdx, Index_t iInnerIdx) const {
258 assert(isNonZero(iOuterIdx, iInnerIdx) && "Error, j does not belong to NZ(i).");
259 Index_t k = m_outerPtr(iOuterIdx);
260 while (m_innerIdx(k) != iInnerIdx) ++k;
261 return k;
262 }
263
264 /*!
265 * \param[in] iDiagIdx - Diagonal index (row == col).
266 * \return Absolute position of the diagonal entry.
267 */
268 inline Index_t getDiagPtr(Index_t iDiagIdx) const { return m_diagPtr(iDiagIdx); }
269
270 /*!
271 * \return Raw pointer to the outer pointer vector.
272 */
273 inline const Index_t* outerPtr() const {
274 assert(!empty() && "Sparse pattern has not been built.");
275 return m_outerPtr.data();
276 }
277
278 /*!
279 * \return Raw pointer to the inner index vector.
280 */
281 inline const Index_t* innerIdx() const {
282 assert(!empty() && "Sparse pattern has not been built.");
283 return m_innerIdx.data();
284 }
285
286 /*!
287 * \return Raw pointer to the inner index vector, offset for a given outer index.
288 */
289 inline const Index_t* innerIdx(Index_t iOuterIdx) const {
290 assert(!empty() && "Sparse pattern has not been built.");
291 return m_innerIdx.data() + m_outerPtr(iOuterIdx);
292 }
293
294 /*!
295 * \return Raw pointer to the diagonal pointer vector.
296 */
297 inline const Index_t* diagPtr() const {
298 assert(!m_diagPtr.empty() && "Diagonal map has not been built.");
299 return m_diagPtr.data();
300 }
301
302 /*!
303 * \return Raw pointer to the transpose pointer vector.
304 */
305 inline const su2vector<Index_t>& transposePtr() const {
306 assert(!m_innerIdxTransp.empty() && "Transpose map has not been built.");
307 return m_innerIdxTransp;
308 }
309
310 /*!
311 * \return The minimum inner index.
312 */
313 Index_t getMinInnerIdx() const {
314 Index_t idx = std::numeric_limits<Index_t>::max();
315 for (Index_t k = 0; k < m_innerIdx.size(); ++k) idx = std::min(idx, m_innerIdx(k));
316 return idx;
317 }
318
319 /*!
320 * \return The maximum inner index.
321 */
322 Index_t getMaxInnerIdx() const {
323 Index_t idx = std::numeric_limits<Index_t>::min();
324 for (Index_t k = 0; k < m_innerIdx.size(); ++k) idx = std::max(idx, m_innerIdx(k));
325 return idx;
326 }
327};
328
329/*!
330 * \brief Alias a type of container as the edge map class, this is a N by 2 container
331 * that maps the two sparse entries referenced by an edge (ij and ji) to two
332 * non zero entries of a sparse pattern.
333 */
334template <typename Index_t>
336
340
341/*!
342 * \brief Build a sparse pattern from geometry information, of type FVM or FEM,
343 * for a given fill-level. At fill-level N, the immediate neighbors of the
344 * points in level N-1 are also considered neighbors of the base point.
345 * The resulting pattern is that of A^{N+1} where A is the sparse matrix
346 * of immediate neighbors.
347 * \note Algorithm is equivalent to the implementation by F. Palacios,
348 * A. Bueno, and T. Economon from CSysMatrix.
349 * \param[in] geometry - Definition of the grid.
350 * \param[in] type - Of connectivity.
351 * \param[in] fillLvl - Target degree of neighborhood (immediate neighbors always added).
352 * \return Compressed-Storage-Row sparse pattern.
353 */
354template <class Geometry_t, typename Index_t>
355CCompressedSparsePattern<Index_t> buildCSRPattern(Geometry_t& geometry, ConnectivityType type, Index_t fillLvl) {
356 Index_t nPoint = geometry.GetnPoint();
357
358 std::vector<Index_t> outerPtr(nPoint + 1);
359 std::vector<Index_t> innerIdx;
360 innerIdx.reserve(nPoint); // at least this much space is needed
361
362 for (Index_t iPoint = 0; iPoint < nPoint; ++iPoint) {
363 /*--- Inner indices for iPoint start here. ---*/
364 outerPtr[iPoint] = innerIdx.size();
365
366 /*--- Use a set to avoid duplication and keep ascending order. ---*/
367 std::set<Index_t> neighbors;
368
369 /*--- Insert base point. ---*/
370 neighbors.insert(iPoint);
371
372 /*--- Neighbors added in previous level. ---*/
373 std::set<Index_t> addedNeighbors(neighbors);
374
375 for (Index_t iLevel = 0;; ++iLevel) {
376 /*--- New points added in this level. ---*/
377 std::set<Index_t> newNeighbors;
378
379 /*--- For each point previously added, add its level 0
380 * neighbors, not duplicating any existing neighbor. ---*/
381 for (auto jPoint : addedNeighbors) {
382 if (type == ConnectivityType::FiniteVolume) {
383 /*--- For FVM we know the neighbors of point j directly. ---*/
384 for (Index_t kPoint : geometry.nodes->GetPoints(jPoint))
385 if (neighbors.count(kPoint) == 0) // no duplication
386 newNeighbors.insert(kPoint);
387 } else // FiniteElement
388 {
389 /*--- For FEM we need the nodes of all elements that contain point j. ---*/
390 for (auto iElem : geometry.nodes->GetElems(jPoint)) {
391 auto elem = geometry.elem[iElem];
392
393 for (unsigned short iNode = 0; iNode < elem->GetnNodes(); ++iNode) {
394 Index_t kPoint = elem->GetNode(iNode);
395
396 if (neighbors.count(kPoint) == 0) // no duplication
397 newNeighbors.insert(kPoint);
398 }
399 }
400 }
401 }
402
403 neighbors.insert(newNeighbors.begin(), newNeighbors.end());
404
405 if (iLevel >= fillLvl) break;
406
407 /*--- For the next level we get the neighbours of the new points. ---*/
408 addedNeighbors = newNeighbors;
409 }
410
411 /*--- Store final sparse pattern for iPoint. ---*/
412 innerIdx.insert(innerIdx.end(), neighbors.begin(), neighbors.end());
413 }
414 outerPtr.back() = innerIdx.size();
415
416 /*--- Return pattern as CCompressedSparsePattern object. ---*/
417 return CCompressedSparsePattern<Index_t>(outerPtr, innerIdx);
418}
419
420/*!
421 * \brief Extract the strictly-lower part of a symmetric compressed sparse pattern.
422 * For each row i, the lower entries are those at positions [outerPtr[i], diagPtr[i]).
423 * \param[in] csr - Full symmetric pattern with diagonal pointer already built.
424 * \return Strictly-lower CSR pattern.
425 */
426template <typename Index_t>
428 assert(!csr.empty());
429 const auto nPoint = csr.getOuterSize();
430 const auto* outerPtr = csr.outerPtr();
431 const auto* innerIdx = csr.innerIdx();
432 const auto* diagPtr = csr.diagPtr();
433
434 su2vector<Index_t> outerPtrL(nPoint + 1);
435 outerPtrL(0) = 0;
436 for (auto i = 0ul; i < nPoint; ++i) outerPtrL(i + 1) = outerPtrL(i) + static_cast<Index_t>(diagPtr[i] - outerPtr[i]);
437
438 su2vector<Index_t> innerIdxL(outerPtrL(nPoint));
439 Index_t k = 0;
440 for (auto i = 0ul; i < nPoint; ++i)
441 for (auto p = outerPtr[i]; p < diagPtr[i]; ++p) innerIdxL(k++) = innerIdx[p];
442
443 return CCompressedSparsePattern<Index_t>(std::move(outerPtrL), std::move(innerIdxL));
444}
445
446/*!
447 * \brief Extract the strictly-upper part of a symmetric compressed sparse pattern.
448 * For each row i, the upper entries are those at positions (diagPtr[i], outerPtr[i+1]).
449 * \param[in] csr - Full symmetric pattern with diagonal pointer already built.
450 * \return Strictly-upper CSR pattern.
451 */
452template <typename Index_t>
454 assert(!csr.empty());
455 const auto nPoint = csr.getOuterSize();
456 const auto* outerPtr = csr.outerPtr();
457 const auto* innerIdx = csr.innerIdx();
458 const auto* diagPtr = csr.diagPtr();
459
460 su2vector<Index_t> outerPtrU(nPoint + 1);
461 outerPtrU(0) = 0;
462 for (auto i = 0ul; i < nPoint; ++i)
463 outerPtrU(i + 1) = outerPtrU(i) + static_cast<Index_t>(outerPtr[i + 1] - diagPtr[i] - 1);
464
465 su2vector<Index_t> innerIdxU(outerPtrU(nPoint));
466 Index_t k = 0;
467 for (auto i = 0ul; i < nPoint; ++i)
468 for (auto p = diagPtr[i] + 1; p < outerPtr[i + 1]; ++p) innerIdxU(k++) = innerIdx[p];
469
470 return CCompressedSparsePattern<Index_t>(std::move(outerPtrU), std::move(innerIdxU));
471}
472
473/*!
474 * \brief Build bijective maps between strictly-lower (L) and strictly-upper (U) non-zero entries
475 * that are each other's transposes. Requires a symmetric pattern.
476 * l_to_u[k_l] = k_u such that U-entry k_u is the transpose of L-entry k_l, and vice-versa.
477 * \param[in] pattern_l - Strictly-lower CSR pattern.
478 * \param[in] pattern_u - Strictly-upper CSR pattern.
479 * \param[out] l_to_u - For each L-entry index, the U-entry index of its transpose.
480 * \param[out] u_to_l - For each U-entry index, the L-entry index of its transpose.
481 */
482template <typename Index_t>
485 su2vector<Index_t>& u_to_l) {
486 const auto nnz_l = pattern_l.getNumNonZeros();
487 const auto nnz_u = pattern_u.getNumNonZeros();
488 assert(nnz_l == nnz_u && "L and U must have the same NNZ (symmetric pattern).");
489
490 l_to_u.resize(nnz_l);
491 u_to_l.resize(nnz_u);
492
493 for (Index_t i = 0; i < pattern_l.getOuterSize(); ++i) {
494 for (Index_t k_l = pattern_l.outerPtr()[i]; k_l < pattern_l.outerPtr()[i + 1]; ++k_l) {
495 const Index_t j = pattern_l.innerIdx()[k_l]; // j < i (strictly lower)
496 const Index_t k_u = pattern_u.quickFindInnerIdx(j, i); // (j,i) is in U since j<i
497 l_to_u(k_l) = k_u;
498 u_to_l(k_u) = k_l;
499 }
500 }
501}
502
503/*!
504 * \brief Create the natural coloring (equivalent to the normal sequential loop
505 * order) for a given number of inner indexes.
506 * \note This is to reduce overhead in "OpenMP-ready" code when only 1 thread is used.
507 * \param[in] numInnerIndexes - Number of indexes that are to be colored.
508 * \return Natural (sequential) coloring of the inner indices.
509 */
510template <class T = CCompressedSparsePatternUL, class Index_t = typename T::IndexType>
511T createNaturalColoring(Index_t numInnerIndexes) {
512 /*--- One color. ---*/
513 su2vector<Index_t> outerPtr(2);
514 outerPtr(0) = 0;
515 outerPtr(1) = numInnerIndexes;
516
517 /*--- Containing all indexes in ascending order. ---*/
518 su2vector<Index_t> innerIdx(numInnerIndexes);
519 std::iota(innerIdx.data(), innerIdx.data() + numInnerIndexes, 0);
520
521 return T(std::move(outerPtr), std::move(innerIdx));
522}
523
524/*!
525 * \brief Color contiguous groups of outer indices of a sparse pattern such that
526 * within each color, any two groups do not have inner indices in common.
527 * \note Within a group, two outer indices will generally have common inner indices.
528 * The coloring is returned as a compressed sparse pattern where the colors
529 * are outer indices, and the outer indices of the input pattern are the
530 * inner indices of the coloring. A simple greedy algorithm is used.
531 * Using a sparse pattern as input allows "anything" to be colored e.g.
532 * FVM edges, FEM elements, the rows/columns of a sparse matrix, etc.
533 * \note The worst that can happen in this method is needing an unreasonable number
534 * of colors, or too much memory due to a large range of the inner indices.
535 * The last two template parameters limit both, in case of failure an empty
536 * pattern is returned.
537 * \param[in] pattern - Sparse pattern to be colored.
538 * \param[in] groupSize - Size of the outer index groups, default 1.
539 * \param[in] includeOuterIdx - If the outer indices are the same type of entity as
540 * the inner indices this must be true. For example, when both are point
541 * indices, i.e. the pattern is the adjacency matrix.
542 * \param[in] balanceColors - Try to balance number of indexes per color,
543 * tends to result in worse locality (thus false by default).
544 * \param[out] indexColor - Optional, vector with colors given to the outer indices.
545 * \return Coloring in the same type of the input pattern.
546 */
547template <typename Color_t = unsigned char, size_t MaxColors = 255, size_t MaxMB = 128, class T>
548T colorSparsePattern(const T& pattern, size_t groupSize = 1, bool includeOuterIdx = false, bool balanceColors = false,
549 std::vector<Color_t>* indexColor = nullptr) {
550 static_assert(std::is_integral<Color_t>::value);
551 static_assert(std::numeric_limits<Color_t>::max() >= MaxColors);
552
553 using Index_t = typename T::IndexType;
554
555 const Index_t grpSz = groupSize;
556 const Index_t nOuter = pattern.getOuterSize();
557
558 /*--- Trivial case. ---*/
559 if (groupSize >= nOuter) return createNaturalColoring(nOuter);
560
561 const Index_t minIdx = pattern.getMinInnerIdx();
562 const Index_t nInner = pattern.getMaxInnerIdx() + 1 - minIdx;
563
564 /*--- Check the max memory condition (<< 23 is to count bits). ---*/
565 if (size_t(nInner) > (MaxMB << 23)) return T();
566
567 /*--- Vector with the color given to each outer index. ---*/
568 std::vector<Color_t> idxColor(nOuter);
569
570 /*--- Start with one color, with no indices assigned. ---*/
571 std::vector<Index_t> colorSize(1, 0);
572 Color_t nColor = 1;
573
574 {
575 /*--- For each color keep track of the inner indices that are in it. ---*/
576 std::vector<std::vector<bool>> innerInColor;
577 innerInColor.emplace_back(nInner, false);
578
579 /*--- Order in which we look for space in the colors to insert a new group. ---*/
580 std::vector<Color_t> searchOrder(MaxColors);
581
582 auto outerPtr = pattern.outerPtr();
583 auto innerIdx = pattern.innerIdx();
584
585 for (Index_t iOuter = 0; iOuter < nOuter; iOuter += grpSz) {
586 Index_t grpEnd = std::min(iOuter + grpSz, nOuter);
587
588 searchOrder.resize(nColor);
589 std::iota(searchOrder.begin(), searchOrder.end(), 0);
590
591 /*--- Balance sizes by looking for space in smaller colors first. ---*/
592 if (balanceColors) {
593 std::sort(searchOrder.begin(), searchOrder.end(),
594 [&colorSize](Color_t a, Color_t b) { return colorSize[a] < colorSize[b]; });
595 }
596
597 auto it = searchOrder.begin();
598
599 for (; it != searchOrder.end(); ++it) {
600 bool free = true;
601 if (includeOuterIdx) {
602 for (Index_t k = iOuter; k < grpEnd && free; ++k) {
603 free = !innerInColor[*it][k];
604 }
605 }
606 /*--- Traverse entire group as a large outer index. ---*/
607 for (Index_t k = outerPtr[iOuter]; k < outerPtr[grpEnd] && free; ++k) {
608 free = !innerInColor[*it][innerIdx[k] - minIdx];
609 }
610 /*--- If none of the inner indices in the group appears in
611 * this color yet, it is assigned to the group. ---*/
612 if (free) break;
613 }
614
615 Color_t color;
616
617 if (it != searchOrder.end()) {
618 /*--- Found a free color. ---*/
619 color = *it;
620 } else {
621 /*--- No color was free, make space for a new one. ---*/
622 color = nColor++;
623 if (nColor == MaxColors) return T();
624 colorSize.push_back(0);
625 innerInColor.emplace_back(nInner, false);
626 }
627
628 /*--- Assign color to group. ---*/
629 for (Index_t k = iOuter; k < grpEnd; ++k) idxColor[k] = color;
630
631 /*--- Mark the inner indices of the group as belonging to the color. ---*/
632 for (Index_t k = outerPtr[iOuter]; k < outerPtr[grpEnd]; ++k) {
633 innerInColor[color][innerIdx[k] - minIdx] = true;
634 }
635
636 /*--- Update count for the assigned color. ---*/
637 colorSize[color] += grpEnd - iOuter;
638 }
639 } // matrix of bools goes out of scope
640
641 /*--- Compress the coloring information. ---*/
642
643 su2vector<Index_t> colorPtr(nColor + 1);
644 colorPtr(0) = 0;
645 su2vector<Index_t> outerIdx(nOuter);
646
647 Index_t k = 0;
648 for (Color_t color = 0; color < nColor; ++color) {
649 colorPtr(color + 1) = colorPtr(color) + colorSize[color];
650
651 for (Index_t iOuter = 0; iOuter < nOuter; ++iOuter)
652 if (idxColor[iOuter] == color) outerIdx(k++) = iOuter;
653 }
654
655 /*--- Optional return of the direct color information. ---*/
656 if (indexColor) *indexColor = std::move(idxColor);
657
658 /*--- Move compressed coloring into result pattern instance. ---*/
659 return T(std::move(colorPtr), std::move(outerIdx));
660}
661
662/*!
663 * \brief A way to represent one grid color that allows range-for syntax.
664 */
665template <typename T = su2uint>
666struct GridColor {
667 static_assert(std::is_integral<T>::value);
668
669 const T size;
671 const T* const indices;
672
673 GridColor(const T* idx = nullptr, T sz = 0, T grp = 0) : size(sz), groupSize(grp), indices(idx) {}
674
675 inline const T* begin() const { return indices; }
676 inline const T* end() const { return indices + size; }
677};
678
679/*!
680 * \brief A way to represent natural coloring {0,1,2,...,size-1} with zero
681 * overhead (behaves like looping with an integer index, after optimization...).
682 */
683template <typename T = su2uint>
685 static_assert(std::is_integral<T>::value);
686
688 struct {
689 inline T operator[](T i) const { return i; }
691
692 DummyGridColor(T sz = 0) : size(sz) {}
693
695 T i;
696 inline IteratorLikeInt(T pos = 0) : i(pos) {}
698 ++i;
699 return *this;
700 }
702 auto j = i++;
703 return IteratorLikeInt(j);
704 }
705 inline T operator*() const { return i; }
706 inline T operator->() const { return i; }
707 inline bool operator==(const IteratorLikeInt& other) const { return i == other.i; }
708 inline bool operator!=(const IteratorLikeInt& other) const { return i != other.i; }
709 };
710 inline IteratorLikeInt begin() const { return IteratorLikeInt(0); }
711 inline IteratorLikeInt end() const { return IteratorLikeInt(size); }
712};
713
714/*!
715 * \brief Computes the efficiency of a grid coloring for given number of threads and chunk size.
716 */
717template <class SparsePattern>
718su2double coloringEfficiency(const SparsePattern& coloring, int numThreads, int chunkSize) {
719 using Index_t = typename SparsePattern::IndexType;
720
721 /*--- Ideally compute time is proportional to total work over number of threads. ---*/
722 su2double ideal = coloring.getNumNonZeros() / su2double(numThreads);
723
724 /*--- In practice the total work is quantized first by colors and then by chunks. ---*/
725 Index_t real = 0;
726 for (Index_t color = 0; color < coloring.getOuterSize(); ++color)
727 real += chunkSize * roundUpDiv(roundUpDiv(coloring.getNumNonZeros(color), chunkSize), numThreads);
728
729 return ideal / real;
730}
731
732/*!
733 * \brief Compute the levels for the lower part of a sparse pattern.
734 * For example, corresponding to the dependencies of forward substitution.
735 */
736template <class T>
737T computeLevels(const T& pattern) {
738 using Index = typename T::IndexType;
739
740 std::vector<std::vector<Index>> levels;
741 {
742 const auto n = pattern.getOuterSize();
743 su2vector<int> level(n);
744 for (Index i = 0; i < n; ++i) {
745 level(i) = 0;
746 for (const auto j : pattern.getInnerIter(i)) {
747 if (j >= i) continue;
748 level(i) = std::max(level(i), level(j) + 1);
749 }
750 if (static_cast<std::size_t>(level(i) + 1) > levels.size()) {
751 levels.emplace_back();
752 }
753 levels[level(i)].push_back(i);
754 }
755 }
756 return T(levels);
757}
758
759/// @}
A templated matrix/vector-like object.
Definition C2DContainer.hpp:374
C2DContainer & resize(const Index_t size1, const Index_t size2=1) noexcept
Request a change of size.
Definition C2DContainer.hpp:541
size_t size() const noexcept
Definition C2DContainer.hpp:222
A simple class to store adjacency information in a compressed format suitable for sparse matrix opera...
Definition graph_toolbox.hpp:59
CCompressedSparsePattern(const T &outerPtr, const T &innerIdx)
Construct from vector-like objects of any type with methods "size()" and "data()" (returning a pointe...
Definition graph_toolbox.hpp:122
bool empty() const
Definition graph_toolbox.hpp:184
const Index_t * diagPtr() const
Definition graph_toolbox.hpp:297
Index_t & getInnerIdx(Index_t iOuterIdx, Index_t iNonZero)
Definition graph_toolbox.hpp:217
Index_t getOuterSize() const
Definition graph_toolbox.hpp:189
const Index_t * innerIdx() const
Definition graph_toolbox.hpp:281
Index_t getMaxInnerIdx() const
Definition graph_toolbox.hpp:322
CInnerIter getInnerIter(Index_t iOuterIdx) const
Definition graph_toolbox.hpp:226
CCompressedSparsePattern(su2vector< Index_t > &&outerPtr, su2vector< Index_t > &&innerIdx)
Construct from rvalue refs.
Definition graph_toolbox.hpp:109
Index_t getInnerIdx(Index_t iOuterIdx, Index_t iNonZero) const
Definition graph_toolbox.hpp:207
Index_t findInnerIdx(Index_t iOuterIdx, Index_t iInnerIdx) const
Definition graph_toolbox.hpp:236
Index_t IndexType
Definition graph_toolbox.hpp:69
bool isNonZero(Index_t iOuterIdx, Index_t iInnerIdx) const
Definition graph_toolbox.hpp:247
Index_t getNumNonZeros() const
Definition graph_toolbox.hpp:194
const Index_t * innerIdx(Index_t iOuterIdx) const
Definition graph_toolbox.hpp:289
Index_t getMinInnerIdx() const
Definition graph_toolbox.hpp:313
CCompressedSparsePattern(const T &lil)
Build from a "list of lists" type object.
Definition graph_toolbox.hpp:138
Index_t quickFindInnerIdx(Index_t iOuterIdx, Index_t iInnerIdx) const
Definition graph_toolbox.hpp:257
Index_t getNumNonZeros(Index_t iOuterIdx) const
Definition graph_toolbox.hpp:200
CCompressedSparsePattern()=default
Default construction.
const su2vector< Index_t > & transposePtr() const
Definition graph_toolbox.hpp:305
void buildDiagPtr()
Build a list of pointers to the diagonal entries of the pattern.
Definition graph_toolbox.hpp:152
CCompressedSparsePattern(Iterator outerPtrBegin, Iterator outerPtrEnd, Index_t defaultInnerIdx)
Construct with default inner indices.
Definition graph_toolbox.hpp:94
Index_t getDiagPtr(Index_t iDiagIdx) const
Definition graph_toolbox.hpp:268
void buildTransposePtr()
Build a list of pointers to the transpose entries of the pattern, requires symmetry.
Definition graph_toolbox.hpp:165
const Index_t * outerPtr() const
Definition graph_toolbox.hpp:273
Scalar_t * data() noexcept
Definition C2DContainer.hpp:222
bool empty() const noexcept
Definition C2DContainer.hpp:222
passivedouble su2double
Definition code_config.hpp:181
void buildLUTransposeMaps(const CCompressedSparsePattern< Index_t > &pattern_l, const CCompressedSparsePattern< Index_t > &pattern_u, su2vector< Index_t > &l_to_u, su2vector< Index_t > &u_to_l)
Build bijective maps between strictly-lower (L) and strictly-upper (U) non-zero entries that are each...
Definition graph_toolbox.hpp:483
CCompressedSparsePattern< Index_t > buildUpperPattern(const CCompressedSparsePattern< Index_t > &csr)
Extract the strictly-upper part of a symmetric compressed sparse pattern. For each row i,...
Definition graph_toolbox.hpp:453
ConnectivityType
In FVM points are connected by the edges (faces) of the grid. In FEM, two points are connected if the...
Definition graph_toolbox.hpp:49
CCompressedSparsePattern< Index_t > buildCSRPattern(Geometry_t &geometry, ConnectivityType type, Index_t fillLvl)
Build a sparse pattern from geometry information, of type FVM or FEM, for a given fill-level....
Definition graph_toolbox.hpp:355
T colorSparsePattern(const T &pattern, size_t groupSize=1, bool includeOuterIdx=false, bool balanceColors=false, std::vector< Color_t > *indexColor=nullptr)
Color contiguous groups of outer indices of a sparse pattern such that within each color,...
Definition graph_toolbox.hpp:548
T computeLevels(const T &pattern)
Compute the levels for the lower part of a sparse pattern. For example, corresponding to the dependen...
Definition graph_toolbox.hpp:737
T createNaturalColoring(Index_t numInnerIndexes)
Create the natural coloring (equivalent to the normal sequential loop order) for a given number of in...
Definition graph_toolbox.hpp:511
su2double coloringEfficiency(const SparsePattern &coloring, int numThreads, int chunkSize)
Computes the efficiency of a grid coloring for given number of threads and chunk size.
Definition graph_toolbox.hpp:718
CCompressedSparsePattern< Index_t > buildLowerPattern(const CCompressedSparsePattern< Index_t > &csr)
Extract the strictly-lower part of a symmetric compressed sparse pattern. For each row i,...
Definition graph_toolbox.hpp:427
#define END_SU2_OMP_PARALLEL
Definition omp_structure.hpp:157
constexpr size_t roundUpDiv(size_t numerator, size_t denominator)
Integer division rounding up.
Definition omp_structure.hpp:204
constexpr int omp_get_max_threads()
Maximum number of threads available.
Definition omp_structure.hpp:70
#define SU2_OMP_PARALLEL_(ARGS)
Definition omp_structure.hpp:146
Type to allow range for loops over inner indices.
Definition graph_toolbox.hpp:74
const IndexType * begin() const
Definition graph_toolbox.hpp:78
CInnerIter(const IndexType *first, const IndexType *last)
Definition graph_toolbox.hpp:77
const IndexType * end() const
Definition graph_toolbox.hpp:79
const IndexType *const m_first
Definition graph_toolbox.hpp:75
const IndexType *const m_last
Definition graph_toolbox.hpp:76
Definition graph_toolbox.hpp:694
T i
Definition graph_toolbox.hpp:695
T operator->() const
Definition graph_toolbox.hpp:706
IteratorLikeInt(T pos=0)
Definition graph_toolbox.hpp:696
IteratorLikeInt & operator++()
Definition graph_toolbox.hpp:697
bool operator==(const IteratorLikeInt &other) const
Definition graph_toolbox.hpp:707
bool operator!=(const IteratorLikeInt &other) const
Definition graph_toolbox.hpp:708
IteratorLikeInt operator++(int)
Definition graph_toolbox.hpp:701
T operator*() const
Definition graph_toolbox.hpp:705
A way to represent natural coloring {0,1,2,...,size-1} with zero overhead (behaves like looping with ...
Definition graph_toolbox.hpp:684
struct DummyGridColor::@83 indices
IteratorLikeInt end() const
Definition graph_toolbox.hpp:711
T size
Definition graph_toolbox.hpp:687
DummyGridColor(T sz=0)
Definition graph_toolbox.hpp:692
IteratorLikeInt begin() const
Definition graph_toolbox.hpp:710
A way to represent one grid color that allows range-for syntax.
Definition graph_toolbox.hpp:666
T groupSize
Definition graph_toolbox.hpp:670
const T size
Definition graph_toolbox.hpp:669
const T * end() const
Definition graph_toolbox.hpp:676
const T *const indices
Definition graph_toolbox.hpp:671
GridColor(const T *idx=nullptr, T sz=0, T grp=0)
Definition graph_toolbox.hpp:673
const T * begin() const
Definition graph_toolbox.hpp:675