Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
719 changes: 719 additions & 0 deletions Examples/tikz_computational_geometry_gallery_example.cc

Large diffs are not rendered by default.

165 changes: 164 additions & 1 deletion Examples/tikz_tree_structures_example.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
#include <iostream>
#include <string>

#include <quadtree.H>
#include <tikzgeom_algorithms.H>
#include <tpl_r_star_tree.H>

using namespace Aleph;

Expand Down Expand Up @@ -47,6 +49,96 @@ AABBTree make_aabb_tree()
return tree;
}

// QuadTree exposes its node structure through public accessors
// (get_root(), QuadNode::get_nw_child()/.../get_se_child(), is_leaf(),
// get_points_set()) but has no built-in TikZ visualizer, so this example
// walks it directly to collect leaf regions (by depth) and stored points.
struct QuadTreeSnapshot
{
Array<Rectangle> leaf_regions;
Array<size_t> leaf_depths;
Array<Point> points;
};

void collect_quadtree_rec(QuadNode * node, const size_t depth, QuadTreeSnapshot & snap)
{
if (node == nullptr)
return;

if (node->is_leaf())
{
snap.leaf_regions.append(Rectangle(node->get_min_x(), node->get_min_y(),
node->get_max_x(), node->get_max_y()));
snap.leaf_depths.append(depth);
for (const Point & p : node->get_points_set())
snap.points.append(p);
return;
}

collect_quadtree_rec(node->get_nw_child(), depth + 1, snap);
collect_quadtree_rec(node->get_ne_child(), depth + 1, snap);
collect_quadtree_rec(node->get_sw_child(), depth + 1, snap);
collect_quadtree_rec(node->get_se_child(), depth + 1, snap);
}

QuadTreeSnapshot collect_quadtree(QuadTree & tree)
{
QuadTreeSnapshot snap;
collect_quadtree_rec(tree.get_root(), 0, snap);
return snap;
}

// Colors cycle by subdivision depth so sibling quadrants at the same depth
// share a color and successive splits are visually distinguishable.
std::string depth_color(const size_t depth)
{
static const char * colors[] =
{"blue!55", "orange!70!black", "green!55!black", "red!60", "violet!65", "brown!60"};
constexpr size_t num_colors = sizeof(colors) / sizeof(colors[0]);
return colors[depth % num_colors];
}

Array<Point> make_quadtree_points()
{
// Deliberately uneven spread (a dense cluster plus scattered outliers) so
// the quadtree subdivides non-uniformly across depths. All coordinates
// stay inside [0, 100) since QuadNode::contains() uses a half-open
// [min, max) region: a point exactly on the upper/right root boundary
// would silently fail to insert.
Array<Point> pts;
pts.append(Point(12, 15));
pts.append(Point(18, 22));
pts.append(Point(15, 30));
pts.append(Point(22, 12));
pts.append(Point(28, 28));
pts.append(Point(20, 20));
pts.append(Point(70, 75));
pts.append(Point(80, 65));
pts.append(Point(75, 82));
pts.append(Point(85, 88));
pts.append(Point(60, 15));
pts.append(Point(90, 20));
pts.append(Point(45, 60));
pts.append(Point(35, 85));
pts.append(Point(8, 92));
pts.append(Point(95, 5));
return pts;
}

Array<Rectangle> make_rtree_rectangles()
{
Array<Rectangle> rects;
for (int i = 0; i < 24; ++i)
{
const int col = i % 6;
const int row = i / 6;
const Geom_Number x0 = Geom_Number(col * 10 + (i % 3));
const Geom_Number y0 = Geom_Number(row * 9 + (i % 2) * 2);
rects.append(Rectangle(x0, y0, x0 + 6, y0 + 5));
}
return rects;
}

} // namespace

int main(int argc, char * argv[])
Expand Down Expand Up @@ -107,6 +199,71 @@ int main(int argc, char * argv[])
make_tikz_draw_style("black"),
Tikz_Plane::Layer_Overlay);

// QuadTree: no dedicated visualizer exists yet (unlike KD/Range/AABB
// above), so leaf regions are collected by walking the public node API
// directly (see collect_quadtree_rec) and drawn colored by subdivision
// depth, one wireframe rectangle per leaf.
Tikz_Plane quadtree_plane(200, 120, 6, 6);
quadtree_plane.put_cartesian_axis();
quadtree_plane.put_coordinate_grid(10, 10, true);
quadtree_plane.set_point_radius_mm(0.7);

QuadTree quadtree(Geom_Number(0), Geom_Number(100), Geom_Number(0), Geom_Number(100), 3);
for (const Point & p : make_quadtree_points())
quadtree.insert(p);

