Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
quadtree_test.cc
Go to the documentation of this file.
1
2/*
3 Aleph_w
4
5 Data structures & Algorithms
6 version 2.0.0b
7 https://github.com/lrleon/Aleph-w
8
9 This file is part of Aleph-w library
10
11 Copyright (c) 2002-2026 Leandro Rabindranath Leon
12
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30*/
31
32
39#include <gtest/gtest.h>
40#include <quadtree.H>
41#include <random>
42#include <algorithm>
43#include <unordered_set>
44#include <sstream>
45
46// ============================================================================
47// Basic Functionality Tests
48// ============================================================================
49
51{
52 QuadTree tree(0, 100, 0, 100, 4);
53
54 EXPECT_NE(tree.get_root(), nullptr);
56}
57
59{
60 QuadTree tree(0, 100, 0, 100, 4);
61
62 Point * inserted = tree.insert(Point(50, 50));
63
64 ASSERT_NE(inserted, nullptr);
65 EXPECT_EQ(inserted->get_x(), 50);
66 EXPECT_EQ(inserted->get_y(), 50);
67}
68
70{
71 QuadTree tree(0, 100, 0, 100, 4);
72
73 Point * result1 = tree.insert(Point(-10, 50));
74 Point * result2 = tree.insert(Point(50, 150));
75 Point * result3 = tree.insert(Point(150, 50));
76 Point * result4 = tree.insert(Point(50, -10));
77
78 EXPECT_EQ(result1, nullptr);
79 EXPECT_EQ(result2, nullptr);
80 EXPECT_EQ(result3, nullptr);
81 EXPECT_EQ(result4, nullptr);
82}
83
85{
86 QuadTree tree(0, 100, 0, 100, 4);
87
88 tree.insert(Point(25, 25));
89 tree.insert(Point(75, 75));
90
91 EXPECT_TRUE(tree.contains(Point(25, 25)));
92 EXPECT_TRUE(tree.contains(Point(75, 75)));
93 EXPECT_TRUE(tree.contains(Point(50, 50))); // In bounds but not inserted
94 EXPECT_FALSE(tree.contains(Point(-10, 50))); // Out of bounds
95 EXPECT_FALSE(tree.contains(Point(150, 50))); // Out of bounds
96}
97
99{
100 QuadTree tree(0, 100, 0, 100, 4);
101
102 tree.insert(Point(30, 40));
103 tree.insert(Point(70, 60));
104
105 Point * found1 = tree.search(Point(30, 40));
106 Point * found2 = tree.search(Point(70, 60));
107
108 ASSERT_NE(found1, nullptr);
109 ASSERT_NE(found2, nullptr);
110 EXPECT_EQ(found1->get_x(), 30);
111 EXPECT_EQ(found1->get_y(), 40);
112 EXPECT_EQ(found2->get_x(), 70);
113 EXPECT_EQ(found2->get_y(), 60);
114}
115
117{
118 QuadTree tree(0, 100, 0, 100, 4);
119
120 tree.insert(Point(30, 40));
121
122 Point * found = tree.search(Point(50, 50));
123
124 EXPECT_EQ(found, nullptr);
125}
126
128{
129 QuadTree tree(0, 100, 0, 100, 4);
130
131 tree.insert(Point(25, 25));
132
133 QuadNode * node = tree.search_container_node(Point(25, 25));
134
135 ASSERT_NE(node, nullptr);
136 EXPECT_TRUE(node->is_leaf());
137 EXPECT_NE(node->search_point(Point(25, 25)), nullptr);
138}
139
141{
142 QuadTree tree(0, 100, 0, 100, 4);
143
144 tree.insert(Point(50, 50));
145 EXPECT_NE(tree.search(Point(50, 50)), nullptr);
146
147 tree.remove(Point(50, 50));
148 EXPECT_EQ(tree.search(Point(50, 50)), nullptr);
149}
150
152{
153 QuadTree tree(0, 100, 0, 100, 4);
154
155 tree.insert(Point(50, 50));
156
157 // Should not crash or cause errors
158 tree.remove(Point(30, 30));
159
160 EXPECT_NE(tree.search(Point(50, 50)), nullptr);
161}
162
164{
165 QuadTree tree(0, 100, 0, 100, 4);
166
167 tree.insert(Point(25, 25));
168 tree.insert(Point(75, 75));
169 tree.insert(Point(50, 50));
170
171 tree.empty();
172
173 EXPECT_EQ(tree.search(Point(25, 25)), nullptr);
174 EXPECT_EQ(tree.search(Point(75, 75)), nullptr);
175 EXPECT_EQ(tree.search(Point(50, 50)), nullptr);
176}
177
179{
180 // Test 1: clear on newly constructed tree (no-op)
181 QuadTree tree(0, 100, 0, 100, 4);
182 tree.clear();
183 EXPECT_EQ(tree.search(Point(50, 50)), nullptr);
184
185 // Test 2: re-insertion after clear
186 tree.insert(Point(25, 25));
187 tree.clear();
188 EXPECT_EQ(tree.search(Point(25, 25)), nullptr);
189
190 tree.insert(Point(25, 25));
191 EXPECT_NE(tree.search(Point(25, 25)), nullptr);
192}
193
195{
196 QuadTree tree(0, 100, 0, 100, 4);
197 tree.insert(Point(25, 25));
198 tree.insert(Point(75, 75));
199
200 tree.clear();
201
202 EXPECT_EQ(tree.search(Point(25, 25)), nullptr);
203 EXPECT_EQ(tree.search(Point(75, 75)), nullptr);
204}
205
206// ============================================================================
207// Subdivision and Merging Tests
208// ============================================================================
209
211{
212 QuadTree tree(0, 100, 0, 100, 2); // Max 2 points per node
213
214 tree.insert(Point(25, 25));
215 tree.insert(Point(30, 30));
216
217 // Root should still be a leaf
218 EXPECT_TRUE(tree.get_root()->is_leaf());
219
220 // Third point should trigger split
221 tree.insert(Point(35, 35));
222
223 // Root should now be internal (Gray)
224 EXPECT_FALSE(tree.get_root()->is_leaf());
225 EXPECT_EQ(COLOR(tree.get_root()), QuadNode::Color::Gray);
226}
227
229{
230 QuadTree tree(0, 100, 0, 100, 2);
231
232 // Insert many points in one quadrant to force deep splits
233 for (int i = 1; i <= 10; ++i)
234 {
235 tree.insert(Point(10 + i, 10 + i));
236 }
237
238 // Verify root is internal
239 EXPECT_FALSE(tree.get_root()->is_leaf());
240
241 // Verify tree has multiple levels
242 QuadNode * nw = NW_CHILD(tree.get_root());
243 ASSERT_NE(nw, nullptr);
244
245 // At least one child should be further subdivided
246 bool has_deep_child = false;
247 if (not nw->is_leaf() || (NE_CHILD(tree.get_root()) != nullptr and not NE_CHILD(tree.get_root())->is_leaf()))
248 has_deep_child = true;
249
251}
252
254{
255 QuadTree tree(0, 100, 0, 100, 1);
256
257 // Insert one point in each quadrant
258 tree.insert(Point(25, 25)); // SW
259 tree.insert(Point(75, 25)); // SE
260 tree.insert(Point(25, 75)); // NW
261 tree.insert(Point(75, 75)); // NE
262
263 QuadNode * root = tree.get_root();
264 EXPECT_FALSE(root->is_leaf());
265
266 // All four children should exist and be leaves
267 ASSERT_NE(NW_CHILD(root), nullptr);
268 ASSERT_NE(NE_CHILD(root), nullptr);
269 ASSERT_NE(SW_CHILD(root), nullptr);
270 ASSERT_NE(SE_CHILD(root), nullptr);
271
276}
277
279{
280 QuadTree tree(0, 100, 0, 100, 2);
281
282 // Insert 3 points in DIFFERENT quadrants to trigger split
283 // Points must be in different quadrants to avoid nested splits
284 tree.insert(Point(25, 25)); // SW quadrant
285 tree.insert(Point(75, 25)); // SE quadrant
286 tree.insert(Point(25, 75)); // NW quadrant
287
288 EXPECT_FALSE(tree.get_root()->is_leaf());
289
290 // Remove one point to go below threshold
291 tree.remove(Point(25, 75));
292
293 // Root should merge back to leaf (only 2 points left, threshold is 2)
294 EXPECT_TRUE(tree.get_root()->is_leaf());
295}
296
298{
299 QuadTree tree(0, 100, 0, 100, 2);
300
301 // Insert many points
302 std::vector<Point> points = {
303 Point(10, 10), Point(15, 15), Point(20, 20),
304 Point(80, 80), Point(85, 85), Point(90, 90)
305 };
306
307 for (const auto & p : points)
308 tree.insert(p);
309
310 // Tree should be subdivided
311 EXPECT_FALSE(tree.get_root()->is_leaf());
312
313 // Remove points one by one
314 for (const auto & p : points)
315 {
316 tree.remove(p);
317 }
318
319 // After all removals, root should be a leaf again
320 EXPECT_TRUE(tree.get_root()->is_leaf());
321 EXPECT_EQ(COLOR(tree.get_root()), QuadNode::Color::White);
322}
323
324// ============================================================================
325// Copy Constructor and Assignment Tests
326// ============================================================================
327
329{
330 QuadTree tree1(0, 100, 0, 100, 3);
331
332 tree1.insert(Point(25, 25));
333 tree1.insert(Point(75, 75));
334 tree1.insert(Point(50, 50));
335
337
338 // Verify all points are in the copy
339 EXPECT_NE(tree2.search(Point(25, 25)), nullptr);
340 EXPECT_NE(tree2.search(Point(75, 75)), nullptr);
341 EXPECT_NE(tree2.search(Point(50, 50)), nullptr);
342
343 // Verify they are independent
344 tree1.insert(Point(10, 10));
345 EXPECT_EQ(tree2.search(Point(10, 10)), nullptr);
346}
347
349{
350 QuadTree tree1(0, 100, 0, 100, 3);
351 tree1.insert(Point(25, 25));
352 tree1.insert(Point(75, 75));
353
354 QuadTree tree2(0, 200, 0, 200, 5);
355 tree2.insert(Point(150, 150));
356
357 tree2 = tree1;
358
359 // tree2 should now have tree1's data
360 EXPECT_NE(tree2.search(Point(25, 25)), nullptr);
361 EXPECT_NE(tree2.search(Point(75, 75)), nullptr);
362 EXPECT_EQ(tree2.search(Point(150, 150)), nullptr);
363
364 // Configuration should also be copied
365 EXPECT_EQ(tree2.get_max_num_points_per_node(), 3);
366}
367
369{
370 QuadTree tree(0, 100, 0, 100, 3);
371 tree.insert(Point(50, 50));
372
373 tree = tree;
374
375 // Should still work
376 EXPECT_NE(tree.search(Point(50, 50)), nullptr);
377}
378
379// ============================================================================
380// Stress Tests
381// ============================================================================
382
384{
385 QuadTree tree(0, 1000, 0, 1000, 4);
386
387 std::mt19937 gen(12345);
388 std::uniform_real_distribution<> dis(0, 1000);
389
390 const size_t num_points = 10000;
391 std::vector<Point> points;
392
393 for (size_t i = 0; i < num_points; ++i)
394 {
395 Point p(dis(gen), dis(gen));
396 points.push_back(p);
397 Point * inserted = tree.insert(p);
398 ASSERT_NE(inserted, nullptr);
399 }
400
401 // Verify all points can be found
402 for (const auto & p : points)
403 {
404 EXPECT_NE(tree.search(p), nullptr);
405 }
406}
407
409{
410 QuadTree tree(0, 100, 0, 100, 4);
411
412 std::mt19937 gen(54321);
413 std::uniform_real_distribution<> dis(0, 100);
414
415 const size_t cycles = 100;
416 const size_t points_per_cycle = 50;
417
418 for (size_t cycle = 0; cycle < cycles; ++cycle)
419 {
420 std::vector<Point> points;
421
422 // Insert points
423 for (size_t i = 0; i < points_per_cycle; ++i)
424 {
425 Point p(dis(gen), dis(gen));
426 points.push_back(p);
427 tree.insert(p);
428 }
429
430 // Remove half of them
431 for (size_t i = 0; i < points_per_cycle / 2; ++i)
432 {
433 tree.remove(points[i]);
434 }
435 }
436
437 // Tree should still be functional
438 Point * test = tree.insert(Point(50, 50));
439 EXPECT_NE(test, nullptr);
440}
441
443{
444 QuadTree tree(0, 100, 0, 100, 2);
445
446 // Insert many points in a small region
447 for (int x = 40; x <= 60; ++x)
448 {
449 for (int y = 40; y <= 60; ++y)
450 {
451 tree.insert(Point(x, y));
452 }
453 }
454
455 // Verify all can be found
456 for (int x = 40; x <= 60; ++x)
457 {
458 for (int y = 40; y <= 60; ++y)
459 {
460 EXPECT_NE(tree.search(Point(x, y)), nullptr);
461 }
462 }
463}
464
465// ============================================================================
466// Edge Cases
467// ============================================================================
468
470{
471 QuadTree tree(0, 100, 0, 100, 4);
472
473 // Test exact boundary points
474 Point * p1 = tree.insert(Point(0, 0));
475 Point * p2 = tree.insert(Point(0, 100)); // Upper bound, should fail
476 Point * p3 = tree.insert(Point(100, 0)); // Upper bound, should fail
477 Point * p4 = tree.insert(Point(100, 100)); // Upper bounds, should fail
478
479 EXPECT_NE(p1, nullptr);
480 EXPECT_EQ(p2, nullptr); // Upper bounds are exclusive
481 EXPECT_EQ(p3, nullptr);
482 EXPECT_EQ(p4, nullptr);
483}
484
486{
487 QuadTree tree(0, 100, 0, 100, 1);
488
489 // Insert point exactly at midpoint
490 tree.insert(Point(50, 50));
491
492 // Insert more to trigger split
493 tree.insert(Point(25, 25));
494
495 // Midpoint should be in one of the quadrants
496 EXPECT_NE(tree.search(Point(50, 50)), nullptr);
497}
498
500{
501 QuadTree tree(0, 100, 0, 100, 1);
502
503 tree.insert(Point(25, 25));
504 tree.insert(Point(30, 30));
505
506 // Should trigger immediate split
507 EXPECT_FALSE(tree.get_root()->is_leaf());
508}
509
511{
512 QuadTree tree(0, 1, 0, 1, 2);
513
514 tree.insert(Point(0.1, 0.1));
515 tree.insert(Point(0.9, 0.9));
516
517 EXPECT_NE(tree.search(Point(0.1, 0.1)), nullptr);
518 EXPECT_NE(tree.search(Point(0.9, 0.9)), nullptr);
519}
520
522{
523 QuadTree tree(-1e9, 1e9, -1e9, 1e9, 4);
524
525 tree.insert(Point(0, 0));
526 tree.insert(Point(1e8, 1e8));
527 tree.insert(Point(-5e8, -5e8));
528
529 EXPECT_NE(tree.search(Point(0, 0)), nullptr);
530 EXPECT_NE(tree.search(Point(1e8, 1e8)), nullptr);
531 EXPECT_NE(tree.search(Point(-5e8, -5e8)), nullptr);
532}
533
534// ============================================================================
535// Traversal Tests
536// ============================================================================
537
539{
540 QuadTree tree(0, 100, 0, 100, 2);
541
542 for (int i = 0; i < 10; ++i)
543 {
544 tree.insert(Point(10 * i, 10 * i));
545 }
546
547 size_t node_count = 0;
548 tree.for_each([&node_count](QuadNode * node) {
549 ++node_count;
550 EXPECT_NE(node, nullptr);
551 });
552
553 EXPECT_GT(node_count, 0);
554}
555
557{
558 QuadTree tree(0, 100, 0, 100, 2);
559
560 tree.insert(Point(10, 10));
561 tree.insert(Point(20, 20));
562 tree.insert(Point(80, 80));
563 tree.insert(Point(90, 90));
564
565 size_t leaf_count = 0;
566 tree.for_each([&leaf_count](QuadNode * node) {
567 if (node->is_leaf())
568 ++leaf_count;
569 });
570
572}
573
574// ============================================================================
575// Fuzz Tests
576// ============================================================================
577
579{
580 QuadTree tree(0, 1000, 0, 1000, 4);
581
582 std::mt19937 gen(99999);
583 // Use integer coordinates to avoid mpq_class precision issues with double conversion
584 std::uniform_int_distribution<int> coord_dis(0, 999);
585 std::uniform_int_distribution<> op_dis(0, 2);
586
587 // Store points directly to avoid string conversion precision issues
588 std::vector<Point> inserted_points;
589
590 for (int i = 0; i < 1000; ++i)
591 {
592 int op = op_dis(gen);
593
594 if (op == 0 || op == 1) // Insert
595 {
597 Point * result = tree.insert(p);
598 if (result != nullptr)
599 inserted_points.push_back(p);
600 }
601 else if (op == 2 && not inserted_points.empty()) // Remove
602 {
603 // Pick a random inserted point
604 size_t idx = gen() % inserted_points.size();
606
607 tree.remove(to_remove);
608 inserted_points.erase(inserted_points.begin() + idx);
609 }
610 }
611
612 // Verify consistency
613 for (const auto & p : inserted_points)
614 EXPECT_NE(tree.search(p), nullptr)
615 << "Point (" << p.get_x() << ", " << p.get_y() << ") should be in tree";
616}
617
618// ============================================================================
619// Main
620// ============================================================================
621
622int main(int argc, char **argv)
623{
624 ::testing::InitGoogleTest(&argc, argv);
625 return RUN_ALL_TESTS();
626}
627
Represents a point with rectangular coordinates in a 2D plane.
Definition point.H:229
const Geom_Number & get_x() const noexcept
Gets the x-coordinate value.
Definition point.H:457
const Geom_Number & get_y() const noexcept
Gets the y-coordinate value.
Definition point.H:466
Node for QuadTree spatial data structure.
Definition quadnode.H:94
Point * search_point(const Point &p) noexcept
Search for a point in this node.
Definition quadnode.H:508
bool is_leaf() const noexcept
Check if this node is a leaf (has no children).
Definition quadnode.H:375
QuadTree - Hierarchical spatial index for 2D points.
Definition quadtree.H:126
void empty(Node *&r) noexcept
Recursively delete all nodes.
Definition quadtree.H:255
void clear()
Alias for empty().
Definition quadtree.H:535
void for_each(Op &op)
Apply an operation to each node in the tree.
Definition quadtree.H:545
void remove(const Point &p)
Remove a point from the tree.
Definition quadtree.H:502
Point * search(const Point &p) noexcept
Search for a point in the tree.
Definition quadtree.H:461
Node * search_container_node(const Point &p) noexcept
Find the leaf node containing a point.
Definition quadtree.H:479
bool contains(const Point &p) const noexcept
Check if a point is within the tree's region.
Definition quadtree.H:424
Point * insert(Node *&r, const Point &p)
Recursive insert helper.
Definition quadtree.H:237
size_t get_max_num_points_per_node() const noexcept
Get the maximum points per leaf node.
Definition quadtree.H:414
Node * get_root() noexcept
Get the root node.
Definition quadtree.H:396
#define TEST(name)
__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
static mpfr_t y
Definition mpfr_mul_d.c:3
and
Check uniqueness with explicit hash + equality functors.
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.
static bool is_leaf(BinNode< std::string > *p) noexcept
Definition Huffman.H:111
#define COLOR(p)
Definition quadnode.H:58
#define SE_CHILD(p)
Definition quadnode.H:57
#define NE_CHILD(p)
Definition quadnode.H:55
#define NW_CHILD(p)
Definition quadnode.H:54
#define SW_CHILD(p)
Definition quadnode.H:56
QuadTree spatial data structure for efficient 2D point indexing.
void test()
Definition test-comb.C:35