• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

networkit / networkit / 23140669299

16 Mar 2026 11:08AM UTC coverage: 79.556% (-0.002%) from 79.558%
23140669299

push

github

web-flow
Merge pull request #1397 from fabratu/codex/docs-stable-default-with-nightly-switch

Doc: Enable dynamic doc builds (stable, nightly), triggered by CI and branch combination.

29621 of 37233 relevant lines covered (79.56%)

2300523.38 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.26
/networkit/cpp/centrality/GroupClosenessLocalSearch.cpp
1

2
#include <algorithm>
3
#include <atomic>
4
#include <cassert>
5
#include <cmath>
6
#include <omp.h>
7
#include <queue>
8
#include <random>
9
#include <stdexcept>
10
#include <unordered_set>
11
#include <utility>
12

13
#ifdef __AVX2__
14
#include <immintrin.h>
15
#include <networkit/auxiliary/AlignedAllocator.hpp>
16
#endif // __AVX2__
17

18
#include <networkit/auxiliary/Parallel.hpp>
19
#include <networkit/auxiliary/Random.hpp>
20
#include <networkit/auxiliary/VectorComparator.hpp>
21
#include <networkit/centrality/GroupClosenessGrowShrink.hpp>
22
#include <networkit/centrality/GroupClosenessLocalSearch.hpp>
23
#include <networkit/distance/Diameter.hpp>
24
#include <networkit/graph/BFS.hpp>
25
#include <networkit/graph/Dijkstra.hpp>
26

27
#include <tlx/container/d_ary_addressable_int_heap.hpp>
28
#include <tlx/container/d_ary_heap.hpp>
29
#include <tlx/unused.hpp>
30