const QuadTreeSnapshot qsnap = collect_quadtree(quadtree);
for (size_t i = 0; i < qsnap.leaf_regions.size(); ++i)
put_in_plane(quadtree_plane, qsnap.leaf_regions(i),
tikz_wire_style(depth_color(qsnap.leaf_depths(i))),
Tikz_Plane::Layer_Default);
put_points(quadtree_plane, qsnap.points, tikz_points_style("black"));
put_in_plane(quadtree_plane,
Text(Point(-2, 105),
"QuadTree leaves=" + std::to_string(qsnap.leaf_regions.size()) +
", points=" + std::to_string(qsnap.points.size())),
make_tikz_draw_style("black"),
Tikz_Plane::Layer_Overlay);

// R-tree (Guttman quadratic split) and R*-tree, built from the *same*
// rectangles and queried with the same rectangle, so the two split
// heuristics can be compared directly: R*-tree typically yields less
// node overlap for the same data.
Tikz_Plane rtree_plane(200, 120, 6, 6);
rtree_plane.put_cartesian_axis();
rtree_plane.put_coordinate_grid(10, 10, true);

RTree<int, 4, 2> rtree;
RStarTree<int, 4, 2> rstar_tree;
{
const Array<Rectangle> rects = make_rtree_rectangles();
for (size_t i = 0; i < rects.size(); ++i)
{
rtree.insert(rects(i), static_cast<int>(i));
rstar_tree.insert(rects(i), static_cast<int>(i));
}
}
const Rectangle rtree_query(8, 5, 28, 20);
const auto rt_result = visualize_rtree_query(rtree_plane, rtree, rtree_query);
put_in_plane(rtree_plane,
Text(Point(-2, 47),
"R-tree (Guttman) nodes=" + std::to_string(rt_result.snapshot.nodes.size()) +
", hits=" + std::to_string(rt_result.query_hit_boxes.size())),
make_tikz_draw_style("black"),
Tikz_Plane::Layer_Overlay);

Tikz_Plane rstar_plane(200, 120, 6, 6);
rstar_plane.put_cartesian_axis();
rstar_plane.put_coordinate_grid(10, 10, true);

const auto rstar_result = visualize_rtree_query(rstar_plane, rstar_tree, rtree_query);
put_in_plane(rstar_plane,
Text(Point(-2, 47),
"R*-tree nodes=" + std::to_string(rstar_result.snapshot.nodes.size()) +
", hits=" + std::to_string(rstar_result.query_hit_boxes.size())),
make_tikz_draw_style("black"),
Tikz_Plane::Layer_Overlay);

out << "\\documentclass[tikz,border=8pt]{standalone}\n"
<< "\\usepackage{tikz}\n"
<< "\\begin{document}\n\n";
Expand All @@ -116,9 +273,15 @@ int main(int argc, char * argv[])
range_plane.draw(out, true);
out << "\n\\vspace{4mm}\n\n";
aabb_plane.draw(out, true);
out << "\n\\vspace{4mm}\n\n";
quadtree_plane.draw(out, true);
out << "\n\\vspace{4mm}\n\n";
rtree_plane.draw(out, true);
out << "\n\\vspace{4mm}\n\n";
rstar_plane.draw(out, true);
out << "\n\\end{document}\n";

std::cout << "Generated " << output_path << '\n';
std::cout << "Compile with: pdflatex " << output_path << '\n';
return 0;
}
}
2 changes: 1 addition & 1 deletion Tests/prefix_tree_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ namespace
track_allocations.store(false, std::memory_order_relaxed);
}

[[nodiscard]] int balance() const noexcept
[[nodiscard]] static int balance() noexcept
{
return tracked_balance.load(std::memory_order_relaxed);
}
Expand Down
75 changes: 42 additions & 33 deletions Tests/r_star_tree_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@
#include <memory>
#include <random>
#include <utility>
#include <vector>

#include <tpl_r_star_tree.H>

#include "r_tree_debug_snapshot_test_helpers.H"

using namespace Aleph;
using namespace Aleph::test_helpers;

namespace
{
Expand All @@ -58,47 +60,41 @@ namespace
}

template <typename Tree>
std::vector<int> sorted_intersects(const Tree &tree, const Rectangle &q)
Array<int> sorted_intersects(const Tree &tree, const Rectangle &q)
{
Array<int> hits = tree.search_intersects(q);
std::vector<int> out;
for (size_t i = 0; i < hits.size(); ++i)
out.push_back(hits(i));
std::sort(out.begin(), out.end());
return out;
std::ranges::sort(hits);
return hits;
}

std::vector<int> brute_intersects(const std::vector<std::pair<Rectangle, int>> &ref,
const Rectangle &q)
Array<int> brute_intersects(const Array<std::pair<Rectangle, int>> &ref,
const Rectangle &q)
{
std::vector<int> out;
Array<int> out;
for (const auto &[b, id] : ref)
if (b.intersects(q))
out.push_back(id);
std::sort(out.begin(), out.end());
out.append(id);
std::ranges::sort(out);
return out;
}

std::vector<int> brute_contains(const std::vector<std::pair<Rectangle, int>> &ref,
const Point &p)
Array<int> brute_contains(const Array<std::pair<Rectangle, int>> &ref,
const Point &p)
{
std::vector<int> out;
Array<int> out;
for (const auto &[b, id] : ref)
if (b.contains(p))
out.push_back(id);
std::sort(out.begin(), out.end());
out.append(id);
std::ranges::sort(out);
return out;
}

