Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
r_tree_test.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 https://github.com/lrleon/Aleph-w
6
7 This file is part of Aleph-w library
8
9 Copyright (c) 2002-2026 Leandro Rabindranath Leon
10
11 Permission is hereby granted, free of charge, to any person obtaining a copy
12 of this software and associated documentation files (the "Software"), to deal
13 in the Software without restriction, including without limitation the rights
14 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 copies of the Software, and to permit persons to whom the Software is
16 furnished to do so, subject to the following conditions:
17
18 The above copyright notice and this permission notice shall be included in all
19 copies or substantial portions of the Software.
20
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 SOFTWARE.
28*/
29
35#include <gtest/gtest.h>
36
37#include <algorithm>
38#include <concepts>
39#include <limits>
40#include <memory>
41#include <random>
42#include <type_traits>
43#include <utility>
44
45#include <ahSort.H>
46#include <tpl_r_tree.H>
47
49
50using namespace Aleph;
51using namespace Aleph::test_helpers;
52
53namespace
54{
55 Rectangle rect(const int x1, const int y1, const int x2, const int y2)
56 {
58 Geom_Number(x2), Geom_Number(y2));
59 }
60
61 Point pt(const int x, const int y)
62 {
63 return Point(Geom_Number(x), Geom_Number(y));
64 }
65
66 template <typename Tree>
67 Array<int> sorted_intersects(const Tree &tree, const Rectangle &q)
68 {
69 Array<int> hits = tree.search_intersects(q);
71 return hits;
72 }
73
74 template <typename Tree>
75 Array<int> sorted_contains(const Tree &tree, const Point &p)
76 {
77 Array<int> hits = tree.search_contains(p);
79 return hits;
80 }
81
82 Array<int> brute_intersects(const Array<std::pair<Rectangle, int>> &ref,
83 const Rectangle &q)
84 {
86 for (const auto &[b, id] : ref)
87 if (b.intersects(q))
88 out.append(id);
90 return out;
91 }
92
93 Array<int> brute_contains(const Array<std::pair<Rectangle, int>> &ref,
94 const Point &p)
95 {
97 for (const auto &[b, id] : ref)
98 if (b.contains(p))
99 out.append(id);
101 return out;
102 }
103
104 struct CopyConstructibleNonCopyAssignablePayload
105 {
106 int value = 0;
107
108 CopyConstructibleNonCopyAssignablePayload() = default;
109 explicit CopyConstructibleNonCopyAssignablePayload(const int v) : value(v) {}
110
111 CopyConstructibleNonCopyAssignablePayload(
112 const CopyConstructibleNonCopyAssignablePayload &) = default;
113 CopyConstructibleNonCopyAssignablePayload(
114 CopyConstructibleNonCopyAssignablePayload &&) noexcept = default;
115
116 CopyConstructibleNonCopyAssignablePayload &operator = (
117 const CopyConstructibleNonCopyAssignablePayload &) = delete;
118 CopyConstructibleNonCopyAssignablePayload &operator = (
119 CopyConstructibleNonCopyAssignablePayload &&) noexcept = default;
120 };
121
122 static_assert(std::default_initializable<CopyConstructibleNonCopyAssignablePayload>);
123 static_assert(std::movable<CopyConstructibleNonCopyAssignablePayload>);
124 static_assert(std::is_copy_constructible_v<CopyConstructibleNonCopyAssignablePayload>);
125 static_assert(not std::is_copy_assignable_v<CopyConstructibleNonCopyAssignablePayload>);
126}
127
129{
130 RTree<int> tree;
131 EXPECT_TRUE(tree.is_empty());
132 EXPECT_EQ(tree.size(), 0u);
133 EXPECT_EQ(tree.height(), 0u);
134 EXPECT_TRUE(tree.verify());
135 EXPECT_EQ(tree.search_intersects(rect(0, 0, 10, 10)).size(), 0u);
136 EXPECT_EQ(tree.search_contains(pt(1, 1)).size(), 0u);
137 EXPECT_FALSE(tree.erase(rect(0, 0, 1, 1), 5));
138}
139
141{
142 RTree<int> tree;
143 tree.insert(rect(0, 0, 2, 2), 42);
144
145 EXPECT_FALSE(tree.is_empty());
146 EXPECT_EQ(tree.size(), 1u);
147 EXPECT_EQ(tree.height(), 1u);
148 EXPECT_TRUE(tree.verify());
149
150 EXPECT_EQ(sorted_intersects(tree, rect(1, 1, 5, 5)), build_array<int>(42));
151 EXPECT_EQ(sorted_intersects(tree, rect(10, 10, 20, 20)), Array<int>());
152 EXPECT_EQ(sorted_contains(tree, pt(1, 1)), build_array<int>(42));
153 EXPECT_EQ(sorted_contains(tree, pt(9, 9)), Array<int>());
154}
155
157{
158 RTree<int> tree;
159 tree.insert(rect(0, 0, 2, 2), 1);
160 EXPECT_TRUE(tree.erase(rect(0, 0, 2, 2), 1));
161 EXPECT_TRUE(tree.is_empty());
162 EXPECT_EQ(tree.size(), 0u);
163 EXPECT_EQ(tree.height(), 0u);
164 EXPECT_TRUE(tree.verify());
165}
166
168{
169 RTree<int> tree;
170 tree.insert(rect(0, 0, 2, 2), 1);
171 EXPECT_FALSE(tree.erase(rect(0, 0, 2, 2), 999)); // wrong value
172 EXPECT_FALSE(tree.erase(rect(5, 5, 6, 6), 1)); // wrong bbox
173 EXPECT_EQ(tree.size(), 1u);
174 EXPECT_TRUE(tree.verify());
175}
176
178{
179 RTree<int, 4, 2> tree; // tiny fanout forces splits early
180 for (int i = 0; i < 200; ++i)
181 {
182 tree.insert(rect(i, i, i + 1, i + 1), i);
183 ASSERT_TRUE(tree.verify());
184 }
185 EXPECT_EQ(tree.size(), 200u);
186 EXPECT_GT(tree.height(), 1u);
187
188 // Every inserted box must be findable at its own corner.
189 for (int i = 0; i < 200; ++i)
190 {
191 const Array<int> hits = sorted_contains(tree, pt(i, i));
192 EXPECT_TRUE(std::find(hits.begin(), hits.end(), i) != hits.end());
193 }
194}
195
197{
198 const RTree<int> tree;
199 const auto snap = tree.debug_snapshot();
200 EXPECT_TRUE(snap.nodes.is_empty());
201 EXPECT_EQ(snap.root, std::numeric_limits<size_t>::max());
202}
203
205{
206 RTree<int, 4, 2> tree; // tiny fanout forces multiple levels
207 for (int i = 0; i < 200; ++i)
208 tree.insert(rect(i, i, i + 1, i + 1), i);
209
210 const auto snap = tree.debug_snapshot();
211 ASSERT_LT(snap.root, snap.nodes.size());
212 const size_t total_entries = check_snapshot_node(snap, snap.root, 0);
214}
215
217{
218 RTree<int> tree;
219 tree.insert(rect(0, 0, 1, 1), 42);
220 const auto snap = tree.debug_snapshot();
221 ASSERT_EQ(snap.nodes.size(), 1u);
222 EXPECT_EQ(snap.root, 0u);
223 EXPECT_TRUE(snap.nodes(0).is_leaf);
224 EXPECT_EQ(snap.nodes(0).depth, 0u);
225 ASSERT_EQ(snap.nodes(0).entry_boxes.size(), 1u);
226 EXPECT_EQ(snap.nodes(0).bbox, rect(0, 0, 1, 1));
227}
228
230{
231 RTree<int, 4, 2> tree;
232 const Rectangle box = rect(3, 3, 6, 6);
233 for (int i = 0; i < 20; ++i)
234 tree.insert(box, i); // identical bbox, distinct payloads
235
236 ASSERT_TRUE(tree.verify());
237 EXPECT_EQ(tree.size(), 20u);
238 EXPECT_EQ(sorted_intersects(tree, rect(4, 4, 5, 5)).size(), 20u);
239
240 // Removing one leaves the other nineteen.
241 ASSERT_TRUE(tree.erase(box, 7));
242 ASSERT_TRUE(tree.verify());
243 const Array<int> hits = sorted_intersects(tree, box);
244 EXPECT_EQ(hits.size(), 19u);
245 EXPECT_TRUE(std::find(hits.begin(), hits.end(), 7) == hits.end());
246}
247
249{
250 RTree<int, 4, 2> tree;
251 // Points (zero area) and lines (zero width or height).
252 for (int i = 0; i < 30; ++i)
253 tree.insert(rect(i, 5, i, 5), i); // points on a horizontal line
254 for (int i = 0; i < 30; ++i)
255 tree.insert(rect(0, i, 40, i), 100 + i); // horizontal segments
256
257 ASSERT_TRUE(tree.verify());
258 EXPECT_EQ(tree.size(), 60u);
259
260 // The point (10,5) is a stored point and lies on segment y=5.
261 const Array<int> hits = sorted_contains(tree, pt(10, 5));
262 EXPECT_TRUE(std::find(hits.begin(), hits.end(), 10) != hits.end());
263 EXPECT_TRUE(std::find(hits.begin(), hits.end(), 105) != hits.end());
264}
265
267{
269 for (int i = 0; i < 50; ++i)
270 original.insert(rect(i, i, i + 2, i + 2), i);
271
272 RTree<int, 4, 2> copy = original; // deep copy
273 ASSERT_TRUE(copy.verify());
274 EXPECT_EQ(copy.size(), original.size());
275
276 // Mutate the original; the copy must not change.
277 for (int i = 0; i < 25; ++i)
278 ASSERT_TRUE(original.erase(rect(i, i, i + 2, i + 2), i));
279
280 ASSERT_TRUE(original.verify());
281 ASSERT_TRUE(copy.verify());
282 EXPECT_EQ(original.size(), 25u);
283 EXPECT_EQ(copy.size(), 50u);
284 EXPECT_EQ(sorted_contains(copy, pt(1, 1)).is_empty(), false);
285 EXPECT_TRUE(sorted_contains(original, pt(1, 1)).is_empty());
286}
287
289{
291 for (int i = 0; i < 30; ++i)
292 a.insert(rect(i, 0, i + 1, 1), i);
293
294 RTree<int, 4, 2> b = std::move(a);
295 EXPECT_EQ(b.size(), 30u);
296 EXPECT_TRUE(b.verify());
297 EXPECT_TRUE(a.is_empty()); // NOLINT(bugprone-use-after-move)
298 EXPECT_TRUE(a.verify());
299}
300
302{
303 RTree<std::unique_ptr<int>, 4, 2> tree;
304 for (int i = 0; i < 20; ++i)
305 tree.insert(rect(i, i, i + 1, i + 1), std::make_unique<int>(i));
306
307 ASSERT_TRUE(tree.verify());
308 EXPECT_EQ(tree.size(), 20u);
309
310 int seen = 0;
311 int sum = 0;
312 tree.for_each_intersecting(rect(0, 0, 100, 100),
313 [&](const Rectangle &, const std::unique_ptr<int> &v)
314 {
315 ++seen;
316 sum += *v;
317 });
318 EXPECT_EQ(seen, 20);
319 EXPECT_EQ(sum, 190); // 0 + 1 + ... + 19
320}
321
323{
324 using Payload = CopyConstructibleNonCopyAssignablePayload;
325
327 const Payload first(7);
328 tree.insert(rect(0, 0, 2, 2), first);
329 tree.insert(rect(3, 3, 5, 5), Payload(11));
330
331 const RTree<Payload, 4, 2> copy(tree);
332 ASSERT_TRUE(copy.verify());
333
334 Array<Payload> intersecting = copy.search_intersects(rect(1, 1, 4, 4));
335 ASSERT_EQ(intersecting.size(), 2u);
337
338 Array<Payload> containing = copy.search_contains(pt(1, 1));
339 ASSERT_EQ(containing.size(), 1u);
341}
342
344{
345 RTree<int, 4, 2> source;
347 for (int i = 0; i < 90; ++i)
348 {
349 const Rectangle b = rect(i % 15, i / 15, i % 15 + 2, i / 15 + 2);
350 source.insert(b, i);
351 ref.append(std::make_pair(b, i));
352 }
353 ASSERT_TRUE(source.verify());
354
356 assigned = source;
357 EXPECT_EQ(assigned.size(), source.size());
358 EXPECT_TRUE(assigned.verify());
359
360 for (int i = 0; i < 30; ++i)
361 ASSERT_TRUE(source.erase(ref(i).first, ref(i).second));
362 EXPECT_EQ(source.size(), 60u);
363 EXPECT_EQ(assigned.size(), 90u);
364 EXPECT_TRUE(source.verify());
365 EXPECT_TRUE(assigned.verify());
366
368 moved = std::move(assigned);
369 EXPECT_EQ(moved.size(), 90u);
370 EXPECT_TRUE(moved.verify());
371 EXPECT_TRUE(assigned.is_empty()); // NOLINT(bugprone-use-after-move)
372 EXPECT_TRUE(assigned.verify());
373
374 moved.clear();
375 EXPECT_TRUE(moved.is_empty());
376 EXPECT_EQ(moved.height(), 0u);
377 EXPECT_TRUE(moved.verify());
378
379 for (const auto &[b, id] : ref)
380 {
381 ASSERT_TRUE(source.erase(b, id) or id < 30);
382 ASSERT_TRUE(source.verify());
383 }
384 EXPECT_TRUE(source.is_empty());
385 EXPECT_EQ(source.height(), 0u);
386}
387
389{
390 std::mt19937 rng(20260714u); // fixed seed
391 std::uniform_int_distribution<int> op_dist(0, 2);
392 std::uniform_int_distribution<int> coord(0, 80);
393 std::uniform_int_distribution<int> extent(0, 12);
394
395 RTree<int, 6, 3> tree;
397 int next_id = 0;
398
399 auto random_rect = [&]()
400 {
401 const int x1 = coord(rng);
402 const int y1 = coord(rng);
403 return rect(x1, y1, x1 + extent(rng), y1 + extent(rng));
404 };
405
406 for (int iter = 0; iter < 4000; ++iter)
407 {
408 const int op = ref.is_empty() ? 0 : op_dist(rng);
409 if (op == 0) // insert
410 {
411 const Rectangle b = random_rect();
412 const int id = next_id++;
413 tree.insert(b, id);
414 ref.append(std::make_pair(b, id));
415 }
416 else if (op == 1) // erase a random existing entry
417 {
418 std::uniform_int_distribution<size_t> pick(0, ref.size() - 1);
419 const size_t k = pick(rng);
420 ASSERT_TRUE(tree.erase(ref(k).first, ref(k).second));
421 std::swap(ref(k), ref(ref.size() - 1)); // swap-and-pop; order is irrelevant here
422 [[maybe_unused]] const auto popped = ref.remove_last();
423 }
424 else // query and compare against brute force
425 {
426 const Rectangle q = random_rect();
428 const Point p = pt(coord(rng), coord(rng));
429 EXPECT_EQ(sorted_contains(tree, p), brute_contains(ref, p));
430 }
431
432 ASSERT_TRUE(tree.verify()) << "invariant broken at iter " << iter;
433 ASSERT_EQ(tree.size(), ref.size());
434 }
435
436 // Final exhaustive parity over a grid of query rectangles.
437 for (int x = 0; x < 90; x += 7)
438 for (int y = 0; y < 90; y += 7)
439 {
440 const Rectangle q = rect(x, y, x + 10, y + 10);
442 }
443}
High-level sorting functions for Aleph containers.
size_t size_t int32_t value
Definition ca-c-api.h:116
size_t size_t int32_t * out
Definition ca-c-api.h:120
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:365
constexpr bool is_empty() const noexcept
Checks if the container is empty.
Definition tpl_array.H:359
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:250
Represents a point with rectangular coordinates in a 2D plane.
Definition point.H:221
Dynamic R-tree indexing axis-aligned rectangles by payload.
Definition tpl_r_tree.H:118
DebugSnapshot debug_snapshot() const
Capture the full tree structure for visualization/debugging.
void insert(const Rectangle &bbox, const Payload &value)
Insert a (bbox, value) entry, copying value.
Definition tpl_r_tree.H:969
bool erase(const Rectangle &bbox, const Payload &value)
Remove one entry equal to (bbox, value).
size_t height() const noexcept
Return the number of node levels (0 when empty, 1 for a lone leaf).
Definition tpl_r_tree.H:946
bool is_empty() const noexcept
Return true when the tree has no entries.
Definition tpl_r_tree.H:928
Array< Payload > search_intersects(const Rectangle &rect) const
Return the payloads of every entry whose bbox intersects rect.
Array< Payload > search_contains(const Point &p) const
Return the payloads of every entry whose bbox contains p.
size_t size() const noexcept
Return the number of stored entries.
Definition tpl_r_tree.H:937
void for_each_intersecting(const Rectangle &rect, F &&f) const
Invoke f for every entry whose bbox intersects rect.
bool verify() const
Verify the R-tree structural invariants.
An axis-aligned rectangle.
Definition point.H:1789
QuadTree - Hierarchical spatial index for 2D points.
Definition quadtree.H:126
iterator end() noexcept
Return an STL-compatible end iterator.
iterator begin() noexcept
Return an STL-compatible iterator to the first element.
#define TEST(name)
static mt19937 rng
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_y1_function > > y1(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4114
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
static mpfr_t y
Definition mpfr_mul_d.c:3
size_t check_snapshot_node(const Snapshot &snap, const size_t idx, const size_t expected_depth)
Recursively validates the structural invariants of a DebugSnapshot produced by RTree::debug_snapshot(...
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
size_t size(Node *root) noexcept
Itor2 copy(Itor1 sourceBeg, const Itor1 &sourceEnd, Itor2 destBeg)
Copy elements from one range to another.
Definition ahAlgo.H:584
bool contains(const std::string_view &str, const std::string_view &substr)
Check if substr appears inside str.
DynArray< T > & in_place_sort(DynArray< T > &c, Cmp cmp=Cmp())
Sorts a DynArray in place.
Definition ahSort.H:328
mpq_class Geom_Number
Numeric type used by the geometry module.
Definition point.H:113
T sum(const Container &container, const T &init=T{})
Compute sum of all elements.
STL namespace.
Shared DebugSnapshot structural-invariant checker for RTree and RStarTree unit tests.
static Rectangle box(const int x1, const int y1, const int x2, const int y2)
static int * k
Dynamic R-tree spatial index over axis-aligned rectangles.