31
namespace NetworKit {
32

33
namespace {
34

35
template <class WeightType>
36
class GroupClosenessLocalSearchImpl final
37
    : public GroupClosenessLocalSearch::GroupClosenessLocalSearchInterface {
38

39
    static constexpr count K = 16; // Number of packed repetitions using AVX
40
    static constexpr float maxInt16 = static_cast<float>(std::numeric_limits<uint16_t>::max());
41
    static constexpr WeightType invalidDecr = static_cast<WeightType>(-1);
42
    static constexpr WeightType infDistance = std::numeric_limits<WeightType>::max();
43

44
public:
45
    template <class InputIt>
46
    GroupClosenessLocalSearchImpl(const Graph &G, InputIt initGroupFirst, InputIt initGroupLast,
47
                                  bool runGrowShrink, count maxIterations);
48

49
    void run() override;
50

51
private:
52
    const Graph *G;
53
    const bool runGrowShrink;
54
    const count maxIterations;
55

56
    // Shortest and 2nd shortest distance between the group to all the nodes
57
    std::vector<WeightType> distance, distance2;
58
    // Shortest and 2nd shortest distances before a swap
59
    std::vector<WeightType> oldDistance, oldDistance2;
60
    // Distance vectors used by SSSPs to avoid dynamic memory allocations
61
    std::vector<std::vector<WeightType>> distanceGlobal, distance2Global;
62
    std::vector<WeightType> farnessIncrease;
63

64
    // First and second nearest node in the group to each other node
65
    std::vector<node> nearest, nearest2;
66
    // Same as above, but use thread-local vector when running in parallel
67
    std::vector<std::vector<node>> nearestGlobal, nearest2Global;
68
    // First and second nearest node in the group to each other node before a swap
69
    std::vector<node> oldNearest, oldNearest2;
70

71
    // Used for graph exploration to avoid dynamic memory allocations
72
    std::vector<std::vector<bool>> visitedGlobal;
73

74
    // Stores at index 'i' the number of nodes at distance 'i' from the group
75
    std::vector<node> nodesAtDistance;
76
    // All nodes sorted by their distance from the group
77
    std::vector<node> nodesSortedByDistance;
78
    // At each index 'i', farness due to the vertices farther than the vertex at distance
79
    // nodesSortedByDistance[i]
80
    std::vector<WeightType> sumOfDistancesGreaterThan;
81
    // When doing a pruned SSSP to evaluate a swap, stores at index 'i' how many unexplored vertices
82
    // are at distance 'i' from the source (one vector per thread).
83
    std::vector<std::vector<node>> nodesLeftToExploreAtDistanceGlobal;
84
    // Each thread independently evaluates the decrease in farness after the insertion of a vertex
85
    // to the group. For each thread we store a pair <u, decrement>, i.e., the decrement in farness
86
    // due to the insertion of vertex 'u' to the group.
87
    std::vector<std::pair<node, WeightType>> decreasePerThread;
88

89
    // Stack used to compute the DAG with a SSSP from the group
90
    std::vector<node> dagStack;
91
    // Nodes to be added to the group sorted by increasing estimated decrease in farness
92
    std::vector<node> candidatesToAddVec;
93
    // Estimate decrease in farness for each candidate node
94
    std::vector<float> approxFarnessDecrease;
95
    // Used by the randomized algorithm that estimates the reachability set size of a vertex in a
96
    // DAG
97
    std::vector<count> sumOfMins;
98

99
    count diam, stackSize;
100

101
    WeightType groupFarness;
102

103
    tlx::d_ary_addressable_int_heap<node, 2, Aux::LessInVector<WeightType>> heap, heap2;
104
    std::vector<tlx::d_ary_addressable_int_heap<node, 2, Aux::LessInVector<WeightType>>> heaps;
105
    tlx::d_ary_heap<node, 2, Aux::GreaterInVector<float>> candidatesToAdd;
106
    tlx::d_ary_heap<node, 2, Aux::LessInVector<WeightType>> candidatesToRemove;
107

108
    std::vector<std::reference_wrapper<std::mt19937_64>> urngs;
109
#ifdef __AVX2__
110
    union RandItem {
111
        uint16_t items[K];
112
        __m256i vec;
113
    };
114
    std::vector<RandItem, AlignedAllocator<RandItem, sizeof(RandItem)>> randVec;
115
#else
116
    std::vector<uint16_t> randVec;
117
#endif // __AVX2__
118

119
    std::vector<std::uniform_int_distribution<uint32_t>> intDistributions;
120

121
    void init();
122
    count estimateDiamDirected();
123
    void computeDistances();
124
    void traversalFromGroup();
125
    tlx::d_ary_addressable_int_heap<node, 2, Aux::LessInVector<WeightType>> &getPQ();
126
    void dijkstra();
127
    bool findAndSwap();
128
    void computeFarnessIncrease();
129
    void updateAfterRemoval(node u, bool updateNodesAtDistance = false);
130
    node estimateFarnessDecrease(bool getTopOnly = false);
131
    WeightType computeFarnessDecrease(node v, WeightType increaseU = 0, bool pruned = false,
132
                                      bool parallel = false);
133
    void computeDAG();
134
    void initRandomVec();
135

136
    // Stopping condition of the algorithm: determines whether the decrease in farness achieved
137
    // is enough to proceed. If not, the algorithm stops.
138
    bool insuffcientDecrease(WeightType decrease, WeightType increase) const noexcept {
54,780✔
139
        const count k = group.size(), n = G->numberOfNodes();
54,780✔
140
        return decrease <= increase
141
               || static_cast<double>(decrease - increase) / static_cast<double>(groupFarness)
101,026✔
142
                      < 1. / static_cast<double>(k * (n - k));
101,026✔
143
    }
144

145
#ifdef NETWORKIT_SANITY_CHECKS
146
    void runSanityCheck(node v = none, bool parallel = false) const;
147
#endif // NETWORKIT_SANITY_CHECKS
148
};
149

150
template <class WeightType>
151
template <class InputIt>
152
GroupClosenessLocalSearchImpl<WeightType>::GroupClosenessLocalSearchImpl(const Graph &G,
24✔
153
                                                                         InputIt initGroupFirst,
154
                                                                         InputIt initGroupLast,
155
                                                                         bool runGrowShrink,
156
                                                                         count maxIterations)
157
    : GroupClosenessLocalSearchInterface(initGroupFirst, initGroupLast), G(&G),
24✔
158
      runGrowShrink(runGrowShrink), maxIterations(maxIterations),
24✔
159
      heap(Aux::LessInVector<WeightType>(distance)),
24✔
160
      heap2(Aux::LessInVector<WeightType>(distance2)),
24✔
161
      candidatesToAdd(Aux::GreaterInVector<float>(approxFarnessDecrease)),
24✔
162
      candidatesToRemove(Aux::LessInVector<WeightType>(farnessIncrease)) {
72✔
163

164
    if (group.empty())
24✔
165
        throw std::runtime_error("Error, empty group.");
8✔
166
}
272✔
167

168
template <class WeightType>
169
count GroupClosenessLocalSearchImpl<WeightType>::estimateDiamDirected() {
8✔
170
    auto &visited = visitedGlobal[omp_get_thread_num()];
8✔
171

172
    count estDiam = 0;
8✔
173

174
    // Farthest node from u in #of hops (weights are ignored)
175
    const auto farthestFrom = [&](node u) -> node {
4,448✔
176
        std::fill(visited.begin(), visited.end(), false);
40✔
177
        visited[u] = true;
40✔
178

179
        std::queue<node> q1, q2;
40✔
180
        q1.push(u);
40✔
181
        node farthest = u;
40✔
182

183
        node dist = 1;
40✔
184
        do {
185
            do {
186
                const node v = q1.front();
4,000✔
187
                q1.pop();
4,000✔
188
                G->forNeighborsOf(v, [&](const node w) {
44,760✔
189
                    if (!visited[w]) {
40,760✔
190
                        visited[w] = true;
3,960✔
191
                        farthest = u;
3,960✔
192
                        q2.push(w);
3,960✔
193
                    }
194
                });
195

196
            } while (!q1.empty());
4,000✔
197

198
            estDiam = std::max(estDiam, dist);
160✔
199
            ++dist;
160✔
200
            std::swap(q1, q2);
160✔
201
        } while (!q1.empty());
160✔
202

203
        return farthest;
40✔
204
    };
40✔
205

206
    node source =
207
        *std::max_element(G->nodeRange().begin(), G->nodeRange().end(),
8✔
208
                          [G = G](node u, node v) { return G->degree(u) < G->degree(v); });
404✔
209
    // Preliminary experiments show that 10 BFSs are enough to get a good estimate of the diameter
210
    for (int i = 0; i < 10; ++i)
88✔
211
        source = farthestFrom(source);
80✔
212

213
    return estDiam;
8✔
214
}
215

216
template <class WeightType>
217
void GroupClosenessLocalSearchImpl<WeightType>::init() {
16✔
218
    const auto n = G->upperNodeIdBound();
16✔
219

220
    distance.assign(n, 0);
16✔
221
    distance2.assign(n, 0);
16✔
222
    distanceGlobal.assign(omp_get_max_threads(), std::vector<WeightType>(n));
16✔
223
    distance2Global.assign(omp_get_max_threads(), std::vector<WeightType>(n));
16✔
224

225
    visitedGlobal.assign(omp_get_max_threads(), std::vector<bool>(n));
16✔
226

227
    nearest.assign(n, none);
16✔
228
    nearest2.assign(n, none);
16✔
229
    oldNearest.assign(n, none);
16✔
230
    oldNearest2.assign(n, none);
16✔
231
    nearestGlobal.assign(omp_get_max_threads(), std::vector<node>(n, none));
16✔
232
    nearest2Global.assign(omp_get_max_threads(), std::vector<node>(n, none));
16✔
233

234
    decreasePerThread.resize(omp_get_max_threads(), std::make_pair(none, 0));
16✔
235

236
    approxFarnessDecrease.assign(n, 0);
16✔
237
    farnessIncrease.assign(n, 0);
16✔
238

239
    heap.reserve(n);
16✔
240
    candidatesToAdd.reserve(n);
16✔
241
    candidatesToRemove.reserve(n);
16✔
242

243
    candidatesToAddVec.reserve(n - group.size());
16✔
244
    dagStack.assign(n, 0);
16✔
245
    sumOfMins.assign(n, 0);
16✔
246

247
    if (G->isDirected()) {
16✔
248
        diam = estimateDiamDirected();
8✔
249
    } else {
250
        Diameter diamAlgo(*G, DiameterAlgo::ESTIMATED_RANGE, 0.1);
8✔
251
        diamAlgo.run();
8✔
252
        diam = diamAlgo.getDiameter().second;
8✔
253
    }
8✔
254

255
    if (G->isWeighted()) {
16✔
256
        nodesSortedByDistance.assign(G->nodeRange().begin(), G->nodeRange().end());
8✔
257
        sumOfDistancesGreaterThan.resize(n);
8✔
258
        heap2.reserve(n);
8✔
259
        heaps.reserve(omp_get_max_threads());
8✔
260
        for (omp_index i = 0; i < static_cast<omp_index>(omp_get_max_threads()); ++i) {
24✔
261
            heaps.emplace_back(distanceGlobal[i]);
16✔
262
            heaps.back().reserve(n);
16✔
263
        }
264
    } else {
265
        nodesAtDistance.resize(diam + 1);
8✔
266
        nodesLeftToExploreAtDistanceGlobal.resize(omp_get_max_threads(),
8✔
267
                                                  std::vector<node>(diam + 1));
16✔
268
    }
269

270
    urngs.reserve(omp_get_max_threads());
16✔
271
    for (omp_index i = 0; i < static_cast<omp_index>(omp_get_max_threads()); ++i)
48✔
272
        urngs.emplace_back(Aux::Random::getURNG());
32✔
273

274
#ifdef __AVX2__
275
    randVec.resize(n);
276
#else
277
    randVec.resize(K * n);
16✔
278
#endif // __AVX2__
279

280
    intDistributions.resize(omp_get_max_threads());
16✔
281
}
16✔
282

283
template <>
284
tlx::d_ary_addressable_int_heap<node, 2, Aux::LessInVector<count>> &
285
GroupClosenessLocalSearchImpl<count>::getPQ() {
52✔
286
    return heap;
52✔
287
}
288

289
template <>
290
tlx::d_ary_addressable_int_heap<node, 2, Aux::LessInVector<edgeweight>> &
291
GroupClosenessLocalSearchImpl<edgeweight>::getPQ() {
42✔
292
    return heap2;
42✔
293
}
294

295
template <class WeightType>
296
void GroupClosenessLocalSearchImpl<WeightType>::initRandomVec() {
156✔
297
    G->parallelForNodes([&](node u) {
150,540✔
298
        // Avoid to generate numbers for nodes in the group
299
        if (distance[u] > 0) {
7,800✔
300
            auto tid = omp_get_thread_num();
7,488✔
301
            auto &curUrng = urngs[tid].get();
7,488✔
302
            auto &distr = intDistributions[tid];
7,488✔
303
#ifdef __AVX2__
304
            // Generating two 16-bit random integers per time
305
            for (index j = 0; j < K; j += 2) {
306
                const auto x = distr(curUrng);
307
                randVec[u].items[j] = static_cast<uint16_t>(x);
308
                randVec[u].items[j + 1] = static_cast<uint16_t>(x >> K);
309
            }
310
            randVec[u].vec = *(__m256i *)(&randVec[u].items[0]);
311
#else
312
            // Generating two 16-bit random integers per time
313
            for (index j = 0; j < K; j += 2) {
67,392✔
314
                const auto x = distr(curUrng);
59,904✔
315
                randVec[K * u + j] = static_cast<uint16_t>(x);
59,904✔
316
                randVec[K * u + j + 1] = static_cast<uint16_t>(x >> K);
59,904✔
317
            }
318
#endif // __AVX2__
319
        }
320
    });
321
}
156✔
322

323
// Unweighted case: perform a BFS
324
template <>
325
void GroupClosenessLocalSearchImpl<count>::traversalFromGroup() {
4✔
326
    groupFarness = 0;
4✔
327
    auto &visited = visitedGlobal[omp_get_thread_num()];
4✔
328
    std::fill(visited.begin(), visited.end(), false);
4✔
329

330
    std::queue<node> q;
4✔
331

332
    for (node u : group) {
24✔
333
        q.push(u);
20✔
334
        distance[u] = 0;
20✔
335
        visited[u] = true;
20✔
336
        nearest[u] = u;
20✔
337
    }
338

339
    do {
340
        const node x = q.front();
400✔
341
        q.pop();
400✔
342
        groupFarness += distance[x];
400✔
343

344
        G->forNeighborsOf(x, [&](node y) {
400✔
345
            if (!visited[y]) {
3,918✔
346
                distance[y] = distance[x] + 1;
380✔
347
                nearest[y] = nearest[x];
380✔
348
                visited[y] = true;
380✔
349
                q.push(y);
380✔
350
            }
351
        });
3,918✔
352

353
    } while (!q.empty());
400✔
354
}
4✔
355

356
// Weighted case: Dijkstra
357
template <>
358
void GroupClosenessLocalSearchImpl<edgeweight>::traversalFromGroup() {
4✔
359
    groupFarness = 0;
4✔
360
    assert(heap.empty());
4✔
361
    auto &visited = visitedGlobal[omp_get_thread_num()];
4✔
362
    std::fill(visited.begin(), visited.end(), false);
4✔
363

364
    for (node u : group) {
24✔
365
        heap.push(u);
20✔
366
        distance[u] = 0;
20✔
367
        visited[u] = true;
20✔
368
        nearest[u] = u;
20✔
369
    }
370

371
    do {
372
        const node x = heap.extract_top();
400✔
373
        groupFarness += distance[x];
400✔
374

375
        G->forNeighborsOf(x, [&](node y, edgeweight weight) {
400✔
376
            if (!visited[y] || distance[y] > distance[x] + weight) {
3,918✔
377
                distance[y] = distance[x] + weight;
797✔
378
                nearest[y] = nearest[x];
797✔
379
                visited[y] = true;
797✔
380
                heap.update(y);
797✔
381
            }
382
        });
3,918✔
383

384
    } while (!heap.empty());
400✔
385
}
4✔
386

387
template <class WeightType>
388
void GroupClosenessLocalSearchImpl<WeightType>::dijkstra() {
16✔
389
    auto &pq = getPQ();
16✔
390
    assert(!pq.empty());
16✔
391

392
    auto &visited = visitedGlobal[omp_get_thread_num()];
16✔
393

394
    do {
395
        const node x = pq.extract_top();
1,602✔
396

397
        G->forNeighborsOf(x, [&](node y, edgeweight weight) {
9,448✔
398
            if (!visited[y] || distance2[y] > distance2[x] + weight) {
7,846✔
399
                distance2[y] = distance2[x] + weight;
255✔
400
                nearest2[y] = nearest2[x];
255✔
401
                pq.update(y);
255✔
402
                visited[y] = true;
255✔
403
            }
404
        });
405

406
    } while (!pq.empty());
1,602✔
407
}
16✔
408

409
template <class WeightType>
410
void GroupClosenessLocalSearchImpl<WeightType>::computeDistances() {
16✔
411
    // Compute 1st shortest distances
412
    traversalFromGroup();
16✔
413

414
    // Compute 2nd shortest distances
415
    // Step 1: find 'boundary' nodes (sources of Dijkstra)
416
    auto &visited = visitedGlobal[omp_get_thread_num()];
16✔
417
    std::fill(visited.begin(), visited.end(), false);
16✔
418
    auto &pq = getPQ();
16✔
419

420
    const auto processEdge = [&](node x, node y, WeightType weight) -> void {
4,979✔
421
        if (!visited[y] || distance2[y] > distance[x] + weight) {
4,963✔
422
            distance2[y] = distance[x] + weight;
1,533✔
423
            visited[y] = true;
1,533✔
424
            nearest2[y] = nearest[x];
1,533✔
425
            pq.update(y);
1,533✔
426
        }
427
    };
428

429
    G->forEdges([&](node x, node y, edgeweight weight) {
26,659✔
430
        if (nearest[x] != nearest[y]) {
5,956✔
431
            processEdge(x, y, static_cast<WeightType>(weight));
3,812✔
432
            if (!G->isDirected())
3,812✔
433
                processEdge(y, x, static_cast<WeightType>(weight));
1,151✔
434
        }
435
    });
436

437
    // Step 2: Explore rest of the graph with Dijkstra
438
    dijkstra();
16✔
439
}
16✔
440

441
template <class WeightType>
442
void GroupClosenessLocalSearchImpl<WeightType>::computeFarnessIncrease() {
90✔
443
    for (node u : group)
540✔
444
        farnessIncrease[u] = 0;
450✔
445

446
    G->forNodes([&](node u) {
27,090✔
447
        assert(group.find(nearest[u]) != group.end());
4,500✔
448
        farnessIncrease[nearest[u]] += distance2[u] - distance[u];
4,500✔
449
    });
450
}
90✔
451

452
// Unweighted graphs: use a BFS
453
template <>
454
void GroupClosenessLocalSearchImpl<count>::computeDAG() {
44✔
455
    auto &visited = visitedGlobal[omp_get_thread_num()];
44✔
456
    std::fill(visited.begin(), visited.end(), false);
44✔
457

458
    std::queue<node> q;
44✔
459
    for (node u : group) {
220✔
460
        q.push(u);
176✔
461
        visited[u] = true;
176✔
462
    }
463

464
    // Recompute the DAG
465
    do {
466
        const node x = q.front();
4,400✔
467
        q.pop();
4,400✔
468

469
        G->forNeighborsOf(x, [&](node y) {
4,400✔
470
            if (!visited[y]) {
42,861✔
471
                visited[y] = true;
4,224✔
472
                q.push(y);
4,224✔
473
            }
474
        });
42,861✔
475

476
        if (distance[x] > 0)
4,400✔
477
            dagStack[stackSize++] = x;
4,224✔
478

479
    } while (!q.empty());
4,400✔
480
}
44✔
481

482
// Weighted graphs: use Dijkstra
483
template <>
484
void GroupClosenessLocalSearchImpl<edgeweight>::computeDAG() {
34✔
485
    assert(heap.empty());
34✔
486
    auto &visited = visitedGlobal[omp_get_thread_num()];
34✔
487
    std::fill(visited.begin(), visited.end(), false);
34✔
488

489
    for (node u : group) {
170✔
490
        heap.push(u);
136✔
491
        visited[u] = true;
136✔
492
    }
493

494
    // Recompute the DAG
495
    do {
496
        const node x = heap.extract_top();
3,400✔
497

498
        G->forNeighborsOf(x, [&](node y, edgeweight weight) {
3,400✔
499
            if (!visited[y] || (distance[y] > distance[x] + weight)) {
33,540✔
500
                visited[y] = true;
3,264✔
501
                heap.update(y);
3,264✔
502
            }
503
        });
33,540✔
504

505
        if (distance[x] > 0)
3,400✔
506
            dagStack[stackSize++] = x;
3,264✔
507

508
    } while (!heap.empty());
3,400✔
509
}
34✔
510

511
template <class WeightType>
512
node GroupClosenessLocalSearchImpl<WeightType>::estimateFarnessDecrease(bool getTopOnly) {
156✔
513
    stackSize = 0;
156✔
514
    computeDAG();
156✔
515

516
    // Generating all the necessary random numbers
517
    initRandomVec();
156✔
518

519
    // Do 16 packed repetitions at once
520
    for (count i = 0; i < stackSize; ++i) {
15,132✔
521
        const node x = dagStack[stackSize - 1 - i];
14,976✔
522

523
#ifdef __AVX2__
524
        // 16 randomly generated integers;
525
        __m256i &x1 = randVec[x].vec;
526
        // Pulling leaves
527
        G->forNeighborsOf(x, [&](node y, edgeweight weight) {
528
            if (distance[y] == distance[x] + weight) {
529
                const __m256i &y1 = randVec[y].vec;
530
                x1 = _mm256_min_epu16(x1, y1);
531
            }
532
        });
533
        *(__m256i *)(&randVec[x].items) = x1;
534
#else
535
        G->forNeighborsOf(x, [&](node y, edgeweight weight) {
816,309✔
536
            if (distance[y] == distance[x] + weight)
72,183✔
537
                for (index i = 0; i < K; ++i)
207,111✔
538
                    randVec[K * x + i] = std::min(randVec[K * x + i], randVec[K * y + i]);
194,928✔
539
        });
540

541
#endif // __AVX2__
542

543
        if (distance[x] > 0) {
14,976✔
544
            sumOfMins[x] = 0;
14,976✔
545
            for (index j = 0; j < K; ++j)
254,592✔
546
#ifdef __AVX2__
547
                sumOfMins[x] += randVec[x].items[j];
548
#else
549
                sumOfMins[x] = randVec[K * x + j];
239,616✔
550
#endif // __AVX2__
551

552
            if (sumOfMins[x] == 0)
14,976✔
553
                sumOfMins[x] = 1;
×
554
        }
555
    }
556

557
    if (!getTopOnly)
156✔
558
        candidatesToAddVec.clear();
156✔
559

560
    node v = none;
156✔
561
    float bestEstimate = -1.f;
156✔
562
    G->forNodes([&](const node x) {
68,796✔
563
        if (distance[x] > 0 && sumOfMins[x] > 0) {
7,800✔
564
            const float estimate = static_cast<float>(distance[x])
7,488✔
565
                                   * (16.f / (static_cast<float>(sumOfMins[x]) / maxInt16) - 1.f);
7,488✔
566
            if (getTopOnly) {
7,488✔
567
                if (estimate > bestEstimate) {
×
568
                    bestEstimate = estimate;
×
569
                    v = x;
×
570
                }
571
            } else if (G->degree(x) > 1) {
7,488✔
572
                approxFarnessDecrease[x] = estimate;
7,488✔
573
                candidatesToAddVec.push_back(x);
7,488✔
574
            }
575
        } else if (!getTopOnly)
312✔
576
            approxFarnessDecrease[x] = -1.f;
312✔
577
    });
578

579
    return v;
156✔
580
}
581

582
// Unweighted graphs
583
template <>
584
count GroupClosenessLocalSearchImpl<count>::computeFarnessDecrease(node v, count increaseU,
2,378✔
585
                                                                   bool pruned, bool parallel) {
586
    auto &curDistance = parallel ? distanceGlobal[omp_get_thread_num()] : distance;
2,378✔
587
    auto &curDistance2 = parallel ? distance2Global[omp_get_thread_num()] : distance2;
2,378✔
588
    auto &curNearest = parallel ? nearestGlobal[omp_get_thread_num()] : nearest;
2,378✔
589
    auto &curNearest2 = parallel ? nearest2Global[omp_get_thread_num()] : nearest2;
2,378✔
590

591
    count decr = 0;
2,378✔
592
    curDistance2[v] = curDistance[v];
2,378✔
593
    curNearest2[v] = curNearest[v];
2,378✔
594
    curDistance[v] = 0;
2,378✔
595
    curNearest[v] = v;
2,378✔
596

597
    std::queue<node> q1, q2;
2,378✔
598
    q1.push(v);
2,378✔
599

600
    auto &visited = visitedGlobal[omp_get_thread_num()];
2,378✔
601
    std::fill(visited.begin(), visited.end(), false);
2,378✔
602
    visited[v] = true;
2,378✔
603

604
    auto &nodesLeftToExploreAtDistance = nodesLeftToExploreAtDistanceGlobal[omp_get_thread_num()];
2,378✔
605
    if (pruned) {
2,378✔
606
        std::copy(nodesAtDistance.begin(), nodesAtDistance.end(),
2,357✔
607
                  nodesLeftToExploreAtDistance.begin());
608
        --nodesLeftToExploreAtDistance[curDistance[v]];
2,357✔
609
    }
610

611
    count level = 0;
2,378✔
612
    do {
613
        count nextLevelDecr = 0;
2,485✔
614

615
        do {
616
            const node x = q1.front();
3,287✔
617
            q1.pop();
3,287✔
618

619
            decr += static_cast<count>(curNearest[x] == v) * (curDistance2[x] - curDistance[x]);
3,287✔
620

621
            G->forNeighborsOf(x, [&](const node y) {
3,287✔
622
                if (visited[y])
31,694✔
623
                    return;
4,967✔
624

625
                const count distY = curDistance[y];
26,727✔
626

627
                if (pruned)
26,727✔
628
                    --nodesLeftToExploreAtDistance[distY];
24,995✔
629

630
                if (distY > curDistance[x] + 1) {
26,727✔
631

632
                    curDistance2[y] = distY;
9,679✔
633
                    curNearest2[y] = curNearest[y];
9,679✔
634
                    curDistance[y] = curDistance[x] + 1;
9,679✔
635
                    curNearest[y] = v;
9,679✔
636
                    q2.push(y);
9,679✔
637
                    if (pruned)
9,679✔
638
                        nextLevelDecr += static_cast<count>(distY - curDistance[y]);
9,466✔
639
                } else if (curNearest[x] == v && curDistance2[y] > curDistance[x] + 1) {
17,048✔
640
                    curDistance2[y] = curDistance[x] + 1;
11,174✔
641
                    curNearest2[y] = v;
11,174✔
642
                    q2.push(y);
11,174✔
643
                } else if (curNearest2[x] == v && curDistance2[y] > curDistance2[x] + 1) {
5,874✔
644
                    curDistance2[y] = curDistance2[x] + 1;
22✔
645
                    curNearest2[y] = v;
22✔
646
                    q2.push(y);
22✔
647
                }
648

649
                visited[y] = true;
26,727✔
650
            });
651

652
        } while (!q1.empty());
3,287✔
653

654
        if (pruned) {
2,485✔
655
            // Upper bound to decrease:
656
            // decrease due to visited vertices +
657
            // decrease due to enqueued vertices (in the next level of the BFS) +
658
            // (vertices at distance level + 2 do not carry any decrease because they stay at the
659
            // same distance) +
660
            // vertices farther than current level + 2 are assumed to be reachable from v in
661
            // level + 2 hops, so they could bring a decrease of (dist(x, S) - level - 2).
662
            // e.g., if nodes at distance (level + 3) can be reached in (level + 2) hops,
663
            // will bring a decrease of 1
664
            count decrUB = decr + nextLevelDecr;
2,429✔
665
            for (count dist = level + 3; dist < diam; ++dist)
4,786✔
666
                decrUB += nodesLeftToExploreAtDistance[dist] * (dist - level - 2);
2,357✔
667

668
            // Prune
669
            if (insuffcientDecrease(decrUB, increaseU))
2,429✔
670
                return invalidDecr;
2,320✔
671
        }
672

673
        ++level;
165✔
674
        std::swap(q1, q2);
165✔
675
    } while (!q1.empty());
165✔
676

677
    return decr;
58✔
678
}
2,378✔
679

680
// Weighted graphs
681
template <>
682
edgeweight
683
GroupClosenessLocalSearchImpl<edgeweight>::computeFarnessDecrease(node v, edgeweight increaseU,
2,072✔
684
                                                                  bool pruned, bool parallel) {
685
    auto &curDistance = parallel ? distanceGlobal[omp_get_thread_num()] : distance;
2,072✔
686
    auto &curDistance2 = parallel ? distance2Global[omp_get_thread_num()] : distance2;
2,072✔
687
    auto &curNearest = parallel ? nearestGlobal[omp_get_thread_num()] : nearest;
2,072✔
688
    auto &curNearest2 = parallel ? nearest2Global[omp_get_thread_num()] : nearest2;
2,072✔
689
    auto &curHeap = parallel ? heaps[omp_get_thread_num()] : heap;
2,072✔
690

691
    curDistance2[v] = curDistance[v];
2,072✔
692
    curNearest2[v] = curNearest[v];
2,072✔
693
    curDistance[v] = 0;
2,072✔
694
    curNearest[v] = v;
2,072✔
695

696
    curHeap.push(v);
2,072✔
697
    edgeweight decr = 0;
2,072✔
698

699
    do {
700
        const node x = curHeap.extract_top();
25,734✔
701
        decr += static_cast<edgeweight>(curNearest[x] == v) * (curDistance2[x] - curDistance[x]);
25,734✔
702

703
        G->forNeighborsOf(x, [&](const node y, const edgeweight weight) {
25,734✔
704
            if (curDistance[y] > curDistance[x] + weight) {
253,465✔
705
                // Nearest nodes could have been already updated
706
                if (curNearest[y] != v) {
11,609✔
707
                    curNearest2[y] = curNearest[y];
11,470✔
708
                    curNearest[y] = v;
11,470✔
709
                    curDistance2[y] = curDistance[y];
11,470✔
710
                }
711
                curDistance[y] = curDistance[x] + weight;
11,609✔
712
                curHeap.update(y);
11,609✔
713
            } else if (curNearest[x] == v && curNearest[y] != v
323,247✔
714
                       && curDistance2[y] > curDistance[x] + weight) {
323,247✔
715
                curDistance2[y] = curDistance[x] + weight;
7,343✔
716
                curNearest2[y] = v;
7,343✔
717
                curHeap.update(y);
7,343✔
718
            } else if (curNearest2[x] == v && curNearest[y] != v
394,978✔
719
                       && curDistance2[y] > curDistance2[x] + weight) {
394,978✔
720
                curDistance2[y] = curDistance2[x] + weight;
13,106✔
721
                curNearest2[y] = v;
13,106✔
722
                curHeap.update(y);
13,106✔
723
            }
724
        });
253,465✔
725

726
        if (curHeap.empty())
25,734✔
727
            break;
321✔
728

729
        if (pruned) {
25,413✔
730
            const count n = G->numberOfNodes();
24,623✔
731
            const node top = curHeap.top();
24,623✔
732
            const edgeweight topDist = distance[top];
24,623✔
733
            assert(curDistance2[top] >= topDist);
24,623✔
734
            edgeweight decrUB =
735
                decr
736
                + static_cast<edgeweight>(curNearest[top] == v) * (curDistance2[top] - topDist);
24,623✔
737
            const count nextIdx =
738
                (std::lower_bound(
24,623✔
739
                     nodesSortedByDistance.begin(), nodesSortedByDistance.end(), top,
740
                     [&](const node a, const node b) { return distance[a] < distance[b]; })
166,106✔
741
                 - nodesSortedByDistance.begin())
49,246✔
742
                + 1;
24,623✔
743

744
            if (nextIdx < n) {
24,623✔
745
                const auto nodesLeft = static_cast<edgeweight>(n - nextIdx);
24,578✔
746
                decrUB += sumOfDistancesGreaterThan[nextIdx] - nodesLeft * topDist;
24,578✔
747
                assert(decrUB >= decr);
24,578✔
748
            }
749

750
            if (insuffcientDecrease(decrUB, increaseU)) {
24,623✔
751
                curHeap.clear();
1,751✔
752
                return invalidDecr;
1,751✔
753
            }
754
        }
755
    } while (true);
23,662✔
756

757
    return decr;
321✔
758
}
759

760
// Unweighted graphs
761
template <>
762
void GroupClosenessLocalSearchImpl<count>::updateAfterRemoval(node u, bool updateNodesAtDistance) {
44✔
763
    G->forNodes([&](node x) {
44✔
764
        tlx::unused(x);
4,400✔
765
        assert(nearest[x] != nearest2[x]);
4,400✔
766
    });
4,400✔
767

768
    if (updateNodesAtDistance)
44✔
769
        std::fill(nodesAtDistance.begin(), nodesAtDistance.end(), 0);
44✔
770

771
    // Updating (1st) shortest distances and setting 2nd shortest distances to infinity
772
    G->forNodes([&](node x) {
44✔
773
        if (nearest[x] == u) {
4,400✔
774
            nearest[x] = nearest2[x];
921✔
775
            distance[x] = distance2[x];
921✔
776
            nearest2[x] = none;
921✔
777
            distance2[x] = infDistance;
921✔
778
            assert(nearest[x] != u);
921✔
779
        } else if (nearest2[x] == u) {
3,479✔
780
            nearest2[x] = none;
845✔
781
            distance2[x] = infDistance;
845✔
782
        }
783
        if (updateNodesAtDistance)
4,400✔
784
            ++nodesAtDistance[distance[x]];
4,400✔
785
    });
4,400✔
786

787
    auto &pq = getPQ();
44✔
788

789
    // Update the distance of y
790
    const auto processEdge = [&](node x, node y, count weight) -> void {
42,861✔
791
        if (nearest[x] != nearest[y]) {
42,861✔
792
            if (distance2[y] > distance[x] + weight) {
25,905✔
793
                distance2[y] = distance[x] + weight;
2,025✔
794
                nearest2[y] = nearest[x];
2,025✔
795
                pq.update(y);
2,025✔
796
            }
797
            // Checking 2nd distances
798
        } else if (distance2[x] < infDistance && distance2[y] > distance2[x] + weight) {
16,956✔
799
            distance2[y] = distance2[x] + weight;
913✔
800
            nearest2[y] = nearest2[x];
913✔
801
            pq.update(y);
913✔
802
            assert(nearest2[x] != nearest[y]);
913✔
803
        }
804
    };
42,905✔
805

806
    G->forEdges([&](node x, node y, edgeweight weight) {
44✔
807
        processEdge(x, y, static_cast<count>(weight));
31,111✔
808
        if (!G->isDirected())
31,111✔
809
            processEdge(y, x, static_cast<count>(weight));
11,750✔
810
    });
31,111✔
811

812
    while (!pq.empty()) {
1,810✔
813
        const node x = pq.extract_top();
1,766✔
814
        G->forNeighborsOf(x, [&](node y, edgeweight weight) {
1,766✔
815
            if (nearest[y] != nearest2[x]
17,300✔
816
                && distance2[y] > distance2[x] + static_cast<count>(weight)) {
17,300✔
817
                distance2[y] = distance2[x] + static_cast<count>(weight);
2✔
818
                nearest2[y] = nearest2[x];
2✔
819
                pq.update(y);
2✔
820
            }
821
        });
17,300✔
822
    }
823

824
    G->forNodes([&](node x) {
44✔
825
        tlx::unused(x);
4,400✔
826
        assert(nearest[x] != u);
4,400✔
827
        assert(nearest2[x] != u);
4,400✔
828
    });
4,400✔
829
}
44✔
830

831
// Weighted graphs
832
template <>
833
void GroupClosenessLocalSearchImpl<edgeweight>::updateAfterRemoval(node u,
34✔
834
                                                                   bool updateNodesAtDistance) {
835
    G->forNodes([&](node x) {
34✔
836
        tlx::unused(x);
3,400✔
837
        assert(nearest[x] != nearest2[x]);
3,400✔
838
    });
3,400✔
839

840
    // Updating (1st) shortest distances and setting 2nd shortest distances to infinity
841
    G->forNodes([&](const node x) {
34✔
842
        if (nearest[x] == u) {
3,400✔
843
            nearest[x] = nearest2[x];
562✔
844
            distance[x] = distance2[x];
562✔
845
            nearest2[x] = none;
562✔
846
            distance2[x] = infDistance;
562✔
847
            assert(nearest[x] != u);
562✔
848
        } else if (nearest2[x] == u) {
2,838✔
849
            nearest2[x] = none;
638✔
850
            distance2[x] = infDistance;
638✔
851
        }
852
    });
3,400✔
853

854
    auto &pq = getPQ();
34✔
855

856
    // Update the distance of y
857
    const auto process = [&](node x, node y, edgeweight weight) -> void {
33,540✔
858
        if (nearest[x] != nearest[y]) {
33,540✔
859
            if (distance2[y] > distance[x] + weight) {
21,110✔
860
                distance2[y] = distance[x] + weight;
2,225✔
861
                nearest2[y] = nearest[x];
2,225✔
862
                pq.update(y);
2,225✔
863
            }
864
            // Checking 2nd distances
865
        } else if (distance2[x] < infDistance && distance2[y] > distance2[x] + weight) {
12,430✔
866
            distance2[y] = distance2[x] + weight;
1,297✔
867
            nearest2[y] = nearest2[x];
1,297✔
868
            pq.update(y);
1,297✔
869
            assert(nearest2[x] != nearest[y]);
1,297✔
870
        }
871
    };
33,574✔
872

873
    G->forEdges([&](node x, node y, edgeweight weight) {
34✔
874
        process(x, y, weight);
26,960✔
875
        if (!G->isDirected())
26,960✔
876
            process(y, x, weight);
6,580✔
877
    });
26,960✔
878

879
    while (!pq.empty()) {
1,234✔
880
        const node x = pq.extract_top();
1,200✔
881
        G->forNeighborsOf(x, [&](node y, edgeweight weight) {
1,200✔
882
            if (nearest[y] != nearest2[x] && distance2[y] > distance2[x] + weight) {
11,789✔
883
                distance2[y] = distance2[x] + weight;
322✔
884
                nearest2[y] = nearest2[x];
322✔
885
                pq.update(y);
322✔
886
            }
887
        });
11,789✔
888
    }
889

890
    if (updateNodesAtDistance) {
34✔
891
        Aux::Parallel::sort(
34✔
892
            nodesSortedByDistance.begin(), nodesSortedByDistance.end(),
893
            [&distance = distance](node x, node y) { return distance[x] < distance[y]; });
24,190✔
894

895
        sumOfDistancesGreaterThan[G->numberOfNodes() - 1] = distance[nodesSortedByDistance.back()];
34✔
896
        for (node i = G->numberOfNodes() - 1; i; --i)
3,400✔
897
            sumOfDistancesGreaterThan[i - 1] =
3,366✔
898
                sumOfDistancesGreaterThan[i] + distance[nodesSortedByDistance[i - 1]];
3,366✔
899
    }
900
}
34✔
901

902
template <class WeightType>
903
bool GroupClosenessLocalSearchImpl<WeightType>::findAndSwap() {
90✔
904

905
    // Copy distances/nearest vertices from shared vectors to thread-local storage
906
    const auto copyStatusToThread = [&]() -> void {
61,816✔
907
        const auto thread = omp_get_thread_num();
4,409✔
908
        std::copy(distance.begin(), distance.end(), distanceGlobal[thread].begin());
4,409✔
909
        std::copy(distance2.begin(), distance2.end(), distance2Global[thread].begin());
4,409✔
910
        std::copy(nearest.begin(), nearest.end(), nearestGlobal[thread].begin());
4,409✔
911
        std::copy(nearest2.begin(), nearest2.end(), nearest2Global[thread].begin());
4,409✔
912
    };
913

914
    // Store distances/nearest vertices from thread-local storage to shared vectors
915
    const auto storeStatusFromThread = [&](const auto thread) -> void {
571✔
916
        std::copy(distanceGlobal[thread].begin(), distanceGlobal[thread].end(), distance.begin());
37✔
917
        std::copy(distance2Global[thread].begin(), distance2Global[thread].end(),
37✔
918
                  distance2.begin());
919
        std::copy(nearestGlobal[thread].begin(), nearestGlobal[thread].end(), nearest.begin());
37✔
920
        std::copy(nearest2Global[thread].begin(), nearest2Global[thread].end(), nearest2.begin());
37✔
921
    };
922

923
    // Update the farness increase due to the removal of every node in the group
924
    computeFarnessIncrease();
90✔
925
    candidatesToRemove.build_heap(group.begin(), group.end());
90✔
926

927
    // Iterate over all vertices in the group
928
    do {
929
        const node u = candidatesToRemove.extract_top();
156✔
930
        const WeightType increaseU = farnessIncrease[u];
156✔
931

932
        group.erase(u);
156✔
933
        updateAfterRemoval(u, true);
156✔
934
#ifdef NETWORKIT_SANITY_CHECKS
935
        runSanityCheck();
936
#endif // NETWORKIT_SANITY_CHECKS
937

938
        // Approximate the farness decrease for each vertex by estimating their BFS DAG
939
        // size
940
        estimateFarnessDecrease();
156✔
941
        candidatesToAdd.build_heap(candidatesToAddVec.begin(), candidatesToAddVec.end());
156✔
942

943
        std::atomic<bool> stop{false};
156✔
944
        std::atomic<int> threadToSelect{-1};
156✔
945

946
#pragma omp parallel
156✔
947
        {
948
            while (!stop.load(std::memory_order_relaxed)) {
949

950
                node v = none;
951
#pragma omp critical
952
                {
953
                    if (candidatesToAdd.empty()) {
954
                        stop.store(true, std::memory_order_relaxed);
955
                    } else {
956
                        v = candidatesToAdd.extract_top();
957
                        if (approxFarnessDecrease[v] <= 0.f) {
958
                            stop.store(true, std::memory_order_relaxed);
959
                            v = none;
960
                        }
961
                    }
962
                }
963

964
                if (v == none)
965
                    break;
966

967
                copyStatusToThread();
968
#ifdef NETWORKIT_SANITY_CHECKS
969
                runSanityCheck(none, true);
970
#endif // NETWORKIT_SANITY_CHECKS
971

972
                const WeightType farnessDecrease = computeFarnessDecrease(v, increaseU, true, true);
973

974
#ifdef NETWORKIT_SANITY_CHECKS
975
                if (farnessDecrease != invalidDecr)
976
                    runSanityCheck(v, true);
977
#endif // NETWORKIT_SANITY_CHECKS
978

979
                if (farnessDecrease == invalidDecr
980
                    || insuffcientDecrease(farnessDecrease, increaseU))
981
                    continue;
982

983
                stop.store(true, std::memory_order_relaxed);
984
                threadToSelect.store(omp_get_thread_num(), std::memory_order_relaxed);
985

986
                decreasePerThread[omp_get_thread_num()] = std::make_pair(v, farnessDecrease);
987
            }
988
        }
989

990
        const int thread = threadToSelect.load(std::memory_order_relaxed);
156✔
991

992
        if (thread != -1) { // There is a convenient swap
156✔
993
            node v;
994
            WeightType farnessDecrease;
995
            std::tie(v, farnessDecrease) = decreasePerThread[thread];
74✔
996

997
            // Removing u from group
998
            groupFarness -= (farnessDecrease - increaseU);
74✔
999

1000
            // Put v into the group
1001
            group.insert(v);
74✔
1002

1003
            storeStatusFromThread(thread);
74✔
1004
#ifdef NETWORKIT_SANITY_CHECKS
1005
            runSanityCheck();
1006
#endif // NETWORKIT_SANITY_CHECKS
1007
            return true;
74✔
1008
        }
1009

1010
        // Swaps with u did not improve the closeness enough, restore u into the group
1011
        group.insert(u);
82✔
1012
        const WeightType decreaseU = computeFarnessDecrease(u);
82✔
1013
        tlx::unused(decreaseU);
82✔
1014

1015
        if (G->isWeighted())
82✔
1016
            assert(std::abs(static_cast<double>(decreaseU - increaseU)) <= 1e-9);
40✔
1017
        else
1018
            assert(decreaseU == increaseU);
42✔
1019

1020
    } while (!candidatesToRemove.empty());
82✔
1021

1022
    return false;
16✔
1023
}
1024

1025
template <class WeightType>
1026
void GroupClosenessLocalSearchImpl<WeightType>::run() {
16✔
1027

1028
    if (runGrowShrink) {
16✔
1029
        GroupClosenessGrowShrink gcgs(*G, group.begin(), group.end());
4✔
1030
        gcgs.run();
4✔
1031
        const auto newGroup = gcgs.groupMaxCloseness();
4✔
1032
        group = std::unordered_set<node>{newGroup.begin(), newGroup.end()};
4✔
1033
    }
4✔
1034

1035
    init();
16✔
1036

1037
    // Compute 1st and 2nd shortest distances and the vertices in the group that realize them
1038
    computeDistances();
16✔
1039

1040
#ifdef NETWORKIT_SANITY_CHECKS
1041
    runSanityCheck();
1042
#endif // NETWORKIT_SANITY_CHECKS
1043

1044
    for (nIterations = 0; nIterations < maxIterations && findAndSwap(); ++nIterations) {
90✔
1045
    }
1046

1047
    hasRun = true;
16✔
1048
}
16✔
1049

1050
#ifdef NETWORKIT_SANITY_CHECKS
1051
template <>
1052
void GroupClosenessLocalSearchImpl<count>::runSanityCheck(node v, bool parallel) const {
1053
    auto tmpGroup = group;
1054
    if (v != none)
1055
        tmpGroup.insert(v);
1056

1057
    const count n = G->upperNodeIdBound();
1058
    std::vector<count> checkDistance(n, std::numeric_limits<count>::max());
1059
    std::vector<count> checkDistance2(n, std::numeric_limits<count>::max());
1060
    for (const node source : tmpGroup) {
1061
        Traversal::BFSfrom(*G, source, [&](node u, count d) {
1062
            if (d < checkDistance[u]) {
1063
                assert(checkDistance2[u] >= checkDistance[u]);
1064
                checkDistance2[u] = checkDistance[u];
1065
                checkDistance[u] = d;
1066
            } else if (d < checkDistance2[u])
1067
                checkDistance2[u] = d;
1068
        });
1069
    }
1070

1071
    const auto &curDistance = parallel ? distanceGlobal[omp_get_thread_num()] : distance;
1072
    const auto &curDistance2 = parallel ? distance2Global[omp_get_thread_num()] : distance2;
1073

1074
    G->forNodes([&](node u) {
1075
        if (group.find(u) != group.end())
1076
            return;
1077
        assert(curDistance2[u] == checkDistance2[u]);
1078
        assert(curDistance[u] == checkDistance[u]);
1079
    });
1080
}
1081

1082
template <>
1083
void GroupClosenessLocalSearchImpl<edgeweight>::runSanityCheck(node v, bool parallel) const {
1084
    auto tmpGroup = group;
1085
    if (v != none)
1086
        tmpGroup.insert(v);
1087

1088
    const count n = G->upperNodeIdBound();
1089
    std::vector<edgeweight> checkDistance(n, std::numeric_limits<edgeweight>::max());
1090
    std::vector<edgeweight> checkDistance2(n, std::numeric_limits<edgeweight>::max());
1091
    for (const auto source : tmpGroup) {
1092
        Traversal::DijkstraFrom(*G, source, [&](node u, edgeweight d) {
1093
            if (d < checkDistance[u]) {
1094
                checkDistance2[u] = checkDistance[u];
1095
                checkDistance[u] = d;
1096
            } else if (d < checkDistance2[u])
1097
                checkDistance2[u] = d;
1098
        });
1099
    }
1100

1101
    const auto &curDistance = parallel ? distanceGlobal[omp_get_thread_num()] : distance;
1102
    const auto &curDistance2 = parallel ? distance2Global[omp_get_thread_num()] : distance2;
1103
    G->forNodes([&](node u) {
1104
        if (group.find(u) != group.end())
1105
            return;
1106
        assert(std::abs(curDistance2[u] - checkDistance2[u]) <= 1e-9);
1107
        assert(std::abs(curDistance[u] - checkDistance[u]) <= 1e-9);
1108
    });
1109
}
1110
#endif // NETWORKIT_SANITY_CHECKS
1111

1112
} // namespace
1113

1114
GroupClosenessLocalSearch::GroupClosenessLocalSearch(const Graph &G, const std::vector<node> &group,
12✔
1115
                                                     bool runGrowShrink, count maxIterations)
12✔
1116
    : weighted(G.isWeighted()) {
12✔
1117

1118
    if (weighted)
12✔
1119
        impl = std::make_unique<GroupClosenessLocalSearchImpl<edgeweight>>(
12✔
1120
            G, group.begin(), group.end(), runGrowShrink, maxIterations);
16✔
1121
    else
1122
        impl = std::make_unique<GroupClosenessLocalSearchImpl<count>>(G, group.begin(), group.end(),
10✔
1123
                                                                      runGrowShrink, maxIterations);
4✔
1124
}
16✔
1125

1126
void GroupClosenessLocalSearch::run() {
8✔
1127
    impl->run();
8✔
1128
    hasRun = true;
8✔
1129
}
8✔
1130

1131
std::vector<node> GroupClosenessLocalSearch::groupMaxCloseness() const {
4✔
1132
    assureFinished();
4✔
1133
    return {impl->group.begin(), impl->group.end()};
4✔
1134
}
1135

1136
count GroupClosenessLocalSearch::numberOfIterations() const {
4✔
1137
    assureFinished();
4✔
1138
    return impl->nIterations;
4✔
1139
}
1140

1141
} // namespace NetworKit
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc