Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
tree_decomposition_test.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
31
37# include <gtest/gtest.h>
38
39# include <Tree_Decomposition.H>
40# include <tpl_agraph.H>
41# include <tpl_graph.H>
42# include <tpl_sgraph.H>
43
44# include <cstddef>
45# include <limits>
46# include <random>
47# include <utility>
48# include <vector>
49
50# include "test_graph_helpers.h"
51
52using namespace Aleph;
54
55namespace
56{
57 template <class GT>
60 const std::vector<int> & values,
61 const std::vector<std::pair<size_t, size_t>> & edges)
62 {
63 using Node = typename GT::Node;
64
65 auto nodes = Array<Node *>::create(values.size());
66 for (size_t i = 0; i < values.size(); ++i)
67 nodes(i) = g.insert_node(values[i]);
68
69 for (const auto & [u, v] : edges)
70 g.insert_arc(nodes(u), nodes(v), 1);
71
72 return nodes;
73 }
74
75 std::vector<std::pair<size_t, size_t>>
76 random_tree_edges(const size_t n, std::mt19937 & rng)
77 {
78 std::vector<std::pair<size_t, size_t>> edges;
79 edges.reserve(n > 0 ? n - 1 : 0);
80
81 for (size_t v = 1; v < n; ++v)
82 {
83 std::uniform_int_distribution<size_t> pick(0, v - 1);
84 edges.emplace_back(v, pick(rng));
85 }
86
87 return edges;
88 }
89
90 class Rooted_Oracle
91 {
92 static constexpr size_t NONE = std::numeric_limits<size_t>::max();
93
94 size_t n_ = 0;
95 size_t root_ = 0;
96
97 std::vector<std::vector<size_t>> adj_;
98 std::vector<std::vector<size_t>> children_;
99 std::vector<size_t> parent_;
100 std::vector<size_t> depth_;
101
102 public:
103 Rooted_Oracle(const size_t n,
104 const std::vector<std::pair<size_t, size_t>> & edges,
105 const size_t root)
106 : n_(n),
107 root_(root),
108 adj_(n),
109 children_(n),
110 parent_(n, NONE),
111 depth_(n, 0)
112 {
113 for (const auto & [u, v] : edges)
114 {
115 adj_[u].push_back(v);
116 adj_[v].push_back(u);
117 }
118
119 std::vector<char> visited(n_, 0);
120 struct Frame
121 {
122 size_t u;
123 size_t p;
124 size_t next;
125 };
126
127 std::vector<Frame> stack;
128 stack.push_back({root_, NONE, 0});
129 visited[root_] = 1;
130 parent_[root_] = NONE;
131 depth_[root_] = 0;
132
133 while (not stack.empty())
134 {
135 auto & fr = stack.back();
136 if (fr.next == adj_[fr.u].size())
137 {
138 stack.pop_back();
139 continue;
140 }
141
142 const size_t v = adj_[fr.u][fr.next++];
143 if (v == fr.p)
144 continue;
145
146 if (visited[v])
147 continue;
148
149 visited[v] = 1;
150 parent_[v] = fr.u;
151 depth_[v] = depth_[fr.u] + 1;
152 children_[fr.u].push_back(v);
153 stack.push_back({v, fr.u, 0});
154 }
155 }
156
157 [[nodiscard]] size_t lca(size_t u, size_t v) const
158 {
159 while (depth_[u] > depth_[v])
160 u = parent_[u];
161
162 while (depth_[v] > depth_[u])
163 v = parent_[v];
164
165 while (u != v)
166 {
167 u = parent_[u];
168 v = parent_[v];
169 }
170
171 return u;
172 }
173
174 [[nodiscard]] size_t distance(const size_t u, const size_t v) const
175 {
176 const size_t a = lca(u, v);
177 return depth_[u] + depth_[v] - 2 * depth_[a];
178 }
179
180 [[nodiscard]] int path_sum(const std::vector<int> & values,
181 size_t u,
182 size_t v) const
183 {
184 int sum = 0;
185
186 while (depth_[u] > depth_[v])
187 {
188 sum += values[u];
189 u = parent_[u];
190 }
191
192 while (depth_[v] > depth_[u])
193 {
194 sum += values[v];
195 v = parent_[v];
196 }
197
198 while (u != v)
199 {
200 sum += values[u];
201 sum += values[v];
202 u = parent_[u];
203 v = parent_[v];
204 }
205
206 sum += values[u];
207 return sum;
208 }
209
210 [[nodiscard]] int subtree_sum(const std::vector<int> & values,
211 const size_t u) const
212 {
213 int sum = 0;
214 std::vector<size_t> stack;
215 stack.push_back(u);
216
217 while (not stack.empty())
218 {
219 const size_t x = stack.back();
220 stack.pop_back();
221 sum += values[x];
222
223 for (const auto ch : children_[x])
224 stack.push_back(ch);
225 }
226
227 return sum;
228 }
229 };
230
231 template <class GT>
232 class TreeDecompositionTypedTest : public ::testing::Test
233 {
234 };
235
236 using GraphBackends = ::testing::Types<
240
241 TYPED_TEST_SUITE(TreeDecompositionTypedTest, GraphBackends);
242
243}
244
245
246TYPED_TEST(TreeDecompositionTypedTest, EmptyGraph)
247{
248 using Graph = TypeParam;
249
250 Graph g;
251
253 EXPECT_TRUE(hld.is_empty());
254 EXPECT_EQ(hld.size(), 0u);
255
258 EXPECT_THROW(q.query_path_id(0, 0), std::domain_error);
259
261 EXPECT_TRUE(cd.is_empty());
262 EXPECT_EQ(cd.size(), 0u);
263 EXPECT_EQ(cd.centroid_root(), nullptr);
265 [](const size_t,
266 const size_t,
267 const size_t) {}),
268 std::domain_error);
269}
270
271TYPED_TEST(TreeDecompositionTypedTest, InvalidGraphsThrow)
272{
273 using Graph = TypeParam;
274
275 {
276 Graph g;
277 auto * n0 = g.insert_node(0);
278 auto * n1 = g.insert_node(1);
279 auto * n2 = g.insert_node(2);
280 g.insert_arc(n0, n1, 1);
281 g.insert_arc(n1, n2, 1);
282 g.insert_arc(n2, n0, 1); // cycle
283
284 EXPECT_THROW((Heavy_Light_Decomposition<Graph>(g, n0)), std::domain_error);
285 EXPECT_THROW((Centroid_Decomposition<Graph>(g, n0)), std::domain_error);
286 }
287
288 {
289 Graph g;
290 auto * n0 = g.insert_node(0);
291 auto * n1 = g.insert_node(1);
292 auto * n2 = g.insert_node(2);
293 auto * n3 = g.insert_node(3);
294 g.insert_arc(n0, n1, 1);
295 g.insert_arc(n2, n3, 1); // disconnected
296
297 EXPECT_THROW((Heavy_Light_Decomposition<Graph>(g, n0)), std::domain_error);
298 EXPECT_THROW((Centroid_Decomposition<Graph>(g, n0)), std::domain_error);
299 }
300
301 {
302 Graph g;
303 auto * n0 = g.insert_node(0);
304 g.insert_arc(n0, n0, 1); // self-loop
305
306 EXPECT_THROW((Heavy_Light_Decomposition<Graph>(g, n0)), std::domain_error);
307 EXPECT_THROW((Centroid_Decomposition<Graph>(g, n0)), std::domain_error);
308 }
309
310 {
311 Graph g;
312 auto * n0 = g.insert_node(0);
313 auto * n1 = g.insert_node(1);
314 g.insert_arc(n0, n1, 1);
315 g.insert_arc(n0, n1, 1); // parallel edges
316
317 EXPECT_THROW((Heavy_Light_Decomposition<Graph>(g, n0)), std::domain_error);
318 EXPECT_THROW((Centroid_Decomposition<Graph>(g, n0)), std::domain_error);
319 }
320}
321
322TYPED_TEST(TreeDecompositionTypedTest, ArcFilterSupport)
323{
324 using Graph = TypeParam;
325
326 Graph g;
327 auto * n0 = g.insert_node(5);
328 auto * n1 = g.insert_node(3);
329 auto * n2 = g.insert_node(4);
330 auto * n3 = g.insert_node(7);
331
332 // Positive arcs define a valid tree.
333 g.insert_arc(n0, n1, 1);
334 g.insert_arc(n1, n2, 1);
335 g.insert_arc(n1, n3, 1);
336
337 // Negative arc exists in graph but is filtered out.
338 g.insert_arc(n0, n2, -8);
339
341 EXPECT_EQ(hld.size(), 4u);
342 EXPECT_EQ(hld.distance(n2, n3), 2u);
343
345 q(g, n0, 0, Aleph::plus<int>(), Positive_Arc_Filter());
346
347 EXPECT_EQ(q.query_path(n2, n3), 3 + 4 + 7);
348
350 EXPECT_EQ(cd.size(), 4u);
351 EXPECT_NE(cd.centroid_root(), nullptr);
352}
353
354TYPED_TEST(TreeDecompositionTypedTest, DeterministicHeavyLightQueries)
355{
356 using Graph = TypeParam;
357
358 const std::vector<int> values = {5, 3, 4, 7, 2, 6, 1};
359 const std::vector<std::pair<size_t, size_t>> edges = {
360 {0, 1}, {0, 2}, {1, 3}, {1, 4}, {2, 5}, {5, 6}};
361
362 Graph g;
363 auto nodes = build_graph_with_values(g, values, edges);
364
365 Rooted_Oracle oracle(values.size(), edges, 0);
366
369
370 for (size_t u = 0; u < values.size(); ++u)
371 for (size_t v = 0; v < values.size(); ++v)
372 {
373 EXPECT_EQ(hld.lca(nodes(u), nodes(v)), nodes(oracle.lca(u, v)));
374 EXPECT_EQ(hld.distance(nodes(u), nodes(v)), oracle.distance(u, v));
375 EXPECT_EQ(q.query_path(nodes(u), nodes(v)), oracle.path_sum(values, u, v));
376 }
377
378 for (size_t u = 0; u < values.size(); ++u)
379 {
380 const size_t id = hld.id_of(nodes(u));
381 const auto range = hld.subtree_range_id(id);
382 EXPECT_EQ(range.right - range.left + 1, hld.subtree_size_of_id(id));
383 EXPECT_EQ(q.query_subtree(nodes(u)), oracle.subtree_sum(values, u));
384 }
385
386 std::vector<int> mutable_values = values;
387 q.update_node(nodes(3), 5); // 7 -> 12
388 mutable_values[3] += 5;
389 q.set_node(nodes(4), 10); // 2 -> 10
390 mutable_values[4] = 10;
391
392 for (size_t u = 0; u < values.size(); ++u)
393 for (size_t v = 0; v < values.size(); ++v)
395 oracle.path_sum(mutable_values, u, v));
396
397 for (size_t u = 0; u < values.size(); ++u)
398 EXPECT_EQ(q.query_subtree(nodes(u)), oracle.subtree_sum(mutable_values, u));
399}
400
401TYPED_TEST(TreeDecompositionTypedTest, RandomHeavyLightAgainstOracle)
402{
403 using Graph = TypeParam;
404
405 std::mt19937 rng(0xA13F2026u);
406 std::uniform_int_distribution<int> pick_value(-20, 20);
407
408 for (size_t trial = 0; trial < 20; ++trial)
409 {
410 std::uniform_int_distribution<size_t> pick_n(5, 60);
411 const size_t n = pick_n(rng);
412
413 auto edges = random_tree_edges(n, rng);
414
415 std::vector<int> values(n);
416 for (size_t i = 0; i < n; ++i)
417 values[i] = pick_value(rng);
418
419 Graph g;
420 auto nodes = build_graph_with_values(g, values, edges);
421
422 Rooted_Oracle oracle(n, edges, 0);
424 const auto & hld = q.decomposition();
425
426 std::uniform_int_distribution<size_t> pick_id(0, n - 1);
427
428 for (size_t it = 0; it < 250; ++it)
429 {
430 const int action = static_cast<int>(rng() % 100);
431
432 if (action < 35)
433 {
434 const size_t u = pick_id(rng);
435 if ((rng() & 1U) == 0U)
436 {
437 const int delta = pick_value(rng);
438 q.update_node(nodes(u), delta);
439 values[u] += delta;
440 }
441 else
442 {
443 const int val = pick_value(rng);
444 q.set_node(nodes(u), val);
445 values[u] = val;
446 }
447 }
448 else
449 {
450 const size_t u = pick_id(rng);
451 const size_t v = pick_id(rng);
452 const size_t w = pick_id(rng);
453
454 EXPECT_EQ(hld.lca(nodes(u), nodes(v)), nodes(oracle.lca(u, v)));
455 EXPECT_EQ(hld.distance(nodes(u), nodes(v)), oracle.distance(u, v));
456 EXPECT_EQ(q.query_path(nodes(u), nodes(v)), oracle.path_sum(values, u, v));
457 EXPECT_EQ(q.query_subtree(nodes(w)), oracle.subtree_sum(values, w));
458 }
459 }
460 }
461}
462
464{
465 using Graph = TypeParam;
466
467 const std::vector<int> values = {0, 1, 2, 3, 4, 5, 6, 7, 8};
468 const std::vector<std::pair<size_t, size_t>> edges = {
469 {0, 1}, {0, 2}, {1, 3}, {1, 4}, {2, 5}, {2, 6}, {6, 7}, {6, 8}};
470
471 Graph g;
472 auto nodes = build_graph_with_values(g, values, edges);
473
474 Rooted_Oracle oracle(values.size(), edges, 0);
476
477 EXPECT_EQ(cd.size(), values.size());
478 EXPECT_NE(cd.centroid_root(), nullptr);
479
480 std::vector<size_t> algo_of_input(values.size());
481 std::vector<size_t> input_of_algo(values.size());
482 for (size_t i = 0; i < values.size(); ++i)
483 {
484 const size_t aid = cd.id_of(nodes(i));
485 algo_of_input[i] = aid;
486 input_of_algo[aid] = i;
487 }
488
489 for (size_t u = 0; u < values.size(); ++u)
490 {
491 const size_t au = algo_of_input[u];
492 const auto & anc = cd.centroid_ancestors_of_id(au);
493 const auto & dist = cd.centroid_distances_of_id(au);
494
495 ASSERT_EQ(anc.size(), dist.size());
496 ASSERT_GT(anc.size(), 0u);
497
498 EXPECT_EQ(anc(0), cd.centroid_root_id());
499 EXPECT_EQ(anc(anc.size() - 1), au);
500 EXPECT_EQ(dist(anc.size() - 1), 0u);
501
502 for (size_t k = 0; k < anc.size(); ++k)
503 EXPECT_EQ(dist(k), oracle.distance(u, input_of_algo[anc(k)]));
504
505 for (size_t k = 1; k < anc.size(); ++k)
506 {
507 EXPECT_EQ(cd.centroid_parent_id(anc(k)), anc(k - 1));
509 cd.centroid_level_of_id(anc(k - 1)) + 1);
510 }
511 }
512
513 const size_t INF = std::numeric_limits<size_t>::max() / 4;
514 std::vector<size_t> best(cd.size(), INF);
515 std::vector<char> active(values.size(), 0);
516
517 auto activate = [&](const size_t u)
518 {
519 active[u] = 1;
521 algo_of_input[u],
522 [&](const size_t c, const size_t d, const size_t)
523 {
524 if (d < best[c])
525 best[c] = d;
526 });
527 };
528
529 auto query = [&](const size_t u)
530 {
531 size_t ans = INF;
533 algo_of_input[u],
534 [&](const size_t c, const size_t d, const size_t)
535 {
536 if (best[c] != INF)
537 ans = std::min(ans, best[c] + d);
538 });
539 return ans;
540 };
541
542 auto brute_query = [&](const size_t u)
543 {
544 size_t ans = INF;
545 for (size_t v = 0; v < active.size(); ++v)
546 if (active[v])
547 ans = std::min(ans, oracle.distance(u, v));
548 return ans;
549 };
550
551 std::mt19937 rng(0xC3E7D01Du);
552 std::uniform_int_distribution<size_t> pick_id(0, values.size() - 1);
553
554 activate(0);
555
556 for (size_t it = 0; it < 300; ++it)
557 {
558 const size_t u = pick_id(rng);
559 if ((rng() % 100) < 40)
560 activate(u);
561 else
562 EXPECT_EQ(query(u), brute_query(u));
563 }
564}
Heavy-Light and Centroid decomposition on rooted trees represented as Aleph graphs.
TYPED_TEST_SUITE(AStarHeapTest, HeapTypes)
WeightedDigraph::Node Node
long double w
Definition btreepic.C:153
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
static Array create(size_t n)
Create an array with n logical elements.
Definition tpl_array.H:194
Centroid decomposition over a rooted tree represented as an Aleph graph.
size_t centroid_level_of_id(const size_t id) const
Level of centroid id in centroid tree (root level = 0).
Node * centroid_root() const noexcept
Root centroid node of the centroid tree (or nullptr if empty).
size_t centroid_root_id() const noexcept
Root centroid ID of the centroid tree (or NONE if empty).
const Array< size_t > & centroid_ancestors_of_id(const size_t id) const
Centroid ancestors of node ID from centroid root down to local centroid.
size_t centroid_parent_id(const size_t id) const
Parent centroid ID of id (or NONE if centroid root).
void for_each_centroid_ancestor_id(const size_t node, F &&f) const
Visit each centroid ancestor of node ID.
size_t id_of(const Node *node) const
const Array< size_t > & centroid_distances_of_id(const size_t id) const
Distances to centroid ancestors (aligned with centroid_ancestors_of_id).
Segment-tree-powered path/subtree queries over an HLD layout.
T query_subtree(const Node *node) const
Subtree query for node in O(log n).
void set_node(const Node *node, const T &value)
Point assignment: a[node] = value.
T query_path(const Node *u, const Node *v) const
Path query between two nodes in O(log^2 n).
void update_node(const Node *node, const T &delta)
Point update: a[node] = Op(a[node], delta).
const Gen_Heavy_Light_Decomposition< GT, SA > & decomposition() const noexcept
T query_path_id(const size_t u, const size_t v) const
Path query between two node IDs in O(log^2 n).
bool is_empty() const noexcept
Heavy-Light Decomposition over a rooted tree represented as an Aleph graph.
Arc for graphs implemented with simple adjacency lists.
Definition tpl_sgraph.H:197
virtual Node * insert_node(Node *node) noexcept
Insertion of a node already allocated.
Definition tpl_graph.H:524
Arc * insert_arc(Node *src_node, Node *tgt_node, void *a)
Definition tpl_graph.H:604
Graph class implemented with singly-linked adjacency lists.
Definition tpl_sgraph.H:274
static mt19937 rng
__gmp_expr< T, __gmp_binary_expr< __gmp_expr< T, U >, unsigned long int, __gmp_root_function > > root(const __gmp_expr< T, U > &expr, unsigned long int l)
Definition gmpfrxx.h:4060
DynArray< Graph::Node * > nodes
Definition graphpic.C:406
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
void next()
Advance all underlying iterators (bounds-checked).
Definition ah-zip.H:171
Container< T > range(const T start, const T end, const T step=1)
Generate a range of values [start, end] with a given step.
T sum(const Container &container, const T &init=T{})
Compute sum of all elements.
Arc of graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:222
static int * k
Array-based graph implementation.
Generic graph and digraph implementations.
Simple graph implementation with adjacency lists.
TYPED_TEST(TreeDecompositionTypedTest, EmptyGraph)