Skip to content

Commit 363ae1e

Browse files
Refactor to avoid duplicate functionality
1 parent 3b3cfc2 commit 363ae1e

16 files changed

Lines changed: 360 additions & 426 deletions
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <cstdint>
5+
#include <utility>
6+
7+
namespace bioimage_cpp::detail {
8+
9+
using NodeId = std::uint64_t;
10+
using Edge = std::pair<NodeId, NodeId>;
11+
12+
inline Edge edge_key(NodeId u, NodeId v) {
13+
if (v < u) {
14+
std::swap(u, v);
15+
}
16+
return {u, v};
17+
}
18+
19+
struct EdgeHash {
20+
std::size_t operator()(const Edge &edge) const {
21+
const auto first = static_cast<std::size_t>(edge.first);
22+
const auto second = static_cast<std::size_t>(edge.second);
23+
return first ^ (second + 0x9e3779b97f4a7c15ULL + (first << 6U) + (first >> 2U));
24+
}
25+
};
26+
27+
} // namespace bioimage_cpp::detail
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <cstdint>
5+
#include <vector>
6+
7+
namespace bioimage_cpp::detail {
8+
9+
// C-order strides for a row-major array of the given shape, in units of array
10+
// elements (not bytes). The innermost (last) axis has stride 1.
11+
inline std::vector<std::ptrdiff_t> c_order_strides(const std::vector<std::ptrdiff_t> &shape) {
12+
std::vector<std::ptrdiff_t> strides(shape.size(), 1);
13+
for (std::ptrdiff_t axis = static_cast<std::ptrdiff_t>(shape.size()) - 2; axis >= 0; --axis) {
14+
strides[static_cast<std::size_t>(axis)] =
15+
strides[static_cast<std::size_t>(axis + 1)] *
16+
shape[static_cast<std::size_t>(axis + 1)];
17+
}
18+
return strides;
19+
}
20+
21+
// Translate a flat node index by a per-axis offset on a row-major grid.
22+
//
23+
// Returns true when the offset keeps the result inside the grid, in which case
24+
// `target_out` is set to the neighbor's flat index. Returns false otherwise and
25+
// leaves `target_out` unchanged. `strides` must match `shape` and is typically
26+
// `c_order_strides(shape)`.
27+
inline bool valid_offset_target(
28+
const std::uint64_t node,
29+
const std::vector<std::ptrdiff_t> &offset,
30+
const std::vector<std::ptrdiff_t> &shape,
31+
const std::vector<std::ptrdiff_t> &strides,
32+
std::uint64_t &target_out
33+
) {
34+
std::int64_t target_signed = static_cast<std::int64_t>(node);
35+
for (std::size_t axis = 0; axis < shape.size(); ++axis) {
36+
const auto coord =
37+
static_cast<std::ptrdiff_t>(node / static_cast<std::uint64_t>(strides[axis])) %
38+
shape[axis];
39+
const auto neighbor = coord + offset[axis];
40+
if (neighbor < 0 || neighbor >= shape[axis]) {
41+
return false;
42+
}
43+
target_signed += static_cast<std::int64_t>(offset[axis] * strides[axis]);
44+
}
45+
target_out = static_cast<std::uint64_t>(target_signed);
46+
return true;
47+
}
48+
49+
inline bool is_valid_grid_edge(
50+
const std::uint64_t node,
51+
const std::vector<std::ptrdiff_t> &offset,
52+
const std::vector<std::ptrdiff_t> &shape,
53+
const std::vector<std::ptrdiff_t> &strides
54+
) {
55+
std::uint64_t unused = 0;
56+
return valid_offset_target(node, offset, shape, strides, unused);
57+
}
58+
59+
} // namespace bioimage_cpp::detail
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <cstdint>
5+
#include <unordered_map>
6+
#include <vector>
7+
8+
namespace bioimage_cpp::detail {
9+
10+
// Map an arbitrary labeling to the dense range [0, k) preserving the first
11+
// occurrence order of each distinct input label.
12+
inline std::vector<std::uint64_t> dense_relabel(const std::vector<std::uint64_t> &labels) {
13+
std::unordered_map<std::uint64_t, std::uint64_t> relabeling;
14+
std::vector<std::uint64_t> result(labels.size());
15+
for (std::size_t index = 0; index < labels.size(); ++index) {
16+
auto found = relabeling.find(labels[index]);
17+
if (found == relabeling.end()) {
18+
found = relabeling.emplace(labels[index], static_cast<std::uint64_t>(relabeling.size())).first;
19+
}
20+
result[index] = found->second;
21+
}
22+
return result;
23+
}
24+
25+
} // namespace bioimage_cpp::detail
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#pragma once
2+
3+
#include <algorithm>
4+
#include <cstddef>
5+
#include <thread>
6+
#include <utility>
7+
#include <vector>
8+
9+
namespace bioimage_cpp::detail {
10+
11+
inline std::size_t normalize_thread_count(
12+
const std::size_t requested,
13+
const std::size_t number_of_work_items
14+
) {
15+
if (number_of_work_items == 0) {
16+
return 1;
17+
}
18+
std::size_t n_threads = requested;
19+
if (n_threads == 0) {
20+
n_threads = std::thread::hardware_concurrency();
21+
if (n_threads == 0) {
22+
n_threads = 1;
23+
}
24+
}
25+
return std::max<std::size_t>(1, std::min(n_threads, number_of_work_items));
26+
}
27+
28+
// Split [0, number_of_work_items) into n_threads contiguous chunks and invoke
29+
// `chunk(thread_id, begin, end)` on each chunk. Thread 0 runs on the calling
30+
// thread; threads 1..n_threads-1 run in parallel std::threads. The caller is
31+
// responsible for picking n_threads via normalize_thread_count and for the
32+
// thread safety of `chunk`.
33+
template <class Chunk>
34+
void parallel_for_chunks(
35+
const std::size_t n_threads,
36+
const std::size_t number_of_work_items,
37+
Chunk &&chunk
38+
) {
39+
const auto bounds = [&](const std::size_t thread_id) {
40+
const auto begin = thread_id * number_of_work_items / n_threads;
41+
const auto end = (thread_id + 1) * number_of_work_items / n_threads;
42+
return std::pair<std::size_t, std::size_t>{begin, end};
43+
};
44+
45+
std::vector<std::thread> threads;
46+
threads.reserve(n_threads > 0 ? n_threads - 1 : 0);
47+
for (std::size_t thread_id = 1; thread_id < n_threads; ++thread_id) {
48+
const auto [begin, end] = bounds(thread_id);
49+
threads.emplace_back([thread_id, begin, end, &chunk]() {
50+
chunk(thread_id, begin, end);
51+
});
52+
}
53+
const auto [begin, end] = bounds(0);
54+
chunk(0, begin, end);
55+
for (auto &thread : threads) {
56+
thread.join();
57+
}
58+
}
59+
60+
} // namespace bioimage_cpp::detail
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <cstdint>
5+
#include <numeric>
6+
#include <utility>
7+
#include <vector>
8+
9+
namespace bioimage_cpp::detail {
10+
11+
// Disjoint-set / union-find with path compression and union-by-rank.
12+
//
13+
// Three merge variants are provided so both rank-driven and caller-driven
14+
// merge directions are expressible:
15+
// * merge(u, v): find both, then union by rank.
16+
// * merge_to(stable, removed): find both, then force `stable` to be the
17+
// parent regardless of rank. The caller picks
18+
// which root survives.
19+
// * unite_roots(a, b): preconditions: a and b are roots and a != b. Union
20+
// by rank without an additional find().
21+
class UnionFind {
22+
public:
23+
explicit UnionFind(const std::size_t size) : parents_(size), ranks_(size, 0) {
24+
std::iota(parents_.begin(), parents_.end(), std::uint64_t{0});
25+
}
26+
27+
std::uint64_t find(const std::uint64_t node) {
28+
auto current = static_cast<std::size_t>(node);
29+
while (parents_[current] != current) {
30+
parents_[current] = parents_[parents_[current]];
31+
current = static_cast<std::size_t>(parents_[current]);
32+
}
33+
return static_cast<std::uint64_t>(current);
34+
}
35+
36+
std::uint64_t merge(std::uint64_t first, std::uint64_t second) {
37+
first = find(first);
38+
second = find(second);
39+
if (first == second) {
40+
return first;
41+
}
42+
return unite_roots(first, second);
43+
}
44+
45+
std::uint64_t merge_to(std::uint64_t stable, std::uint64_t removed) {
46+
stable = find(stable);
47+
removed = find(removed);
48+
if (stable == removed) {
49+
return stable;
50+
}
51+
parents_[static_cast<std::size_t>(removed)] = stable;
52+
if (ranks_[static_cast<std::size_t>(stable)] <= ranks_[static_cast<std::size_t>(removed)]) {
53+
ranks_[static_cast<std::size_t>(stable)] = ranks_[static_cast<std::size_t>(removed)] + 1;
54+
}
55+
return stable;
56+
}
57+
58+
std::uint64_t unite_roots(std::uint64_t first, std::uint64_t second) {
59+
if (ranks_[static_cast<std::size_t>(first)] < ranks_[static_cast<std::size_t>(second)]) {
60+
std::swap(first, second);
61+
}
62+
parents_[static_cast<std::size_t>(second)] = first;
63+
if (ranks_[static_cast<std::size_t>(first)] == ranks_[static_cast<std::size_t>(second)]) {
64+
++ranks_[static_cast<std::size_t>(first)];
65+
}
66+
return first;
67+
}
68+
69+
[[nodiscard]] std::size_t size() const {
70+
return parents_.size();
71+
}
72+
73+
private:
74+
std::vector<std::uint64_t> parents_;
75+
std::vector<std::uint64_t> ranks_;
76+
};
77+
78+
} // namespace bioimage_cpp::detail

include/bioimage_cpp/graph/connected_components.hxx

Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,17 @@
11
#pragma once
22

3+
#include "bioimage_cpp/detail/union_find.hxx"
34
#include "bioimage_cpp/graph/undirected_graph.hxx"
45

56
#include <cstddef>
67
#include <cstdint>
7-
#include <numeric>
88
#include <unordered_map>
9-
#include <utility>
109
#include <vector>
1110

1211
namespace bioimage_cpp::graph {
1312

14-
class UnionFind {
15-
public:
16-
explicit UnionFind(const std::size_t size) : parents_(size), ranks_(size, 0) {
17-
std::iota(parents_.begin(), parents_.end(), std::uint64_t{0});
18-
}
19-
20-
std::uint64_t find(const std::uint64_t node) {
21-
if (parents_[static_cast<std::size_t>(node)] != node) {
22-
parents_[static_cast<std::size_t>(node)] = find(parents_[static_cast<std::size_t>(node)]);
23-
}
24-
return parents_[static_cast<std::size_t>(node)];
25-
}
26-
27-
void merge(std::uint64_t first, std::uint64_t second) {
28-
first = find(first);
29-
second = find(second);
30-
if (first == second) {
31-
return;
32-
}
33-
if (ranks_[static_cast<std::size_t>(first)] < ranks_[static_cast<std::size_t>(second)]) {
34-
std::swap(first, second);
35-
}
36-
parents_[static_cast<std::size_t>(second)] = first;
37-
if (ranks_[static_cast<std::size_t>(first)] == ranks_[static_cast<std::size_t>(second)]) {
38-
++ranks_[static_cast<std::size_t>(first)];
39-
}
40-
}
41-
42-
void merge_to(std::uint64_t stable, std::uint64_t removed) {
43-
stable = find(stable);
44-
removed = find(removed);
45-
if (stable == removed) {
46-
return;
47-
}
48-
parents_[static_cast<std::size_t>(removed)] = stable;
49-
if (ranks_[static_cast<std::size_t>(stable)] <= ranks_[static_cast<std::size_t>(removed)]) {
50-
ranks_[static_cast<std::size_t>(stable)] = ranks_[static_cast<std::size_t>(removed)] + 1;
51-
}
52-
}
53-
54-
private:
55-
std::vector<std::uint64_t> parents_;
56-
std::vector<std::uint64_t> ranks_;
57-
};
58-
5913
inline std::vector<std::uint64_t> dense_labels_from_union_find(
60-
UnionFind &sets,
14+
detail::UnionFind &sets,
6115
const std::uint64_t number_of_nodes
6216
) {
6317
std::unordered_map<std::uint64_t, std::uint64_t> relabeling;
@@ -77,7 +31,7 @@ inline std::vector<std::uint64_t> connected_components(
7731
const UndirectedGraph &graph,
7832
const std::uint8_t *edge_mask = nullptr
7933
) {
80-
UnionFind sets(static_cast<std::size_t>(graph.number_of_nodes()));
34+
detail::UnionFind sets(static_cast<std::size_t>(graph.number_of_nodes()));
8135
for (std::uint64_t edge = 0; edge < graph.number_of_edges(); ++edge) {
8236
if (edge_mask != nullptr && edge_mask[static_cast<std::size_t>(edge)] == 0) {
8337
continue;

0 commit comments

Comments
 (0)