Skip to content

Commit 3b3cfc2

Browse files
Optimization round 1
1 parent 6c4adae commit 3b3cfc2

7 files changed

Lines changed: 85 additions & 33 deletions

File tree

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,8 @@ nanobind_add_module(_core
2121

2222
target_include_directories(_core PRIVATE include)
2323
target_compile_features(_core PRIVATE cxx_std_20)
24+
target_compile_options(_core PRIVATE
25+
$<$<NOT:$<CONFIG:Debug>>:-O3>
26+
)
2427

2528
install(TARGETS _core LIBRARY DESTINATION bioimage_cpp)

development/graph/multicut/_compatibility.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ def time_call(function: Callable[[], np.ndarray], repeats: int):
6767
return timings, result
6868

6969

70+
def optimize_bic_solver(make_bic_solver, objective):
71+
objective.reset_labels()
72+
return make_bic_solver().optimize(objective)
73+
74+
7075
def bic_energy(graph, costs: np.ndarray, labels: np.ndarray) -> float:
7176
import bioimage_cpp as bic
7277

@@ -89,10 +94,11 @@ def run_comparison(
8994
import nifty.graph.opt.multicut as nmc
9095

9196
bic_graph, nifty_graph, costs = load_problem(args.path, timeout=args.timeout)
97+
bic_objective = bic.graph.MulticutObjective(bic_graph, costs)
9298
nifty_objective = nmc.multicutObjective(nifty_graph, costs)
9399

94100
bic_timings, bic_labels = time_call(
95-
lambda: make_bic_solver().optimize(bic.graph.MulticutObjective(bic_graph, costs)),
101+
lambda: optimize_bic_solver(make_bic_solver, bic_objective),
96102
args.repeats,
97103
)
98104
nifty_timings, nifty_labels = time_call(

include/bioimage_cpp/graph/connected_components.hxx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ public:
3939
}
4040
}
4141

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+
4254
private:
4355
std::vector<std::uint64_t> parents_;
4456
std::vector<std::uint64_t> ranks_;

include/bioimage_cpp/graph/multicut/detail.hxx

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,38 @@
1717

1818
namespace bioimage_cpp::graph::multicut::detail {
1919

20+
struct DynamicEdge {
21+
double weight = 0.0;
22+
std::uint64_t edition = 0;
23+
};
24+
2025
struct DynamicGraph {
2126
explicit DynamicGraph(const std::size_t size) : adjacency(size), constraints(size), alive(size, true), alive_count(size) {
2227
}
2328

29+
explicit DynamicGraph(const UndirectedGraph &graph)
30+
: adjacency(static_cast<std::size_t>(graph.number_of_nodes())),
31+
constraints(static_cast<std::size_t>(graph.number_of_nodes())),
32+
alive(static_cast<std::size_t>(graph.number_of_nodes()), true),
33+
alive_count(static_cast<std::size_t>(graph.number_of_nodes())) {
34+
for (std::uint64_t node = 0; node < graph.number_of_nodes(); ++node) {
35+
const auto degree = graph.node_adjacency(node).size();
36+
adjacency[static_cast<std::size_t>(node)].reserve(degree);
37+
}
38+
}
39+
2440
[[nodiscard]] bool edge_exists(const std::size_t u, const std::size_t v) const {
2541
return alive[u] && alive[v] && adjacency[u].find(v) != adjacency[u].end();
2642
}
2743

44+
[[nodiscard]] const DynamicEdge *edge(const std::size_t u, const std::size_t v) const {
45+
if (!alive[u] || !alive[v]) {
46+
return nullptr;
47+
}
48+
const auto found = adjacency[u].find(v);
49+
return found == adjacency[u].end() ? nullptr : &found->second;
50+
}
51+
2852
[[nodiscard]] bool has_constraint(const std::size_t u, const std::size_t v) const {
2953
return constraints[u].find(v) != constraints[u].end();
3054
}
@@ -34,9 +58,17 @@ struct DynamicGraph {
3458
constraints[v].insert(u);
3559
}
3660

37-
void set_edge(const std::size_t u, const std::size_t v, const double weight) {
38-
adjacency[u][v] = weight;
39-
adjacency[v][u] = weight;
61+
void set_initial_edge(const std::size_t u, const std::size_t v, const double weight) {
62+
adjacency[u][v] = DynamicEdge{weight, 0};
63+
adjacency[v][u] = DynamicEdge{weight, 0};
64+
}
65+
66+
std::uint64_t update_edge(const std::size_t u, const std::size_t v, const double weight) {
67+
const auto current = adjacency[u].find(v);
68+
const auto edition = current == adjacency[u].end() ? 0 : current->second.edition + 1;
69+
adjacency[u][v] = DynamicEdge{weight, edition};
70+
adjacency[v][u] = DynamicEdge{weight, edition};
71+
return edition;
4072
}
4173

4274
void remove_node(const std::size_t node) {
@@ -50,7 +82,7 @@ struct DynamicGraph {
5082
--alive_count;
5183
}
5284

53-
std::vector<std::unordered_map<std::size_t, double>> adjacency;
85+
std::vector<std::unordered_map<std::size_t, DynamicEdge>> adjacency;
5486
std::vector<std::unordered_set<std::size_t>> constraints;
5587
std::vector<bool> alive;
5688
std::size_t alive_count;
@@ -71,7 +103,6 @@ inline void initialize_dynamic_graph(
71103
const UndirectedGraph &graph,
72104
const std::vector<double> &costs,
73105
DynamicGraph &dynamic_graph,
74-
std::vector<std::unordered_map<std::size_t, std::uint64_t>> &editions,
75106
std::priority_queue<QueueEdge> &queue,
76107
const bool absolute_priority,
77108
const bool add_noise = false,
@@ -88,9 +119,7 @@ inline void initialize_dynamic_graph(
88119
if (add_noise) {
89120
weight += noise(generator);
90121
}
91-
dynamic_graph.set_edge(u, v, weight);
92-
editions[u][v] = 0;
93-
editions[v][u] = 0;
122+
dynamic_graph.set_initial_edge(u, v, weight);
94123
queue.push(QueueEdge{u, v, absolute_priority ? std::abs(weight) : weight, 0});
95124
}
96125
}
@@ -108,7 +137,6 @@ inline std::size_t stop_node_count(const UndirectedGraph &graph, const double no
108137
inline std::size_t merge_dynamic_nodes(
109138
DynamicGraph &dynamic_graph,
110139
UnionFind &sets,
111-
std::vector<std::unordered_map<std::size_t, std::uint64_t>> &editions,
112140
std::priority_queue<QueueEdge> &queue,
113141
std::size_t u,
114142
std::size_t v,
@@ -119,28 +147,28 @@ inline std::size_t merge_dynamic_nodes(
119147
if (u == v) {
120148
return u;
121149
}
122-
sets.merge(u, v);
123-
const auto stable = static_cast<std::size_t>(sets.find(u));
124-
const auto removed = (stable == u) ? v : u;
125-
std::vector<std::pair<std::size_t, double>> neighbors(
150+
auto stable = u;
151+
auto removed = v;
152+
if (dynamic_graph.adjacency[stable].size() < dynamic_graph.adjacency[removed].size()) {
153+
std::swap(stable, removed);
154+
}
155+
sets.merge_to(stable, removed);
156+
std::vector<std::pair<std::size_t, DynamicEdge>> neighbors(
126157
dynamic_graph.adjacency[removed].begin(),
127158
dynamic_graph.adjacency[removed].end()
128159
);
129160
dynamic_graph.adjacency[stable].erase(removed);
130161
dynamic_graph.constraints[stable].erase(removed);
131-
for (const auto &[neighbor, removed_weight] : neighbors) {
162+
for (const auto &[neighbor, removed_edge] : neighbors) {
132163
if (neighbor == stable) {
133164
continue;
134165
}
135-
const auto current = dynamic_graph.edge_exists(stable, neighbor)
136-
? dynamic_graph.adjacency[stable][neighbor]
137-
: 0.0;
138-
const auto merged_weight = current + removed_weight;
166+
const auto current = dynamic_graph.edge(stable, neighbor);
167+
const auto merged_weight = (current == nullptr ? 0.0 : current->weight) + removed_edge.weight;
139168
if (dynamic_graph.has_constraint(removed, neighbor)) {
140169
dynamic_graph.add_constraint(stable, neighbor);
141170
}
142-
dynamic_graph.set_edge(stable, neighbor, merged_weight);
143-
const auto edition = ++editions[std::min(stable, neighbor)][std::max(stable, neighbor)];
171+
const auto edition = dynamic_graph.update_edge(stable, neighbor, merged_weight);
144172
queue.push(QueueEdge{
145173
std::min(stable, neighbor),
146174
std::max(stable, neighbor),

include/bioimage_cpp/graph/multicut/greedy_additive.hxx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,28 @@ inline std::vector<std::uint64_t> greedy_additive(
2222
const double sigma
2323
) {
2424
validate_costs(graph, costs);
25-
detail::DynamicGraph dynamic_graph(static_cast<std::size_t>(graph.number_of_nodes()));
25+
detail::DynamicGraph dynamic_graph(graph);
2626
UnionFind sets(static_cast<std::size_t>(graph.number_of_nodes()));
27-
std::vector<std::unordered_map<std::size_t, std::uint64_t>> editions(static_cast<std::size_t>(graph.number_of_nodes()));
2827
std::priority_queue<detail::QueueEdge> queue;
29-
detail::initialize_dynamic_graph(graph, costs, dynamic_graph, editions, queue, false, add_noise, seed, sigma);
28+
detail::initialize_dynamic_graph(graph, costs, dynamic_graph, queue, false, add_noise, seed, sigma);
3029

3130
while (!queue.empty() && dynamic_graph.alive_count > 1) {
3231
auto edge = queue.top();
3332
queue.pop();
3433
const auto u = std::min(edge.u, edge.v);
3534
const auto v = std::max(edge.u, edge.v);
36-
if (!dynamic_graph.edge_exists(u, v) || edge.edition < editions[u][v]) {
35+
const auto *dynamic_edge = dynamic_graph.edge(u, v);
36+
if (dynamic_edge == nullptr || edge.edition < dynamic_edge->edition) {
3737
continue;
3838
}
39-
const auto weight = dynamic_graph.adjacency[u][v];
39+
const auto weight = dynamic_edge->weight;
4040
if (weight <= weight_stop) {
4141
break;
4242
}
4343
if (node_num_stop > 0.0 && dynamic_graph.alive_count <= detail::stop_node_count(graph, node_num_stop)) {
4444
break;
4545
}
46-
detail::merge_dynamic_nodes(dynamic_graph, sets, editions, queue, u, v, false);
46+
detail::merge_dynamic_nodes(dynamic_graph, sets, queue, u, v, false);
4747
}
4848
return detail::labels_from_sets(sets, graph);
4949
}

include/bioimage_cpp/graph/multicut/greedy_fixation.hxx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,21 @@ inline std::vector<std::uint64_t> greedy_fixation(
2020
const double node_num_stop
2121
) {
2222
validate_costs(graph, costs);
23-
detail::DynamicGraph dynamic_graph(static_cast<std::size_t>(graph.number_of_nodes()));
23+
detail::DynamicGraph dynamic_graph(graph);
2424
UnionFind sets(static_cast<std::size_t>(graph.number_of_nodes()));
25-
std::vector<std::unordered_map<std::size_t, std::uint64_t>> editions(static_cast<std::size_t>(graph.number_of_nodes()));
2625
std::priority_queue<detail::QueueEdge> queue;
27-
detail::initialize_dynamic_graph(graph, costs, dynamic_graph, editions, queue, true);
26+
detail::initialize_dynamic_graph(graph, costs, dynamic_graph, queue, true);
2827

2928
while (!queue.empty() && dynamic_graph.alive_count > 1) {
3029
auto edge = queue.top();
3130
queue.pop();
3231
const auto u = std::min(edge.u, edge.v);
3332
const auto v = std::max(edge.u, edge.v);
34-
if (!dynamic_graph.edge_exists(u, v) || edge.edition < editions[u][v]) {
33+
const auto *dynamic_edge = dynamic_graph.edge(u, v);
34+
if (dynamic_edge == nullptr || edge.edition < dynamic_edge->edition) {
3535
continue;
3636
}
37-
const auto weight = dynamic_graph.adjacency[u][v];
37+
const auto weight = dynamic_edge->weight;
3838
if (std::abs(weight) <= weight_stop) {
3939
break;
4040
}
@@ -45,7 +45,7 @@ inline std::vector<std::uint64_t> greedy_fixation(
4545
continue;
4646
}
4747
if (weight > 0.0) {
48-
detail::merge_dynamic_nodes(dynamic_graph, sets, editions, queue, u, v, true);
48+
detail::merge_dynamic_nodes(dynamic_graph, sets, queue, u, v, true);
4949
} else if (weight < 0.0) {
5050
dynamic_graph.add_constraint(u, v);
5151
}

src/bioimage_cpp/graph/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,9 @@ def __init__(
371371
self.number_of_threads = _normalize_number_of_threads(number_of_threads)
372372

373373
def optimize(self, objective: MulticutObjective) -> np.ndarray:
374+
if self.fallthrough_solver is None and isinstance(self.sub_solver, GreedyAdditiveMulticut):
375+
return self.sub_solver.optimize(objective)
376+
374377
component_labels = connected_components(
375378
objective.graph,
376379
edge_mask=objective.edge_costs > 0.0,

0 commit comments

Comments
 (0)