Skip to content

Commit 36cda5a

Browse files
committed
Optimise connectWells and connectNeighbors for NLDD partitioning
1 parent b06737c commit 36cda5a

File tree

1 file changed

+66
-108
lines changed

1 file changed

+66
-108
lines changed

opm/simulators/flow/partitionCells.cpp

Lines changed: 66 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -109,42 +109,24 @@ class ZoltanPartitioner
109109
///
110110
/// \param[in] zoltan_ctrl Control parameters for on-rank subdomain
111111
/// partitioning.
112+
///
113+
/// \param[in] num_neighbor_levels Number of neighbor levels to include when connecting well cells.
114+
/// Default is 0, which means only direct well connections are considered.
112115
template <class GridView, class Element>
113116
void buildLocalGraph(const GridView& grid_view,
114117
const std::vector<Opm::Well>& wells,
115118
const std::unordered_map<std::string, std::set<int>>& possibleFutureConnections,
116119
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl,
117120
const int num_neighbor_levels);
118121

119-
/// Find all neighbors of a given cell.
120-
///
121-
/// \tparam GridView DUNE grid view type
122-
///
123-
/// \param[in] grid_view Current rank's reachable cells.
124-
/// \param[in] cell_index Index of the cell to find neighbors for.
125-
/// \param[in] zoltan_ctrl Control parameters for on-rank subdomain partitioning.
126-
///
127-
/// \return Set of neighbor cell indices.
128-
template <class GridView, class Element>
129-
std::set<int> findNeighbors(const GridView& grid_view,
130-
const int cell_index,
131-
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl) const;
132-
133122
/// Connect neighbors of cells up to a specified level.
134123
///
135-
/// \tparam GridView DUNE grid view type
124+
/// \param[in,out] cells Initial cells to find neighbors of. Updated to include all neighbors
125+
/// up to specified level.
136126
///
137-
/// \param[in] grid_view Current rank's reachable cells.
138-
/// \param[in] cells Initial set of cells.
139127
/// \param[in] num_levels Number of neighbor levels to include.
140-
/// \param[in] zoltan_ctrl Control parameters for on-rank subdomain partitioning.
141-
///
142-
/// \return Set of all connected cells including neighbors up to specified level.
143-
template <class GridView, class Element>
144-
std::set<int> connectNeighbors(const GridView& grid_view,
145-
const std::set<int>& cells,
146-
const int num_levels,
147-
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl) const;
128+
void connectNeighbors(std::vector<int>& cells,
129+
const int num_levels) const;
148130

149131
/// Partition rank's interior cells into non-overlapping domains using
150132
/// the Zoltan graph partitioning software package.
@@ -179,6 +161,9 @@ class ZoltanPartitioner
179161
/// Zoltan partitioning backend.
180162
Opm::ParallelNLDDPartitioningZoltan partitioner_;
181163

164+
/// Map of cell indices to their neighbors for fast lookup
165+
std::unordered_map<int, std::unordered_set<int>> neighbor_map_;
166+
182167
/// Form internal connectivity graph between rank's reachable, interior
183168
/// cells.
184169
///
@@ -197,7 +182,8 @@ class ZoltanPartitioner
197182
template <class GridView, class Element>
198183
std::unordered_map<int, int>
199184
connectElements(const GridView& grid_view,
200-
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl);
185+
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl,
186+
const bool build_neighbor_map);
201187

202188
/// Adapt internal connectivity graph to account for connections induced
203189
/// by well reservoir connections.
@@ -216,13 +202,11 @@ class ZoltanPartitioner
216202
///
217203
/// \param[in] num_neighbor_levels Number of neighbor levels to include when connecting well cells.
218204
/// Default is 0, which means only direct well connections are considered.
219-
template <typename Comm, class GridView, class Element>
205+
template <typename Comm>
220206
void connectWells(const Comm comm,
221-
const GridView& grid_view,
222207
const std::vector<Opm::Well>& wells,
223208
std::unordered_map<std::string, std::set<int>> possibleFutureConnections,
224209
const std::unordered_map<int, int>& g2l,
225-
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl,
226210
const int num_neighbor_levels);
227211
};
228212

