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

networkit / networkit / 23148121505

16 Mar 2026 02:13PM UTC coverage: 79.548% (-0.008%) from 79.556%
23148121505

push

github

web-flow
Merge pull request #1400 from fabratu/20260316_fix_docs_deploy

CI: Remove smoke test for URL

29618 of 37233 relevant lines covered (79.55%)

2256105.66 hits per line

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

98.7
/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 {
62,904✔
139
        const count k = group.size(), n = G->numberOfNodes();
62,904✔
140
        return decrease <= increase
141
               || static_cast<double>(decrease - increase) / static_cast<double>(groupFarness)
117,592✔
142
                      < 1. / static_cast<double>(k * (n - k));
117,592✔
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() {
43✔
286
    return heap;
43✔
287
}
288

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

295
template <class WeightType>
296
void GroupClosenessLocalSearchImpl<WeightType>::initRandomVec() {
154✔
297
    G->parallelForNodes([&](node u) {
148,610✔
298
        // Avoid to generate numbers for nodes in the group
299
        if (distance[u] > 0) {
7,700✔
300
            auto tid = omp_get_thread_num();
7,392✔
301
            auto &curUrng = urngs[tid].get();
7,392✔
302
            auto &distr = intDistributions[tid];
7,392✔
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) {
66,528✔
314
                const auto x = distr(curUrng);
59,136✔
315
                randVec[K * u + j] = static_cast<uint16_t>(x);
59,136✔
316
                randVec[K * u + j + 1] = static_cast<uint16_t>(x >> K);
59,136✔
317
            }
318
#endif // __AVX2__
319
        }
320
    });
321
}
154✔
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;
777✔
378
                nearest[y] = nearest[x];
777✔
379
                visited[y] = true;
777✔
380
                heap.update(y);
777✔
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,606✔
396

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

406
    } while (!pq.empty());
1,606✔
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 {
5,017✔
421
        if (!visited[y] || distance2[y] > distance[x] + weight) {
5,001✔
422
            distance2[y] = distance[x] + weight;
1,508✔
423
            visited[y] = true;
1,508✔
424
            nearest2[y] = nearest[x];
1,508✔
425
            pq.update(y);
1,508✔
426
        }
427
    };
428

429
    G->forEdges([&](node x, node y, edgeweight weight) {
26,719✔
430
        if (nearest[x] != nearest[y]) {
5,956✔
431
            processEdge(x, y, static_cast<WeightType>(weight));
3,834✔
432
            if (!G->isDirected())
3,834✔
433
                processEdge(y, x, static_cast<WeightType>(weight));
1,167✔
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() {
35✔
455
    auto &visited = visitedGlobal[omp_get_thread_num()];
35✔
456
    std::fill(visited.begin(), visited.end(), false);
35✔
457

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

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

469
        G->forNeighborsOf(x, [&](node y) {
3,500✔
470
            if (!visited[y]) {
34,401✔
471
                visited[y] = true;
3,360✔
472
                q.push(y);
3,360✔
473
            }
474
        });
34,401✔
475

476
        if (distance[x] > 0)
3,500✔
477
            dagStack[stackSize++] = x;
3,360✔
478

479
    } while (!q.empty());
3,500✔
480
}
35✔
481

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

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

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

498
        G->forNeighborsOf(x, [&](node y, edgeweight weight) {
4,200✔
499
            if (!visited[y] || (distance[y] > distance[x] + weight)) {
41,534✔
500
                visited[y] = true;
4,032✔
501
                heap.update(y);
4,032✔
502
            }
503
        });
41,534✔
504

505
        if (distance[x] > 0)
4,200✔
506
            dagStack[stackSize++] = x;
4,032✔
507

508
    } while (!heap.empty());
4,200✔
509
}
42✔
510

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

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

519
    // Do 16 packed repetitions at once
520
    for (count i = 0; i < stackSize; ++i) {
14,938✔
521
        const node x = dagStack[stackSize - 1 - i];
14,784✔
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) {
762,936✔
536
            if (distance[y] == distance[x] + weight)
72,008✔
537
                for (index i = 0; i < K; ++i)
188,462✔
538
                    randVec[K * x + i] = std::min(randVec[K * x + i], randVec[K * y + i]);
177,376✔
539
        });
540

541
#endif // __AVX2__
542

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

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

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

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