template <typename Tree>
std::vector<int> sorted_contains(const Tree &tree, const Point &p)
Array<int> sorted_contains(const Tree &tree, const Point &p)
{
Array<int> hits = tree.search_contains(p);
std::vector<int> out;
for (size_t i = 0; i < hits.size(); ++i)
out.push_back(hits(i));
std::sort(out.begin(), out.end());
return out;
std::ranges::sort(hits);
return hits;
}

// Randomized parity + per-step invariant check for a given fanout.
Expand All @@ -112,7 +108,7 @@ namespace
std::uniform_int_distribution<int> extent(0, ext);

RStarTree<int, Max, Min> tree;
std::vector<std::pair<Rectangle, int>> ref;
Array<std::pair<Rectangle, int>> ref;
int next_id = 0;

auto random_rect = [&]()
Expand All @@ -124,20 +120,21 @@ namespace

for (int iter = 0; iter < iters; ++iter)
{
const int op = ref.empty() ? 0 : op_dist(rng);
const int op = ref.is_empty() ? 0 : op_dist(rng);
if (op == 0)
{
const Rectangle b = random_rect();
const int id = next_id++;
tree.insert(b, id);
ref.emplace_back(b, id);
ref.append(std::make_pair(b, id));
}
else if (op == 1)
{
std::uniform_int_distribution<size_t> pick(0, ref.size() - 1);
const size_t k = pick(rng);
ASSERT_TRUE(tree.erase(ref[k].first, ref[k].second));
ref.erase(ref.begin() + k);
ASSERT_TRUE(tree.erase(ref(k).first, ref(k).second));
std::swap(ref(k), ref(ref.size() - 1)); // swap-and-pop; order is irrelevant here
[[maybe_unused]] const auto popped = ref.remove_last();
}
else
{
Expand Down Expand Up @@ -176,7 +173,7 @@ TEST(RStarTree, SingletonAndErase)
RStarTree<int> tree;
tree.insert(rect(0, 0, 2, 2), 42);
EXPECT_EQ(tree.size(), 1u);
EXPECT_EQ(sorted_intersects(tree, rect(1, 1, 5, 5)), (std::vector<int>{42}));
EXPECT_EQ(sorted_intersects(tree, rect(1, 1, 5, 5)), build_array<int>(42));
EXPECT_TRUE(tree.erase(rect(0, 0, 2, 2), 42));
EXPECT_TRUE(tree.is_empty());
EXPECT_EQ(tree.height(), 0u);
Expand All @@ -200,11 +197,23 @@ TEST(RStarTree, ForcedReinsertionKeepsTreeValid)
for (int i = 0; i < 500; ++i)
{
const Rectangle b = rect(i % 50, i / 50, i % 50 + 3, i / 50 + 3);
const std::vector<int> hits = sorted_intersects(tree, b);
const Array<int> hits = sorted_intersects(tree, b);
EXPECT_TRUE(std::find(hits.begin(), hits.end(), i) != hits.end());
}
}

TEST(RStarTree, DebugSnapshotStructuralInvariants)
{
RStarTree<int> tree; // Max=16, Min=8; same insert pattern as forced reinsertion
for (int i = 0; i < 500; ++i)
tree.insert(rect(i % 50, i / 50, i % 50 + 3, i / 50 + 3), i);

const auto snap = tree.debug_snapshot();
ASSERT_LT(snap.root, snap.nodes.size());
const size_t total_entries = check_snapshot_node(snap, snap.root, 0);
EXPECT_EQ(total_entries, tree.size());
}

TEST(RStarTree, CoincidentRectangles)
{
RStarTree<int, 4, 2> tree;
Expand All @@ -229,7 +238,7 @@ TEST(RStarTree, DegenerateRectangles)
ASSERT_TRUE(tree.verify());
EXPECT_EQ(tree.size(), 60u);

const std::vector<int> hits = sorted_contains(tree, pt(10, 5));
const Array<int> hits = sorted_contains(tree, pt(10, 5));
EXPECT_TRUE(std::find(hits.begin(), hits.end(), 10) != hits.end());
EXPECT_TRUE(std::find(hits.begin(), hits.end(), 105) != hits.end());
}
Expand Down Expand Up @@ -278,12 +287,12 @@ TEST(RStarTree, SupportsMoveOnlyPayload)
TEST(RStarTree, AssignmentClearAndDrainRemainValid)
{
RStarTree<int, 4, 2> source;
std::vector<std::pair<Rectangle, int>> ref;
Array<std::pair<Rectangle, int>> ref;
for (int i = 0; i < 100; ++i)
{
const Rectangle b = rect(i % 20, i / 20, i % 20 + 3, i / 20 + 2);
source.insert(b, i);
ref.emplace_back(b, i);
ref.append(std::make_pair(b, i));
}
ASSERT_TRUE(source.verify());

Expand Down
Loading
Loading