@@ -243,74 +227,40 @@ void ZoltanPartitioner::buildLocalGraph(const GridView&
243227
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl,
244228
const int num_neighbor_levels)
245229
{
246-
this->connectWells(grid_view.comm(), grid_view, wells, possibleFutureConnections,
247-
this->connectElements(grid_view, zoltan_ctrl), zoltan_ctrl, num_neighbor_levels);
230+
const bool build_neighbor_map = num_neighbor_levels > 0;
231+
auto g2l = this->connectElements(grid_view, zoltan_ctrl, build_neighbor_map);
232+
this->connectWells(grid_view.comm(), wells, possibleFutureConnections, g2l, num_neighbor_levels);
248233
}
249234

250-
template <class GridView, class Element>
251-
std::set<int> ZoltanPartitioner::findNeighbors(const GridView& grid_view,
252-
const int cell_index,
253-
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl) const
235+
void ZoltanPartitioner::connectNeighbors(std::vector<int>& cells,
236+
const int num_levels) const
254237
{
255-
std::set<int> neighbors;
256-
257-
for (const auto& element : elements(grid_view, Dune::Partitions::interior)) {
258-
if (zoltan_ctrl.index(element) != cell_index) {
259-
continue;
260-
}
261-
262-
for (const auto& is : intersections(grid_view, element)) {
263-
if (!is.neighbor()) {
264-
continue;
265-
}
266-
267-
const auto& out = is.outside();
268-
if (out.partitionType() != Dune::InteriorEntity) {
269-
continue;
238+
// Initialize search structures
239+
std::unordered_set<int> visited(cells.begin(), cells.end());
240+
std::vector<int> frontier;
241+
std::vector<int> new_frontier;
242+
frontier.swap(cells);
243+
244+
// Breadth-first search for neighbor cells up to num_levels away
245+
for (int level = 0; level < num_levels && !frontier.empty(); ++level) {
246+
new_frontier.clear();
247+
new_frontier.reserve(frontier.size() * 2);
248+
249+
// Expand current frontier using precomputed neighbor map
250+
for (const int cell : frontier) {
251+
for (const int neighbor : neighbor_map_.at(cell)) {
252+
if (visited.insert(neighbor).second) { // New discovery
253+
new_frontier.push_back(neighbor);
254+
}
270255
}
271-
272-
neighbors.insert(zoltan_ctrl.index(out));
273256
}
274-
break;
275-
}
276257

277-
return neighbors;
278-
}
279-
280-
template <class GridView, class Element>
281-
std::set<int> ZoltanPartitioner::connectNeighbors(const GridView& grid_view,
282-
const std::set<int>& cells,
283-
const int num_levels,
284-
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl) const
285-
{
286-
if (num_levels <= 0) {
287-
return cells;
258+
frontier.swap(new_frontier); // Prepare next level
288259
}
289-
std::set<int> all_cells = cells;
290-
std::set<int> frontier = cells;
291-
292-
for (int level = 0; level < num_levels; ++level) {
293-
std::set<int> new_frontier;
294-
295-
for (const int cell : frontier) {
296-
auto neighbors = findNeighbors(grid_view, cell, zoltan_ctrl);
297-
new_frontier.insert(neighbors.begin(), neighbors.end());
298-
}
299-
300-
// Remove cells we've already processed
301-
for (const int cell : all_cells) {
302-
new_frontier.erase(cell);
303-
}
304-
305-
// If no new cells found, we can stop
306-
if (new_frontier.empty()) {
307-
break;
308-
}
309260

310-
all_cells.insert(new_frontier.begin(), new_frontier.end());
311-
frontier = std::move(new_frontier);
312-
}
313-
return all_cells;
261+
// Sort final result for faster processing in later steps
262+
cells.assign(visited.begin(), visited.end());
263+
std::sort(cells.begin(), cells.end());
314264
}
315265

316266
template <class GridView, class Element>
@@ -344,16 +294,24 @@ ZoltanPartitioner::partition(const int num_
344294
template <class GridView, class Element>
345295
std::unordered_map<int, int>
346296
ZoltanPartitioner::connectElements(const GridView& grid_view,
347-
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl)
297+
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl,
298+
const bool build_neighbor_map)
348299
{
349300
auto g2l = std::unordered_map<int, int>{};
301+
if (build_neighbor_map) {
302+
neighbor_map_.clear();
303+
}
350304

351305
for (const auto& element : elements(grid_view, Dune::Partitions::interior)) {
352306
{
353307
const auto c = zoltan_ctrl.index(element);
354308
g2l.insert_or_assign(zoltan_ctrl.local_to_global(c), c);
355-
}
356309

310+
if (build_neighbor_map) {
311+
// Initialize neighbor set for this cell
312+
neighbor_map_[c];
313+
}
314+
}
357315
for (const auto& is : intersections(grid_view, element)) {
358316
if (! is.neighbor()) {
359317
continue;
@@ -374,19 +332,23 @@ ZoltanPartitioner::connectElements(const GridView&
374332

375333
this->partitioner_.registerConnection(c1, c2);
376334
this->partitioner_.registerConnection(c2, c1);
335+
336+
if (build_neighbor_map) {
337+
// Store neighbors in both directions
338+
neighbor_map_[c1].insert(c2);
339+
neighbor_map_[c2].insert(c1);
340+
}
377341
}
378342
}
379343

380344
return g2l;
381345
}
382346

383-
template <typename Comm, class GridView, class Element>
347+
template <typename Comm>
384348
void ZoltanPartitioner::connectWells(const Comm comm,
385-
const GridView& grid_view,
386349
const std::vector<Opm::Well>& wells,
387350
std::unordered_map<std::string, std::set<int>> possibleFutureConnections,
388351
const std::unordered_map<int, int>& g2l,
389-
const Opm::ZoltanPartitioningControl<Element>& zoltan_ctrl,
390352
const int num_neighbor_levels)
391353
{
392354
auto distributedWells = 0;
@@ -398,15 +360,13 @@ void ZoltanPartitioner::connectWells(const Comm
398360
for (const auto& well : wells) {
399361
auto cellIx = std::vector<int>{};
400362
auto otherProc = 0;
401-
std::set<int> wellCells;
402363

403364
for (const auto& conn : well.getConnections()) {
404365
auto locPos = g2l.find(conn.global_index());
405366
if (locPos == g2l.end()) {
406367
++otherProc;
407368
continue;
408369
}
409-
wellCells.insert(locPos->second);
410370
cellIx.push_back(locPos->second);
411371
}
412372
const auto possibleFutureConnectionSetIt = possibleFutureConnections.find(well.name());
@@ -417,7 +377,6 @@ void ZoltanPartitioner::connectWells(const Comm
417377
++otherProc;
418378
continue;
419379
}
420-
wellCells.insert(locPos->second);
421380
cellIx.push_back(locPos->second);
422381
}
423382
}
@@ -427,19 +386,18 @@ void ZoltanPartitioner::connectWells(const Comm
427386
continue;
428387
}
429388

430-
// If neighbor levels > 0, expand the well cells to include neighbors
431-
if (num_neighbor_levels > 0 && !wellCells.empty()) {
432-
wellCells = connectNeighbors(grid_view, wellCells, num_neighbor_levels, zoltan_ctrl);
433-
cellIx.clear();
434-
cellIx.insert(cellIx.end(), wellCells.begin(), wellCells.end());
389+
// Process neighbors and get deduplicated result
390+
if (num_neighbor_levels > 0 && !cellIx.empty()) {
391+
connectNeighbors(cellIx, num_neighbor_levels);
435392
}
436393

437394
const auto nc = cellIx.size();
438-
for (auto ic1 = 0*nc; ic1 < nc; ++ic1) {
439-
for (auto ic2 = ic1 + 1; ic2 < nc; ++ic2) {
440-
this->partitioner_.registerConnection(cellIx[ic1], cellIx[ic2]);
441-
this->partitioner_.registerConnection(cellIx[ic2], cellIx[ic1]);
442-
}
395+
if (nc == 0) {
396+
continue;
397+
}
398+
for (auto ic1 = 0*nc; ic1 < nc-1; ++ic1) {
399+
this->partitioner_.registerConnection(cellIx[ic1], cellIx[ic1+1]);
400+
this->partitioner_.registerConnection(cellIx[ic1+1], cellIx[ic1]);
443401
}
444402

445403
// Collect all cells for the well into a vertex group
@@ -708,7 +666,7 @@ Opm::partitionCells(const std::string& method,
708666
[[maybe_unused]] const std::vector<Well>& wells,
709667
[[maybe_unused]] const std::unordered_map<std::string, std::set<int>>& possibleFutureConnections,
710668
[[maybe_unused]] const ZoltanPartitioningControl<Element>& zoltan_ctrl,
711-
const int num_neighbor_levels)
669+
[[maybe_unused]] const int num_neighbor_levels)
712670
{
713671
if (method == "zoltan") {
714672
#if HAVE_MPI && HAVE_ZOLTAN

0 commit comments

Comments
 (0)