579
    return v;
154✔
580
}
581

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

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

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

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

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

611
    count level = 0;
2,075✔
612
    do {
613
        count nextLevelDecr = 0;
2,165✔
614

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

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

621
            G->forNeighborsOf(x, [&](const node y) {
2,824✔
622
                if (visited[y])
27,402✔
623
                    return;
4,124✔
624

625
                const count distY = curDistance[y];
23,278✔
626

627
                if (pruned)
23,278✔
628
                    --nodesLeftToExploreAtDistance[distY];
21,662✔
629

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

632
                    curDistance2[y] = distY;
9,020✔
633
                    curNearest2[y] = curNearest[y];
9,020✔
634
                    curDistance[y] = curDistance[x] + 1;
9,020✔
635
                    curNearest[y] = v;
9,020✔
636
                    q2.push(y);
9,020✔
637
                    if (pruned)
9,020✔
638
                        nextLevelDecr += static_cast<count>(distY - curDistance[y]);
8,800✔
639
                } else if (curNearest[x] == v && curDistance2[y] > curDistance[x] + 1) {
14,258✔
640
                    curDistance2[y] = curDistance[x] + 1;
10,348✔
641
                    curNearest2[y] = v;
10,348✔
642
                    q2.push(y);
10,348✔
643
                } else if (curNearest2[x] == v && curDistance2[y] > curDistance2[x] + 1) {
3,910✔
644
                    curDistance2[y] = curDistance2[x] + 1;
18✔
645
                    curNearest2[y] = v;
18✔
646
                    q2.push(y);
18✔
647
                }
648

649
                visited[y] = true;
23,278✔
650
            });
651

652
        } while (!q1.empty());
2,824✔
653

654
        if (pruned) {
2,165✔
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,105✔
665
            for (count dist = level + 3; dist < diam; ++dist)
4,160✔
666
                decrUB += nodesLeftToExploreAtDistance[dist] * (dist - level - 2);
2,055✔
667

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

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

677
    return decr;
45✔
678
}
2,075✔
679

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

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

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

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

703
        G->forNeighborsOf(x, [&](const node y, const edgeweight weight) {
30,152✔
704
            if (curDistance[y] > curDistance[x] + weight) {
298,194✔
705
                // Nearest nodes could have been already updated
706
                if (curNearest[y] != v) {
11,998✔
707
                    curNearest2[y] = curNearest[y];
11,874✔
708
                    curNearest[y] = v;
11,874✔
709
                    curDistance2[y] = curDistance[y];
11,874✔
710
                }
711
                curDistance[y] = curDistance[x] + weight;
11,998✔
712
                curHeap.update(y);
11,998✔
713
            } else if (curNearest[x] == v && curNearest[y] != v
374,722✔
714
                       && curDistance2[y] > curDistance[x] + weight) {
374,722✔
715
                curDistance2[y] = curDistance[x] + weight;
9,178✔
716
                curNearest2[y] = v;
9,178✔
717
                curHeap.update(y);
9,178✔
718
            } else if (curNearest2[x] == v && curNearest[y] != v
474,688✔
719
                       && curDistance2[y] > curDistance2[x] + weight) {
474,688✔
720
                curDistance2[y] = curDistance2[x] + weight;
16,302✔
721
                curNearest2[y] = v;
16,302✔
722
                curHeap.update(y);
16,302✔
723
            }
724
        });
298,194✔
725

726
        if (curHeap.empty())
30,152✔
727
            break;
353✔
728

729
        if (pruned) {
29,799✔
730
            const count n = G->numberOfNodes();
28,989✔
731
            const node top = curHeap.top();
28,989✔
732
            const edgeweight topDist = distance[top];
28,989✔
733
            assert(curDistance2[top] >= topDist);
28,989✔
734
            edgeweight decrUB =
735
                decr
736
                + static_cast<edgeweight>(curNearest[top] == v) * (curDistance2[top] - topDist);
28,989✔
737
            const count nextIdx =
738
                (std::lower_bound(
28,989✔
739
                     nodesSortedByDistance.begin(), nodesSortedByDistance.end(), top,
740
                     [&](const node a, const node b) { return distance[a] < distance[b]; })
195,795✔
741
                 - nodesSortedByDistance.begin())
57,978✔
742
                + 1;
28,989✔
743

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

750
            if (insuffcientDecrease(decrUB, increaseU)) {
28,989✔
751
                curHeap.clear();
1,894✔
752
                return invalidDecr;
1,894✔
753
            }
754
        }
