opm-common
CSRGraphFromCoordinates_impl.hpp
1 /*
2  Copyright 2016 SINTEF ICT, Applied Mathematics.
3  Copyright 2016 Statoil ASA.
4  Copyright 2022 Equinor ASA
5 
6  This file is part of the Open Porous Media Project (OPM).
7 
8  OPM is free software: you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation, either version 3 of the License, or
11  (at your option) any later version.
12 
13  OPM is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  GNU General Public License for more details.
17 
18  You should have received a copy of the GNU General Public License
19  along with OPM. If not, see <http://www.gnu.org/licenses/>.
20 */
21 
22 #include <algorithm>
23 #include <cassert>
24 #include <cstddef>
25 #include <exception>
26 #include <iterator>
27 #include <optional>
28 #include <set>
29 #include <stdexcept>
30 #include <string>
31 #include <unordered_map>
32 #include <utility>
33 #include <vector>
34 
35 // ---------------------------------------------------------------------
36 // Class Opm::utility::CSRGraphFromCoordinates::Connections
37 // ---------------------------------------------------------------------
38 
39 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
40 void
42 Connections::add(const VertexID v1, const VertexID v2)
43 {
44  this->i_.push_back(v1);
45  this->j_.push_back(v2);
46 
47  this->max_i_ = std::max(this->max_i_.value_or(BaseVertexID{}), this->i_.back());
48  this->max_j_ = std::max(this->max_j_.value_or(BaseVertexID{}), this->j_.back());
49 }
50 
51 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
52 void
54 Connections::add(VertexID maxRowIdx,
55  VertexID maxColIdx,
56  const Neighbours& rows,
57  const Neighbours& cols)
58 {
59  if (cols.size() != rows.size()) {
60  throw std::invalid_argument {
61  "Coordinate format column index table size does not match "
62  "row index table size"
63  };
64  }
65 
66  this->i_.insert(this->i_.end(), rows .begin(), rows .end());
67  this->j_.insert(this->j_.end(), cols .begin(), cols .end());
68 
69  this->max_i_ = std::max(this->max_i_.value_or(BaseVertexID{}), maxRowIdx);
70  this->max_j_ = std::max(this->max_j_.value_or(BaseVertexID{}), maxColIdx);
71 }
72 
73 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
74 void
77 {
78  this->j_.clear();
79  this->i_.clear();
80 
81  this->max_i_.reset();
82  this->max_j_.reset();
83 }
84 
85 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
86 bool
88 Connections::empty() const
89 {
90  return this->i_.empty();
91 }
92 
93 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
94 bool
97 {
98  return this->i_.size() == this->j_.size();
99 }
100 
101 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
102 std::optional<typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::BaseVertexID>
104 Connections::maxRow() const
105 {
106  return this->max_i_;
107 }
108 
109 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
110 std::optional<typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::BaseVertexID>
112 Connections::maxCol() const
113 {
114  return this->max_j_;
115 }
116 
117 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
121 {
122  return this->i_.size();
123 }
124 
125 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
129 {
130  return this->i_;
131 }
132 
133 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
137 {
138  return this->j_;
139 }
140 
141 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
142 VertexID
144 Connections::findMergedVertexID(VertexID v, const std::unordered_map<VertexID, VertexID>& vertex_merges) const
145 {
146  // If found in the map, return the merged vertex ID
147  // Otherwise, return the original vertex ID unchanged
148  // vertex_merges is fully resolved from our disjoint set union structure
149  auto it = vertex_merges.find(v);
150  if (it != vertex_merges.end()) {
151  return it->second;
152  } else {
153  return v;
154  }
155 }
156 
157 
158 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
159 std::unordered_map<VertexID, VertexID>
161 Connections::applyVertexMerges(const std::unordered_map<VertexID, VertexID>& vertex_merges)
162 {
163 
164  // Find the maximum vertex ID in the original connections
165  VertexID max_original_vertex_id = std::max(max_i_.value_or(BaseVertexID{}), max_j_.value_or(BaseVertexID{}));
166 
167  // Apply merges to all connections directly in one pass
168  // We can leverage that vertex_merges is already fully resolved from our disjoint set union
169  // structure, so we don't need nested lookups
170  std::ranges::transform(i_, i_.begin(),
171  [this, &vertex_merges](VertexID v) { return findMergedVertexID(v, vertex_merges); });
172 
173  std::ranges::transform(j_, j_.begin(),
174  [this, &vertex_merges](VertexID v) { return findMergedVertexID(v, vertex_merges); });
175 
176  // Remove self-connections if required using erase-remove idiom
177  if constexpr (!PermitSelfConnections) {
178  auto write_pos = 0*i_.size();
179  for (auto read_pos = 0*i_.size(); read_pos < i_.size(); ++read_pos) {
180  if (i_[read_pos] != j_[read_pos]) {
181  if (write_pos != read_pos) {
182  i_[write_pos] = i_[read_pos];
183  j_[write_pos] = j_[read_pos];
184  }
185  ++write_pos;
186  }
187  }
188  i_.resize(write_pos);
189  j_.resize(write_pos);
190  }
191 
192  // Create compact vertex numbering
193  std::set<VertexID> sorted_unique_vertices;
194  sorted_unique_vertices.insert(i_.begin(), i_.end());
195  sorted_unique_vertices.insert(j_.begin(), j_.end());
196 
197  // Generate direct mapping from old to compact vertex IDs
198  std::unordered_map<VertexID, VertexID> vertex_map;
199  vertex_map.reserve(sorted_unique_vertices.size());
200  VertexID new_id = 0;
201  for (auto& v : sorted_unique_vertices) {
202  vertex_map.emplace(v, new_id++);
203  }
204 
205  // Update max indices
206  this->max_i_ = this->max_j_ = new_id - 1;
207 
208  // Remap all vertices to compact IDs
209  auto remap = [&vertex_map](auto v) {
210  // Using at() is appropriate here since we know all values from i_ and j_ are in vertex_map
211  return vertex_map.at(v);
212  };
213 
214  std::ranges::transform(i_, i_.begin(), remap);
215  std::ranges::transform(j_, j_.begin(), remap);
216 
217  // Build final mapping (original -> compact ID)
218  std::unordered_map<VertexID, VertexID> final_mapping;
219  final_mapping.reserve(max_original_vertex_id + 1);
220 
221  for (VertexID vertex = 0; vertex <= max_original_vertex_id; ++vertex) {
222  auto merged_id = findMergedVertexID(vertex, vertex_merges);
223  final_mapping.emplace(vertex, vertex_map.at(merged_id));
224  }
225  return final_mapping;
226 }
227 
228 // =====================================================================
229 
230 // ---------------------------------------------------------------------
231 // Class Opm::utility::CSRGraphFromCoordinates::CSR
232 // ---------------------------------------------------------------------
233 
234 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
235 void
237 CSR::merge(const Connections& conns,
238  const Offset maxNumVertices,
239  const bool expandExistingIdxMap)
240 {
241  const auto maxRow = conns.maxRow();
242 
243  if (maxRow.has_value() &&
244  (static_cast<Offset>(*maxRow) >= maxNumVertices))
245  {
246  throw std::invalid_argument {
247  "Number of vertices in input graph (" +
248  std::to_string(*maxRow) + ") "
249  "exceeds maximum graph size implied by explicit size of "
250  "adjacency matrix (" + std::to_string(maxNumVertices) + ')'
251  };
252  }
253 
254  this->assemble(conns.rowIndices(), conns.columnIndices(),
255  maxRow.value_or(BaseVertexID{0}),
256  conns.maxCol().value_or(BaseVertexID{0}),
257  expandExistingIdxMap);
258 
259  this->compress(maxNumVertices);
260 }
261 
262 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
265 CSR::numRows() const
266 {
267  return this->startPointers().empty()
268  ? 0 : this->startPointers().size() - 1;
269 }
270 
271 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
272 typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::BaseVertexID
274 CSR::maxRowID() const
275 {
276  return this->numRows_ - 1;
277 }
278 
279 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
280 typename Opm::utility::CSRGraphFromCoordinates<VertexID, TrackCompressedIdx, PermitSelfConnections>::BaseVertexID
282 CSR::maxColID() const
283 {
284  return this->numCols_ - 1;
285 }
286 
287 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
290 CSR::startPointers() const
291 {
292  return this->ia_;
293 }
294 
295 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
298 CSR::columnIndices() const
299 {
300  return this->ja_;
301 }
302 
303 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
307 {
308  auto rowIdx = Neighbours{};
309 
310  if (this->ia_.empty()) {
311  return rowIdx;
312  }
313 
314  rowIdx.reserve(this->ia_.back());
315 
316  auto row = BaseVertexID{};
317 
318  const auto m = this->ia_.size() - 1;
319  for (auto i = 0*m; i < m; ++i, ++row) {
320  const auto n = this->ia_[i + 1] - this->ia_[i + 0];
321 
322  rowIdx.insert(rowIdx.end(), n, row);
323  }
324 
325  return rowIdx;
326 }
327 
328 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
329 void
331 CSR::clear()
332 {
333  this->ia_.clear();
334  this->ja_.clear();
335 
336  if constexpr (TrackCompressedIdx) {
337  this->compressedIdx_.clear();
338  }
339 
340  this->numRows_ = 0;
341  this->numCols_ = 0;
342 }
343 
344 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
345 void
347 CSR::assemble(const Neighbours& rows,
348  const Neighbours& cols,
349  const BaseVertexID maxRowIdx,
350  const BaseVertexID maxColIdx,
351  [[maybe_unused]] const bool expandExistingIdxMap)
352 {
353  [[maybe_unused]] auto compressedIdx = this->compressedIdx_;
354  [[maybe_unused]] const auto numOrigNNZ = this->ja_.size();
355 
356  auto i = this->coordinateFormatRowIndices();
357  i.insert(i.end(), rows.begin(), rows.end());
358 
359  auto j = this->ja_;
360  j.insert(j.end(), cols.begin(), cols.end());
361 
362  // Use the number of unique vertices as the size
363  const auto thisNumRows = std::max(this->numRows_, maxRowIdx + 1);
364  const auto thisNumCols = std::max(this->numCols_, maxColIdx + 1);
365 
366  this->preparePushbackRowGrouping(thisNumRows, i);
367 
368  this->groupAndTrackColumnIndicesByRow(i, j);
369 
370  if constexpr (TrackCompressedIdx) {
371  if (expandExistingIdxMap) {
372  this->remapCompressedIndex(std::move(compressedIdx), numOrigNNZ);
373  }
374  }
375 
376  this->numRows_ = thisNumRows;
377  this->numCols_ = thisNumCols;
378 }
379 
380 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
381 void
383 CSR::compress(const Offset maxNumVertices)
384 {
385  if (this->numRows() > maxNumVertices) {
386  throw std::invalid_argument {
387  "Number of vertices in input graph (" +
388  std::to_string(this->numRows()) + ") "
389  "exceeds maximum graph size implied by explicit size of "
390  "adjacency matrix (" + std::to_string(maxNumVertices) + ')'
391  };
392  }
393 
394  this->sortColumnIndicesPerRow();
395 
396  // Must be called *after* sortColumnIndicesPerRow().
397  this->condenseDuplicates();
398 
399  const auto nRows = this->startPointers().size() - 1;
400  if (nRows < maxNumVertices) {
401  this->ia_.insert(this->ia_.end(),
402  maxNumVertices - nRows,
403  this->startPointers().back());
404  }
405 }
406 
407 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
408 void
411 {
412  // Transposition is, in this context, effectively a linear time (O(nnz))
413  // bucket insertion procedure. In other words transposing the structure
414  // twice creates a structure with column indices in (ascendingly) sorted
415  // order.
416 
417  this->transpose();
418  this->transpose();
419 }
420 
421 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
422 void
425 {
426  // Note: Must be called *after* sortColumnIndicesPerRow().
427 
428  const auto colIdx = this->ja_;
429  auto end = colIdx.begin();
430 
431  this->ja_.clear();
432 
433  [[maybe_unused]] auto compressedIdx = this->compressedIdx_;
434  if constexpr (TrackCompressedIdx) {
435  this->compressedIdx_.clear();
436  }
437 
438  const auto numRows = this->ia_.size() - 1;
439  for (auto row = 0*numRows; row < numRows; ++row) {
440  auto begin = end;
441 
442  std::advance(end, this->ia_[row + 1] - this->ia_[row + 0]);
443 
444  const auto q = this->ja_.size();
445 
446  this->condenseAndTrackUniqueColumnsForSingleRow(begin, end);
447 
448  this->ia_[row + 0] = q;
449  }
450 
451  if constexpr (TrackCompressedIdx) {
452  this->remapCompressedIndex(std::move(compressedIdx));
453  }
454 
455  // Record final table sizes.
456  this->ia_.back() = this->ja_.size();
457 }
458 
459 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
460 void
462 CSR::preparePushbackRowGrouping(const int numRows,
463  const Neighbours& rowIdx)
464 {
465  assert (numRows >= 0);
466 
467  this->ia_.assign(numRows + 1, 0);
468 
469  // Count number of neighbouring vertices for each row. Accumulate in
470  // "next" bin since we're positioning the end pointers.
471  for (const auto& row : rowIdx) {
472  this->ia_[row + 1] += 1;
473  }
474 
475  // Position "end" pointers.
476  //
477  // After this loop, ia_[i + 1] points to the *start* of the range of the
478  // column indices/neighbouring vertices of vertex 'i'. This, in turn,
479  // enables using the statement ja_[ia_[i+1]++] = v in groupAndTrack()
480  // to insert vertex 'v' as a neighbour, at the end of the range of known
481  // neighbours, *and* advance the end pointer of row/vertex 'i'. We use
482  // ia_[0] as an accumulator for the total number of neighbouring
483  // vertices in the graph.
484  //
485  // Note index range: 1..numRows inclusive.
486  for (typename Start::size_type i = 1, n = numRows; i <= n; ++i) {
487  this->ia_[0] += this->ia_[i];
488  this->ia_[i] = this->ia_[0] - this->ia_[i];
489  }
490 
491  assert (this->ia_[0] == rowIdx.size());
492 }
493 
494 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
495 void
497 CSR::groupAndTrackColumnIndicesByRow(const Neighbours& rowIdx,
498  const Neighbours& colIdx)
499 {
500  assert (this->ia_[0] == rowIdx.size());
501 
502  const auto nnz = rowIdx.size();
503 
504  this->ja_.resize(nnz);
505 
506  if constexpr (TrackCompressedIdx) {
507  this->compressedIdx_.clear();
508  this->compressedIdx_.reserve(nnz);
509  }
510 
511  // Group/insert column indices according to their associate vertex/row
512  // index.
513  //
514  // At the start of the loop the end pointers ia_[i+1], formed in
515  // preparePushback(), are positioned at the *start* of the column index
516  // range associated to vertex 'i'. After this loop all vertices
517  // neighbouring vertex 'i' will be placed consecutively, in order of
518  // appearance, into ja_. Furthermore, the row pointers ia_ will have
519  // their final position.
520  //
521  // The statement ja_[ia_[i+1]++] = v, split into two statements using
522  // the helper object 'k', inserts 'v' as a neighbouring vertex of vertex
523  // 'i' *and* advances the end pointer ia_[i+1] of that vertex. We use
524  // and maintain the invariant that ia_[i+1] at all times records the
525  // insertion point of the next neighbouring vertex of vertex 'i'. When
526  // the list of neighbouring vertices for vertex 'i' has been exhausted,
527  // ia_[i+1] will hold the start position for in ja_ for vertex i+1.
528  for (auto nz = 0*nnz; nz < nnz; ++nz) {
529  const auto k = this->ia_[rowIdx[nz] + 1] ++;
530 
531  this->ja_[k] = colIdx[nz];
532 
533  if constexpr (TrackCompressedIdx) {
534  this->compressedIdx_.push_back(k);
535  }
536  }
537 
538  this->ia_[0] = 0;
539 }
540 
541 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
542 void
545 {
546  [[maybe_unused]] auto compressedIdx = this->compressedIdx_;
547 
548  {
549  const auto rowIdx = this->coordinateFormatRowIndices();
550  const auto colIdx = this->ja_;
551 
552  this->preparePushbackRowGrouping(this->numCols_, colIdx);
553 
554  // Note parameter order. Transposition switches role of rows and
555  // columns.
556  this->groupAndTrackColumnIndicesByRow(colIdx, rowIdx);
557  }
558 
559  if constexpr (TrackCompressedIdx) {
560  this->remapCompressedIndex(std::move(compressedIdx));
561  }
562 
563  std::swap(this->numRows_, this->numCols_);
564 }
565 
566 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
567 void
569 CSR::condenseAndTrackUniqueColumnsForSingleRow(typename Neighbours::const_iterator begin,
570  typename Neighbours::const_iterator end)
571 {
572  // We assume that we're only called *after* sortColumnIndicesPerRow()
573  // whence duplicate elements appear consecutively in [begin, end).
574  //
575  // Note: This is essentially the same as std::unique(begin, end) save
576  // for the return value and the fact that we additionally record the
577  // 'compressedIdx_' mapping. That mapping enables subsequent, decoupled
578  // accumulation of the 'sa_' contributions.
579 
580  while (begin != end) {
581  // Note: Order of ja_ and compressedIdx_ matters here.
582 
583  if constexpr (TrackCompressedIdx) {
584  this->compressedIdx_.push_back(this->ja_.size());
585  }
586 
587  this->ja_.push_back(*begin);
588 
589  auto next_unique =
590  std::find_if(begin, end, [last = this->ja_.back()]
591  (const auto j) { return j != last; });
592 
593  if constexpr (TrackCompressedIdx) {
594  // Number of duplicate elements in [begin, next_unique).
595  const auto ndup = std::distance(begin, next_unique);
596 
597  if (ndup > 1) {
598  // Insert ndup - 1 copies of .back() to represent the
599  // duplicate pairs in [begin, next_unique). We subtract one
600  // to account for .push_back() above representing *begin.
601  this->compressedIdx_.insert(this->compressedIdx_.end(),
602  ndup - 1,
603  this->compressedIdx_.back());
604  }
605  }
606 
607  begin = next_unique;
608  }
609 }
610 
611 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
612 void
614 remapCompressedIndex([[maybe_unused]] Start&& compressedIdx,
615  [[maybe_unused]] std::optional<typename Start::size_type> numOrig)
616 {
617  if constexpr (TrackCompressedIdx) {
618  std::ranges::transform(compressedIdx, compressedIdx.begin(),
619  [this](const auto& i)
620  { return this->compressedIdx_[i]; });
621 
622  if (numOrig.has_value() && (*numOrig < this->compressedIdx_.size())) {
623  // Client called add() after compress(). Remap existing portion
624  // of compressedIdx (above), and append new entries (here).
625  compressedIdx
626  .insert(compressedIdx.end(),
627  this->compressedIdx_.begin() + *numOrig,
628  this->compressedIdx_.end());
629  }
630 
631  this->compressedIdx_.swap(compressedIdx);
632  }
633 }
634 
635 // =====================================================================
636 
637 // ---------------------------------------------------------------------
638 // Class Opm::utility::CSRGraphFromCoordinates
639 // ---------------------------------------------------------------------
640 
641 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
643 {
644  this->uncompressed_.clear();
645  this->csr_.clear();
646 }
647 
648 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
649 void
651 addConnection(const VertexID v1, const VertexID v2)
652 {
653  if ((v1 < 0) || (v2 < 0)) {
654  throw std::invalid_argument {
655  "Vertex IDs must be non-negative. Got (v1,v2) = ("
656  + std::to_string(v1) + ", " + std::to_string(v2)
657  + ')'
658  };
659  }
660 
661  if constexpr (! PermitSelfConnections) {
662  if (v1 == v2) {
663  // Ignore self connections.
664  return;
665  }
666  }
667 
668  this->uncompressed_.add(v1, v2);
669 }
670 
671 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
672 void
674 addVertexGroup(const std::vector<VertexID>& vertices)
675 {
676  if (vertices.empty()) {
677  return;
678  }
679  // Initialize any new vertices in the disjoint set union structure
680  for (const auto& v : vertices) {
681  if (parent_.find(v) == parent_.end()) {
682  parent_.emplace(v, v); // Each vertex initially points to itself
683  }
684  }
685 
686  // Union all vertices in the group
687  if (vertices.size() > 1) {
688  for (std::size_t i = 1; i < vertices.size(); ++i) {
689  unionSets(vertices[0], vertices[i]);
690  }
691  }
692 }
693 
694 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
695 VertexID
697 find(VertexID v)
698 {
699  // If vertex is not in the structure, add it as its own parent
700  auto it = parent_.find(v);
701  if (it == parent_.end()) {
702  parent_.emplace(v, v);
703  return v;
704  }
705 
706  // Path compression: update parent pointers to point directly to the root
707  if (it->second != v) {
708  it->second = find(it->second);
709  }
710  return it->second;
711 }
712 
713 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
714 void
716 unionSets(VertexID a, VertexID b)
717 {
718  // Find representatives (roots) of both sets
719  VertexID rootA = find(a);
720  VertexID rootB = find(b);
721 
722  // If already in same set, do nothing
723  if (rootA == rootB) {
724  return;
725  }
726 
727  // Union by rank: we'll use the smaller vertex ID as the root
728  // (This is a simple heuristic that works well for this use case)
729  if (rootA < rootB) {
730  parent_.at(rootB) = rootA;
731  } else {
732  parent_.at(rootA) = rootB;
733  }
734 }
735 
736 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
740 {
741  // If no vertex groups were defined, nothing to do
742  if (parent_.empty()) {
743  return this->uncompressed_.maxRow().value_or(0) + 1;
744  }
745 
746  // Create the direct mapping for merges (original → target)
747  std::unordered_map<VertexID, VertexID> vertex_merges;
748  for (auto& [vertex, parent] : parent_) {
749  // Find the final root for this vertex which is the min vertex ID in the set
750  VertexID root = find(vertex);
751 
752  // Only add to merges if the vertex isn't its own root
753  if (vertex != root) {
754  vertex_merges.emplace(vertex, root);
755  }
756  }
757  // Apply the merges to the uncompressed data
758  if (!vertex_merges.empty()) {
759  vertex_mapping_ = this->uncompressed_.applyVertexMerges(vertex_merges);
760  }
761 
762  return this->uncompressed_.maxRow().value_or(0) + 1;
763 }
764 
765 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
766 void
768 compress(Offset maxNumVertices, bool expandExistingIdxMap)
769 {
770  // Apply vertex merges if there are vertex groups but merges haven't been applied yet
771  if (!parent_.empty() && vertex_mapping_.empty()) {
772  applyVertexMerges();
773  }
774 
775  if (! this->uncompressed_.isValid()) {
776  throw std::logic_error {
777  "Cannot compress invalid connection list"
778  };
779  }
780 
781  this->csr_.merge(this->uncompressed_, maxNumVertices, expandExistingIdxMap);
782 
783  this->uncompressed_.clear();
784 }
785 
786 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
787 VertexID
789 getFinalVertexID(VertexID v) const
790 {
791  if (vertex_mapping_.empty()) {
792  return v;
793  }
794  else {
795  return vertex_mapping_.at(v);
796  }
797 }
798 
799 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
802 {
803  return this->csr_.numRows();
804 }
805 
806 template <typename VertexID, bool TrackCompressedIdx, bool PermitSelfConnections>
809 {
810  const auto& ia = this->startPointers();
811 
812  return ia.empty() ? 0 : ia.back();
813 }
Offset numEdges() const
Retrieve number of edges (non-zero matrix elements) in input graph.
Definition: CSRGraphFromCoordinates_impl.hpp:808
VertexID getFinalVertexID(VertexID originalVertexID) const
Get the final vertex ID after all merges and renumbering for a given original vertex ID...
Definition: CSRGraphFromCoordinates_impl.hpp:789
std::vector< BaseVertexID > Neighbours
Representation of neighbouring regions.
Definition: CSRGraphFromCoordinates.hpp:65
Offset numVertices() const
Retrieve number of rows (source entities) in input graph.
Definition: CSRGraphFromCoordinates_impl.hpp:801
typename Neighbours::size_type Offset
Offset into neighbour array.
Definition: CSRGraphFromCoordinates.hpp:68
Offset applyVertexMerges()
Apply vertex merges to all vertex groups.
Definition: CSRGraphFromCoordinates_impl.hpp:739
void addVertexGroup(const std::vector< VertexID > &vertices)
Add a group of vertices that should be merged together.
Definition: CSRGraphFromCoordinates_impl.hpp:674
void addConnection(VertexID v1, VertexID v2)
Add flow rate connection between regions.
Definition: CSRGraphFromCoordinates_impl.hpp:651
void compress(Offset maxNumVertices, bool expandExistingIdxMap=false)
Form CSR adjacency matrix representation of input graph from connections established in previous call...
Definition: CSRGraphFromCoordinates_impl.hpp:768
Form CSR adjacency matrix representation of unstructured graphs.
Definition: CSRGraphFromCoordinates.hpp:55
void clear()
Clear all internal buffers, but preserve allocated capacity.
Definition: CSRGraphFromCoordinates_impl.hpp:642
std::vector< Offset > Start
CSR start pointers.
Definition: CSRGraphFromCoordinates.hpp:71