755
    } while (true);
27,905✔
756

757
    return decr;
353✔
758
}
759

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

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

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

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

789
    // Update the distance of y
790
    const auto processEdge = [&](node x, node y, count weight) -> void {
34,401✔
791
        if (nearest[x] != nearest[y]) {
34,401✔
792
            if (distance2[y] > distance[x] + weight) {
20,753✔
793
                distance2[y] = distance[x] + weight;
1,659✔
794
                nearest2[y] = nearest[x];
1,659✔
795
                pq.update(y);
1,659✔
796
            }
797
            // Checking 2nd distances
798
        } else if (distance2[x] < infDistance && distance2[y] > distance2[x] + weight) {
13,648✔
799
            distance2[y] = distance2[x] + weight;
660✔
800
            nearest2[y] = nearest2[x];
660✔
801
            pq.update(y);
660✔
802
            assert(nearest2[x] != nearest[y]);
660✔
803
        }
804
    };
34,436✔
805

806
    G->forEdges([&](node x, node y, edgeweight weight) {
35✔
807
        processEdge(x, y, static_cast<count>(weight));
26,881✔
808
        if (!G->isDirected())
26,881✔
809
            processEdge(y, x, static_cast<count>(weight));
7,520✔
810
    });
26,881✔
811

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

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

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

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

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

856
    // Update the distance of y
857
    const auto process = [&](node x, node y, edgeweight weight) -> void {
41,534✔
858
        if (nearest[x] != nearest[y]) {
41,534✔
859
            if (distance2[y] > distance[x] + weight) {
25,997✔
860
                distance2[y] = distance[x] + weight;
2,573✔
861
                nearest2[y] = nearest[x];
2,573✔
862
                pq.update(y);
2,573✔
863
            }
864
            // Checking 2nd distances
865
        } else if (distance2[x] < infDistance && distance2[y] > distance2[x] + weight) {
15,537✔
866
            distance2[y] = distance2[x] + weight;
1,508✔
867
            nearest2[y] = nearest2[x];
1,508✔
868
            pq.update(y);
1,508✔
869
            assert(nearest2[x] != nearest[y]);
1,508✔
870
        }
871
    };
41,576✔
872

873
    G->forEdges([&](node x, node y, edgeweight weight) {
42✔
874
        process(x, y, weight);
34,014✔
875
        if (!G->isDirected())
34,014✔
876
            process(y, x, weight);
7,520✔
877
    });
34,014✔
878

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

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

895
        sumOfDistancesGreaterThan[G->numberOfNodes() - 1] = distance[nodesSortedByDistance.back()];
42✔
896
        for (node i = G->numberOfNodes() - 1; i; --i)
4,200✔
897
            sumOfDistancesGreaterThan[i - 1] =
4,158✔
898
                sumOfDistancesGreaterThan[i] + distance[nodesSortedByDistance[i - 1]];
4,158✔
899
    }
900
}
42✔
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 {
60,038✔
907
        const auto thread = omp_get_thread_num();
4,282✔
908
        std::copy(distance.begin(), distance.end(), distanceGlobal[thread].begin());
4,282✔
909
        std::copy(distance2.begin(), distance2.end(), distance2Global[thread].begin());
4,282✔
910
        std::copy(nearest.begin(), nearest.end(), nearestGlobal[thread].begin());
4,282✔
911
        std::copy(nearest2.begin(), nearest2.end(), nearest2Global[thread].begin());
4,282✔
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();
154✔
930
        const WeightType increaseU = farnessIncrease[u];
154✔
931

932
        group.erase(u);
154✔
933
        updateAfterRemoval(u, true);
154✔
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();
154✔
941
        candidatesToAdd.build_heap(candidatesToAddVec.begin(), candidatesToAddVec.end());
154✔
942

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

946
#pragma omp parallel
154✔
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);
154✔
991

992
        if (thread != -1) { // There is a convenient swap
154✔
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);
80✔
1012
        const WeightType decreaseU = computeFarnessDecrease(u);
80✔
1013
        tlx::unused(decreaseU);
80✔
1014

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

1020
    } while (!candidatesToRemove.empty());
80✔
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